Primer: Using Exchange Online PowerShell in Azure Automation Runbooks

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).

Selecting the Exchange Online management module to load into an Azure Automation account.
Figure 1: Selecting the Exchange Online management module to load into an Azure Automation account

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.

Selecting the service principal for an automation account to assign the Exchange administrator role.
Figure 2: Selecting the service principal for an automation account to assign the Exchange administrator role

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.

Output from an Exchange Online script run by Azure Automation.

Azure automation Exchange Online.
Figure 3: Output from an Exchange Online script run by Azure Automation

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.

21 Replies to “Primer: Using Exchange Online PowerShell in Azure Automation Runbooks”

  1. 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.

    1. 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?

      1. 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/

      2. 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.

      3. 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…

  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’.

  3. 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.

    1. 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.

  4. 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

    1. 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.

      1. 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

      2. 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.

      3. 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.

Leave a Reply

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