Table of Contents
Exchange Online PowerShell Assumes Administrators Run Its Cmdlets
My last primer article in the Azure Automation series covered how to send email using the Exchange Online High-Volume Email (HVE) facility. HVE is still in preview (Microsoft is targeting September 2025 for general availability) but it still does a nice job of sending email from scheduled automation jobs.
This article discusses how to create and execute Azure Automation Exchange runbooks using PowerShell cmdlets from the Exchange Online management module. Unlike HVE, which doesn’t require any Exchange cmdlets, Automation accounts that use the Exchange module in their jobs need some special configuration. This is because the Exchange module assumes that anyone running its cmdlets is an Exchange administrator. There’s no concept of least privilege implemented in the module: once a process loads the module, it can act like a human administrator.
Loading Exchange Online PowerShell into an Automation Account
At least, an app can be all-powerful for Exchange if it meets three conditions. First, it can load the Exchange Online management module. For Azure automation accounts, this means that module is loaded as a resource into the account (Figure 1).

At the time of writing, Exchange Online PowerShell only supports PowerShell V5.1 for automation runbooks, so be sure to install that version of the module. Due to module dependencies, you must install the PackageManagement and PowerShellGet modules (loaded jn that order) before installing the Exchange Online module.
Assigning Exchange Online Permissions and Roles for the Automation Account
Second, the service principal for the app must be assigned the Exchange administrator RBAC role. For Azure Automation, this means the service principal for the automation account. The assignment can be done through the Entra admin center (Figure 2) or with PowerShell. Make sure that you select the correct automation account from the set of enterprise applications listed in the picker.

Third, the app must be assigned the Exchange.ManageAsApp permission. This is not a Microsoft Graph permission. It is an Office 365 Exchange Online permission designed to allow apps to act as administrators. The assignment can only be made through PowerShell. Here’s how to do the job with the Microsoft Graph PowerShell SDK:
$ExoApp = Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'" $TargetSP = Get-MgServicePrincipal -filter "displayname eq 'M365Automation'" $Role = $ExoApp.AppRoles | Where-Object {$_.DisplayName -eq "Manage Exchange As Application"} $AppRoleAssignment = @{} $AppRoleAssignment.Add("PrincipalId",$TargetSP.Id) $AppRoleAssignment.Add("ResourceId",$ExoApp.Id) $AppRoleAssignment.Add("AppRoleId",$Role.Id) $RoleAssignment = New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $TargetSP.Id -BodyParameter $AppRoleAssignment If ($RoleAssignment.AppRoleId) { Write-Host ("{0} permission granted to {1}" -f $Role.Value, $TargetSP.DisplayName) }
Creating a Runbook to use Exchange Online Cmdlets
With the three prerequisites in place, you can create a runbook. To test that everything works as expected, create a V5.1 PowerShell runbook with the following code (replace the organization name with your tenant):
Connect-ExchangeOnline -ManagedIdentity -Organization Office365itpros.com (Get-OrganizationConfig).DisplayName
Save the runbook and use the test pane to execute it. The output should be the display name for your organization. If that’s all you see, you can go ahead and build out the runbook with code to do more useful work.
As a demonstration, I took the script to report missing properties for user mailboxes and copied it into the runbook. The only changes that I made were:
- Remove the code that checks for an active connection to Exchange Online at the start of the script and replace it with the Connect-ExchangeOnline -ManagedIdentity command.
- Remove the Clear-Host cmdlet (Azure Automation doesn’t have a host to clear).
- Replace the Write-Host cmdlet with Write-Output (Azure Automation outputs everything together (a stream) at the end of a job).
- Remove the code to output the results as an CSV file at the end of the script.
Figure 3 shows the output of the runbook in the test pane. Everything works and we know that there are some mailboxes with missing properties that should be addressed.

