When it comes to automating Exchange Online operations, IT administrators can find many examples that use Azure Automation. Most Azure Automation scenarios use PowerShell Runbooks. A complete walkthrough of using Azure Automation with Exchange Online can be found in this article.
But what about Azure Functions?
Introducing Azure Functions
Azure Functions is a serverless computing platform, which can be triggered by different events:
- Queue
- Timer
- Event Grid
- HTTP
The full list of triggers is described in the list of supported bindings. At first glance, the number of triggers seems to be very limited, but don’t underestimate the capabilities of the Event Grid trigger. This trigger is a service that allows an app to receive messages from Azure services, which makes it very flexible. One example is to use a configured alert from a Log Analytics workspace.
Why use Azure Functions for Exchange Online?
For years customers have asked Microsoft to implement some sort of recipient management in Microsoft Graph. However, nothing is currently available. Yes, it’s possible to work with Microsoft 365 groups through the Graph Groups API, but you cannot perform Exchange Online tasks like assigning mailbox or mailbox folder permissions. This means that any system that wants to interact with Exchange Online must use the Exchange Online PowerShell V3 module.
In some cases, it’s not realistic to use Exchange Online PowerShell. Many systems like ServiceNow and Ping Identity prefer a JSON-formatted API. But it’s not all about these systems. Think about self-service scenarios where a user wants to check permissions for a mailbox or enable an archive mailbox. These and other operations could be performed through a web portal or a developed app supported by a variety of platforms like Windows, Mac, Android, or iOS.
Another reason to use Azure functions to manage Exchange Online is the fact that HTTP requests can trigger PowerShell Runbooks but will not return any data to a client. An end-user or an app expects a response with the requested data. The failure to receive a response leads to a poor user experience.
This article describes an example of how to build an Azure function to retrieve mailbox permissions. End users cannot see or set mailbox permissions. Only an administrator has the necessary privileges to maintain permissions on the mailbox level: The most common scenario when users need administrator intervention to set mailbox permissions is to update permissions for a shared mailbox.
Creating a Function App
As a prerequisite to creating an Azure Function, you must have an Azure subscription for your tenant.
In the following steps, we create a function to return the mailbox permission for a given mailbox. The client, which could be a mobile app or any self-service portal, is unimportant. Briefly, we use PowerShell to send a request to our Azure Function. The response is a JSON-formatted payload containing mailbox permissions that our app can interpret and display to the user.
Create app
To start, create a Function app (to contain the functions) by following the steps outlined in Microsoft’s documentation. Make sure to select the correct runtime stack (PowerShell Core). It’s up to you to define the storage account for the app to use. Figure 1 shows the basic properties of the function app, while Figure 2 shows a summary of the app before creation.
After creating the function app, for best performance, make sure that the app uses the 64-bit platform (Figure 3).
Use System Assigned Managed Identity
The function app must be able to authenticate before it can run any functions. It uses a system-assigned managed identity for this purpose (Figure 4). The functions run in the context of the managed identity. For more information about using managed identities, read this article and this article about securing managed identities.
Assign Administrative Role to the Managed Identity
After enabling the app to use a managed identity, you need to assign the necessary role to the service principal of the managed identity. The easiest way (from my perspective) is to go to Roles and Administrators, select Exchange Recipient, then Administrator Active assignments, and add the service principal of the managed identity used by the app (Figure 5).
Note: You might ask why I selected the Exchange Recipients Administrator role rather than the Exchange Administrator role. The reason is simple: always use the role with the least privilege necessary to do the job.
Grant Admin Consent to the Service Principal
The next step grants the Manage Exchange as Application role to the service principal of the managed identity. If this is not done, the app cannot run PowerShell commands as an administrator, even with the assigned role. This article explains how to add the Manage Exchange as Application role to the managed identity using PowerShell (you can’t make the role assignment through the GUI). You’ll end up with a situation like that shown in Figure 6.
Add Exchange Online PowerShell V3 Module
As we want to use PowerShell to connect to Exchange Online, we need to add the Exchange Online management module to our app. This is done by modifying the requirements.psd1 file (Figure 7).
Create the Function
Now the Function app is configured, we can create a function itself. For our scenario, we chose the HTTP trigger template. Give the function a suitable name, like GetMailboxPermissions (Figure 8).
Whenever you create a function, the service add some demo code to illustrate how to use a function. Delete this code and replace it with the code shown below.
using namespace System.Net # Input bindings are passed in via param block. param($Request, $TriggerMetadata) Write-Host "Connecting to EXO..." $paramsEXO = @{ ManagedIdentity = $true Organization = 'M365x….onmicrosoft.com'# replace with your tenant name ShowBanner = $false CommandName = @('Get-EXOMailboxPermission','Get-MailboxPermission') ErrorAction = 'Stop' } try { Connect-ExchangeOnline @paramsEXO } catch{ # create response body in JSON format $body = $_.Exception.Message | ConvertTo-Json -Compress -Depth 10 break } # get name from query parameter $name = $Request.Query.Name if (-not [System.String]::IsNullOrEmpty($name)) { try { Write-Host "Retrieving mailbox permissions for:$name" $paramsPerms = @{ Identity = $name ErrorAction = 'Stop' } $permEXO = Get-EXOMailboxPermission @paramsPerms $body = $permEXO | ConvertTo-Json -Compress } catch{ # create response body in JSON format $body = $_.Exception.Message | ConvertTo-Json -Compress -Depth 10 } } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = $body })
Using the Function
Now we have created our function, we can use it by sending it a request. In this example, I wanted to get the permissions set on the mailbox AdeleV@M365x16362668.OnMicrosoft.com. Here’s how to do the job with some simple PowerShell:
$Uri = 'https://tecdemo.azurewebsites.net/api/GetMailboxPermissions?name=AdeleV@M365x16362668.OnMicrosoft.com' $paramsReq = @{ Uri = $Uri Method = 'GET' Headers = @{'x-functions-key' = '8ejgOTwP2HSElWs5fo_hiwdcqjQQTPexylTRxmzUca9qAzFusF7j_g=='} } $Resp = Invoke-WebRequest @paramsReq
The Uri used in the function contains the following:
- tecdemo: the name of the Function App.
- azurewebsites.net/api: the default namespace for Azure Function.
- GetMailboxPermissions: the name of the function within the Function App.
You might have spotted that the parameters include a header called x-functions-key. This header contains the key configured on the function (Figure 9). Azure creates this key.
This is one way of securing your Azure function. If you don’t pass the key in the request, the response is error 401 unauthorized. Other ways securing your function are documented here.
The response to the function is JSON formatted and can be easily converted. Figure 10 shows the raw response.
It’s easy to convert the JSON data in the response to something more like what an administrator sees when they interrogate mailbox permissions using the Exchange Online management module (Figure 11).
Limitations
The most important limitation you need to understand is a timeout (the time when the function must respond to the request). The default is 5 minutes and can be increased to a maximum of 10 minutes. If you use a different Azure plan, you can get an unlimited timeout, but it’s all a question of costs (plans can get expensive!). Other limits are described in Service limits.
Conclusion
If you have long-running administrative tasks, I recommend using PowerShell Runbooks. However, if you have the need to interact with Exchange Online without using PowerShell, you should consider Azure Functions.
If you want you can meet me at The Experts Conference (TEC) 2023, where I’m speaking about this topic in more detail.
Microsoft Platform Migration Planning and Consolidation
Simplify migration planning, overcome migration challenges, and finish projects faster while minimizing the costs, risks and disruptions to users.
Great article!, it was helpful for me. Thanks.
Hi Egor,
I see where you coming from and what you’re aiming for. This was the regular and supported way. Let’s wait for other upcoming posts…
Ciao,
Ingo
This is interesting, but I believe the time of the Exchange management module has passed. As your colleague Michev described, you can use a language of your choice to hit the REST adminApi Exchange endpoints directly, that’s way more lightweight approach.