1 minute read

Introduction

There are more options like Graph API on Exchange Online compared with Exchange Server for bulk tasks for user mailboxes. It’s necessary to get used to EWS for those jobs on Exchange Server.
Using service account with Applicationimpersonation role, let’s practice authentication process for bulk jobs.

Prerequisites

  1. Download EWS managed API
  2. Assign Applicationimpersonation for to service account

Download EWS Managed API

You can get it from following link: NuGet Gallery | Exchange.WebServices.Managed.Api

Using package manager or, direct download is available.
ews-download

You can find it on folder named ‘lib’ after extract ZIP file.
We will use Microsoft.Exchange.WebServices.dll
ews-download

Assign Applicationimpersonation role

We can use ECP or PowerShell to assign role.

Using ECP

On ECP, add new role group by using permissions -> Admin role
For new added group, assign Applicationimpersonation role.
Add your service account as member.
Applicationimpersonation also used to mailbox migration from Exchange to 3rd party email services.

ecp-role

Using PowerShell

It can be done by one simple line of cmdlet.

New-RoleGroup -Name "Application impersonation" -Roles "Applicationimpersonation" -Members "admin@3namu.shop"

Connection

In this article, we are uing PowerShell.

Import downloaded EWS managed API library.
Please modify file path depends on your environment.

Import-Module "C:\Users\ho\Downloads\microsoft.exchange.webservices.2.2.0\lib\40\Microsoft.Exchange.WebServices.dll"

Set target mailbox.

$MailboxName ="on@3namu.shop"

Provide credential for service account.

$PSCredential = Get-Credential

Create EWS service instance, and add credential, service URL.
Normally, application (script) connecting to their own mailbox, steps to this point are sufficient to access mailbox using EWS.

$Service = [Microsoft.Exchange.WebServices.Data.ExchangeService]::new()
$Service.Credentials = New-Object System.Net.NetworkCredential($PSCredential.UserName.ToString(),$PSCredential.GetNetworkCredential().password.ToString())
$Service.Url = "https://mail.3namu.shop/EWS/Exchange.asmx"

But accessing other mailboxes without permission, an error occurs.

When trying to connect target mailbox,

$folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox ,$MailboxName)
$folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service,$folderid)

A permission error occurs like below:

Exception calling "Bind" with "2" argument(s): "The specified object was not found in the store."
At line:2 char:1
+ $folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ServiceResponseException

We need to fill ImpersonatedUserId property to use impersonated permission.

$ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SMTPAddress,$MailboxName)
$service.ImpersonatedUserId = $ImpersonatedUserId

If trying to connect mailbox again, we can see it work now.

mailbox-connected

Apply to…

A good example for using impersonated permission, we can imagine to empty RecipientCache folder for all mailboxes in organization, retrieved by Get-Mailbox cmdlet.

$mailboxes = Get-Mailbox -Resultsize Unlimited

$mailboxes | %{
  $MailboxName = $_.WindowsEmailAddress
  $ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SMTPAddress,$MailboxName)
  $service.ImpersonatedUserId = $ImpersonatedUserId
  $folderid= new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::RecipientCache, $MailboxName)
  $folder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($service, $folderid)
  $folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete, $true)
}