Configure an auto-reply rule for Microsoft 365 mailboxes via EWS

Every now and then, the question of using an Outlook rule as a form of auto-reply pops up. One typical scenario is when you want to ensure the auto-reply is sent on each received message, as opposed to the behavior of the built-in “out of office”  functionality, which only sends it once per sender. Another common scenario is scoping down the rule to only act on specific messages, i.e. those received from specific recipients/domains, messages of specific types, and so on. While the out of office functionality also supports rules and can address many scenarios, there’s still the need to use Outlook rules in some cases.

Setting such a rule is no rocket science, and many example articles can be found online. The usual caveat is that you cannot use OWA to configure it, only Outlook. The good old “classic” Outlook desktop client that is. The more interesting scenario is when you have to configure such a rule on behalf of another user, i.e. when you need a programmatic solution. While Exchange Online PowerShell can help you with both setting an OOO reply and managing Outlook rules, in this particular scenario it fails short, as the corresponding “have server reply using a specific message” action is not supported. For the same reason, you cannot use the Graph API either, as the list of supported message rule actions also fails short on this requirement. In other words, if you need to use good old (and soon to be deprecated) EWS.

So in this article, we will take a look at how you can leverage the EWS API to configure an auto-reply rule on behalf of another user. The solution is focused on Microsoft 365 users only, and as such uses a “hardcoded” value for the EWS URL. It also takes into account the need to leverage OAuth methods for authentication. Before we dive in though – credit for this solution goes to Glen, whose code I’ve “borrowed”. My only contribution here is packaging the code in a form that is suitable for use with Microsoft 365 mailboxes in 2023. I strongly recommend going over Glen’s other articles and GitHub repo’s if you are interested in working with EWS or Outlook’s Graph API endpoints.

Prerequisites

I’ll try to keep the article short, but there are some prerequisites that need to be met in order for the solution to work. First, you will need a copy of the EWS managed API binaries, which nowadays come only as a NuGet package. If you are reading this article, you are likely an IT Pro and not a dev, so chances are you don’t particularly care about NuGet. Michel has a good guide on how you can leverage PowerShell to install the EWS binaries instead, so use that.

Once the binaries are in place, we can leverage them to establish a connection… but for that we need to handle the authentication pieces. The first piece is obtaining the correct EWS URL, however for Microsoft 365 that’s an easy task – you can hardcode the “https://outlook.office365.com/EWS/Exchange.asmx” value. As for credentials, we need to leverage the OAuth methods in order to obtain a valid Access token to present to the service. Herein, few more details need to be explained.

Firstly, we need to have an Azure AD integrated application we can leverage for obtaining the token. As EWS can be used to perform most of the operations within a user’s mailbox, I’d strongly recommend you create your own app registration and permission it accordingly (check the end of the article for more details on that), in order to minimize the chances of something bad happening. The app registration will need to have the permission consented to, in either the delegate or application permissions model. Let’s briefly talk about those, too.

Applications leveraging delegate permissions run in the context of a given user, and thus only allow you to manage objects to which the user itself has access. While this is great from security perspective, as in the Exchange scenario the user can only make changes to his own mailbox and any mailboxes he’s a delegate for (Full Access), it becomes a problem when you want to run the app against a set of users. Applications leveraging application permission on the other hand get unrestricted access to all objects withing the organization by default (you can restrict it in the case of EWS as we cover later), and are more convenient when you plan to use a given app across a set (or all) of users.

Exchange’s implementation of EWS adds another layer on top of the application permissions model. The so-called impersonation permission allows a given user (or the app) to present itself as another user and perform operations on his behalf. While very powerful, this functionality can also be very destructive in the wrong hands, so make sure to limit the scope by leveraging the methods outlined at the end of the article. In particular, in the application permissions model an app that has been granted the full_access_as_app (impersonation) permission can be used to impersonate every user, by default. On the positive side, this makes the task of handling authentication a bit simpler, as we don’t have to deal with user’s credentials.

The choice of application permissions makes things easier on the application registration front, too. All you need to do is register a new app with the default configuration – no need to specify Redirect URIs or anything. Then, proceed to the API permissions tab to add the required permissions. As Microsoft has long ago hidden the Exchange Online resource from the UI, you will have to either manually edit the manifest file or use the method outlined here to add the desired full_access_as_app Role. Make sure to also Grant admin consent at this stage. Lastly, go to the Certificates & Secrets tab and generate a client secret.

For additional details and code samples for both delegate and application permissions scenarios, you can consult the official documentation.

Authentication

We are now ready to obtain an Access token and pass it to the service in order to create a working Exchange service object. The code below leverages direct HTTPS request to facilitate the process of obtaining a token, by issuing a POST request against the endpoint and providing the client secret as payload.

$tenantID = "tenant.onmicrosoft.com" #your tenantID or tenant root domain
$appID = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #the GUID of your app
$client_secret = "verylongsecurestring" #client secret for the app

$url = 'https://login.microsoftonline.com/' + $tenantId + '/oauth2/v2.0/token'
$body = @{
grant_type = "client_credentials"
client_id = $appID
client_secret = $client_secret
scope = "https://graph.microsoft.com/.default"
}

$tokenRequest = Invoke-WebRequest -Method Post -Uri $url -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing -ErrorAction Stop


$token = ($tokenRequest.Content | ConvertFrom-Json).access_token

If you prefer to use the MSAL methods, here’s an example of that as well:

$app = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder]::Create("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx").WithClientSecret("verylongclientsecret").WithTenantId("xxxxx.onmicrosoft.com").Build()