Azure Automation can create an output file on the headless server where the runbook executes, but the question is then how to copy the file to somewhere more accessible later. The easy answer is to use HVE to send the file as an email attachment or to include the data in the body of a message. Something more complicated, like creating a file in a SharePoint Online site, will require more effort.
Not So Difficult
Running Exchange Online scripts in Azure Automation isn’t difficult once the initial setup for the automation account is in place. Some tweaking of the script code is probably necessary, but it’s not difficult to make the changes and will become second nature after a while. If you need to run jobs that process large numbers of Exchange objects (like mailboxes), Azure Automation is an excellent platform choice.
Need some assistance to write and manage PowerShell scripts for Microsoft 365? Get a copy of the Automating Microsoft 365 with PowerShell eBook, available standalone or as part of the Office 365 for IT Pros eBook bundle.
Thanks. I needed this literally today. 🙂
So, a couple things to note.
You can use PowerShell 7.2, but you are limited to ExchangeOnlineManagement module 3.5.0 only, no later.
To get this module installed, you have to install required version on your PC, then export the module and import it to the Automation Account manually, not by using the gallery. There is a reddit post (not sure if I can link it here) with instructions.
Also, if you happen to be using both Exchange and Azure cmdlets in the same runbook, make sure you put “import-module ExchangeOnlineManagement” and “connect-exchangeonline” PRIOR to both “Import-module Az.Accounts” and “Connect-AzAccount”
eg….
#connect to exchange using managed identity
import-module ExchangeOnlineManagement
Connect-ExchangeOnline -ManagedIdentity -Organization contoso.onmicrosoft.com
Write-Output “connected to exchange, now AZ”
# Ensures you do not inherit an AzContext in your runbook
Disable-AzContextAutosave -Scope Process
import-module az.accounts
# Connect to Azure with system-assigned managed identity
Connect-AzAccount -Identity
If you connect to Azure first, you will get an error and be unable to connect to exchange.
Thanks for pointing out that EXO 3.5 works with a PowerShell 7.2 runbook. I see some other discussion in https://stackoverflow.com/questions/79362037/automation-account-importing-powershell-module-issue on this topic. I will take it up with Microsoft as there’s no good reason why the latest EXO module should not work with a 7.2 runbook, just like other well-mannered modules.
The problem with module clashes is not unknown. Exchange and Teams have one (https://office365itpros.com/2023/10/20/powershell-module-clash-exo/) too.
I’m intrigued by you importing modules in a runbook. If the modules are added as resources for the automation account, why do you need to impport them explicitly?
You need to explicitly import a specific version if you need it for a specific runtime.
Try to install EchangeOnlineManagement for a 7.2 runtime on version 3.5 – it is not possible. PSGallery does not allow you to specify the module version AND the runtime, it will instead import the most recent.
Even using AZ powershell within the cloudshell does not work.
The workaround is manual, but it works.
Here is the link: https://www.reddit.com/r/PowerShell/comments/1f2v977/exchangeonlinemanagement_in_azure_runbooks/
Ah, OK… I see your thinking.
I’ve sent the issue on to some people at Microsoft who might be able to help. It’s silly that the latest version of the EXO module doesn’t work with a PowerShell 7.2 runbook.
did you ever got an update Toni
I did. I was chatting with some folks in Microsoft yesterday who are aware of the problem and are discussing fixes. There are several issues around Azure Automation right now, several being linked to component dependencies (see yesterday’s post). I think it’s fair to say that Microsoft knows they have work to do to fix PowerShell for Microsoft 365…
Thank you Tony, this helped me a bunch!
PS: You forgot to blur out the GUID in Figure 2
Hi, thank you for this tutorial.. I’am stuck when adding permissions to the application with your script.
New-MgServicePrincipalAppRoleAssignment : Cannot convert the literal ” to the expected type ‘Edm.Guid’.
I just ran the code with SDK V2.26.1 and it worked perfectly. Did you use V2.26 by any chance?
Hi Tony, thank you for your quick reply. I used the 2.26.1 and that works.
Hence when I run the command in the runbook in Test:
Connect-ExchangeOnline -ManagedIdentity -Organization domainname
then i get > UnAuthorized (UnAuthorized)
I checked the permissions > Exchange.ManageAsApp > thats ok.. and the RBAC Exchange rol is assigned to the application.
There are some problems with Azure Automation and clashes between Exchange Online and the Graph SDK that Microsoft is sorting out now. Anyway, I am glad that you have sorted the problem.
Hi Tony
Thank you for this, I have followed the steps and I am able to connect to exchange online fine with Managed Identity and even able to get the Distribution group but when I try to update my Distribution group membership, I get the below error
System.Management.Automation.CommandNotFoundException: The term ‘Add-DistributionGroupMember’ is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Can you please let me know what am I missing
What version of PowerShell and the EXO module are you using?
I did a quick test using PowerShell 5.1 runbook and EXO V3.7.2 and everything worked.
Thank you for the quick reply , Run book version 5.1, Exchange 3.8.0, Is there a way i can downgrade in the runbook to that module
Search for https://www.google.com/search?q=install+earlier+version+of+powershell+module+azure+automation+account&rlz=1CDGOYI_enIE1105IE1105&oq=install+earlier+version+of+powershell+module+azure+auto&gs_lcrp=EgZjaHJvbWUqBwgDECEYoAEyBggAEEUYOTIHCAEQIRigATIHCAIQIRigATIHCAMQIRigATIHCAQQIRigATIHCAUQIRigAdIBCTQ5OTQ3ajBqN6gCGbACAeIDBBgBIF_xBYMjn09b3VtB&hl=en-GB&sourceid=chrome-mobile&ie=UTF-8 and follow the instructions to install an earlier version.
I downgraded, and the strange thing is that any set-commands (Set-Mailbox, Set-DistributionGroupMember) are causing an error, while get-commands (Get-Mailbox, Get-DistributionGroup) are working correctly.
I’ll flag this to Microsoft. I see the same issues. Please report it to support for your tenant. That will make sure that the issue is recorded officially.
I found that V3.8.0 of the Exchange Online management module works with V5.1 PowerShell runbooks. There might be a clash with runtime components similar to that experienced with .NET by the Microsoft Graph PowerShell SDK . I have pinged the EXO PS team to ask what’s going on.
Thank you for confirming, I just created a case with them