$Scopes = New-Object System.Collections.Generic.List[string]
$Scope = "https://outlook.office365.com/.default"
$Scopes.Add($Scope)

$token = $app.AcquireTokenForClient($Scopes).ExecuteAsync().Result.AccessToken

Goes without saying that you should NEVER store credentials/secrets in script files!

Creating an auto-reply rule via the EWS methods

Now that we have a valid token, we can proceed by loading the EWS Managed API binaries, creating an Exchange Service object, and authenticating. The same object will later on be used for creating the rule.

Add-Type -Path 'C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll'
$global:exchangeService = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013)
$exchangeService.Url = "https://outlook.office365.com/EWS/Exchange.asmx"
$exchangeService.Credentials = New-Object Microsoft.Exchange.WebServices.Data.OAuthCredentials -ArgumentList $token

At this point, we should have a valid Exchange service object. However, as we are running in the application permissions model, there is no actual user/mailbox against whom we can run the rule operations. So, we need to impersonate a user first:

$exchangeService.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, "user@tenant.onmicrosoft.com")

With that, we are ready to run operations against the user’s mailbox, such as using the GetInboxRules method to list all rules. To create a new rule, we will leverage the UpdateInboxRules method, but first, we need to configure the rule object itself. As mentioned in Glen’s original article and the official documentation, an auto-reply rule configured with the “have server reply using a specific message” action uses a reply template, which is just a new message item with a specific message class. In other words, we need to configure the message template first:

$tmTemplateEmail = New-Object Microsoft.Exchange.WebServices.Data.EmailMessage -ArgumentList $exchangeService
$tmTemplateEmail.ItemClass = "IPM.Note.Rules.ReplyTemplate.Microsoft"
$tmTemplateEmail.IsAssociated = $true
$tmTemplateEmail.Subject = "This is an auto-reply message"
$htmlBodyString = "Dear sender, I am currently out of office."
$tmTemplateEmail.Body = New-Object Microsoft.Exchange.WebServices.Data.MessageBody($htmlBodyString)

$PidTagReplyTemplateId = New-Object Microsoft.Exchange.WebServices.Data.ExtendedPropertyDefinition(0x65C2, [Microsoft.Exchange.WebServices.Data.MapiPropertyType]::Binary)
$tmTemplateEmail.SetExtendedProperty($PidTagReplyTemplateId, [System.Guid]::NewGuid().ToByteArray())
$tmTemplateEmail.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

Of course, feel free to edit the subject line and the actual content of the message as you see fit. Once we have a reply template, we can proceed with the actual rule configuration. Herein, you can go very creative with the set of conditions/exceptions, or even add additional actions as needed. for the same of brevity however, only the minimum set of properties are configured in the example below:

$nrNewInboxRule = New-Object Microsoft.Exchange.WebServices.Data.Rule
$nrNewInboxRule.DisplayName = "Auto Reply Rule"
$nrNewInboxRule.Actions.ServerReplyWithMessage = $tmTemplateEmail.Id
$cnCreateNewRule = New-Object Microsoft.Exchange.WebServices.Data.createRuleOperation[] 1
$cnCreateNewRule[0] = $nrNewInboxRule
$exchangeService.UpdateInboxRules($cnCreateNewRule,$true)

And with that, we are done. The new rule is created and saved and should be in effect immediately. You can verify by running the GetInboxRules method against the mailbox, or simply by sending a message to it. For real-life scenarios, you probably want to add few more conditions, such as the ones outlined in Glen’s original article. And if you want to automate this, I’ve uploaded a sample script that contains all the code bits above over at my GitHub repo. Do make sure to edit the variables before running it!

The script takes a single parameter, -Mailbox, which designates where you want the rule created. No attempt is made to actually validate that the mailbox exists, and overall the error checking in the script is minimal. As with all my other script samples, treat this as a “proof of concept” and not a production-ready solution. And as always, feel free to modify it to best suit your needs. As an example, here’s how to run the script:

.\EWS_Autoreply.ps1 -Mailbox user@domain.com

The above will create an auto-reply rule within the user@domain.com mailbox, assuming you’ve configured all the authentication details. Speaking of which, do make sure to update the authentication block with your preferred method, as well as update the subject and body text of the template message as needed. If you don’t want the rule to run on every message received, consider adding some conditions/exceptions as well.

Addendum: scoping EWS permissions

While leveraging EWS to perform operations on behalf of users is certainly a convenient solution, it can also be a problem due to the broad set of functionalities supported by the API. Combine this with the impersonation capabilities and the unrestricted access you get in application permission scenarios, and you’re in for some annoying talks with your security team. The good news is that EWS impersonation can be restricted to specific mailboxes only. Depending on the type of permissions you are using, you can do one of the following:

  • Application permissions model: Configure an Application access policy as detailed for example in this article.
  • Delegate permissions model: Assign the user under whose identity your app is running to the ApplicationImpersonation role, with a custom management scope restricted to just the mailboxes you want him to be able to access.
  • Or, leverage the recently introduced RBAC for Apps controls, which can be used in both scenarios.

3 thoughts on “Configure an auto-reply rule for Microsoft 365 mailboxes via EWS

    1. Vasil Michev says:

      Hm, those all powerful AI engines don’t seem to consider “autoresponder” as equivalent to “auto-reply”, I’ve no other explanation why I didn’t run stumble into your solution when I did the initial search. And yours is much more robust, in comparison. Meh, could’ve saved me some time 🙂

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.