Exchange Online PowerShell with MFA enforced using Azure Automation

Secure unattended PowerShell against Exchange Online in Azure Automation using Certificate access.

Securely accessing Exchange Online PowerShell with privileged access (Exchange Administrator) – while MFA is enforced through Conditional Access? It has been a real headache for many admins!

For scheduled tasks or Azure Automation, connecting to Exchange Online PowerShell is a must for any scripted solution!

But with new security measures like Conditional Access. And MFA enforcement coming into their rightful place in most organizations, many of these scripts have broken.

A quick fix is just to exclude the account. Or set up conditions in Conditional Access to allow a non MFA connection for unattended scripts.

But connecting without exclusions and keeping the enforcement in place – is something that has driven many admins to tears.

UPDATE: Here is a requested example of how to do this with Managed Identities, which is a much better way.
ExchangeOnlineScripts/ConnectEXOwithMSIRunbookExample.ps1 at main · mardahl/ExchangeOnlineScripts (

Service Principals with Certificates to the rescue!

A picture of some Exchange Online PowerShell

Service Principals or App registrations in Azure AD are secure modern authentication entities. They can give applications access to Microsoft Online Services and more!

This article will try to sum up what you should do to get a working secure connection to Exchange Online PowerShell. But it is written with the expectation that you are very familiar with PowerShell and Azure AD App Registrations.

I used to have a hacky workaround for this with a script in the PowerShell Gallery. And it is still there, but not needed anymore!

Here is the link to the now useless PowerShell Gallery script:

The official ExchangeOnlineManagement V2 module now supports the use of Certificate-Based Authentication with Service Principals!

How to – Azure Automation

If you created your Azure Automation account with a “RunAs” account, it would already have a Service Principal with a certificate (that expires every year btw!).

This article won’t deal with the configuration of Azure Automation, but we cover that in several other articles like this one: Configuring Azure Automation.

So with A RunAs account in Azure Automation, you would first need to install the ExchangeOnlineManagement PowerShell Module into your Azure Automation Account.

Then you can start a new Runbook that can do all sorts of Exchange Online Magic – with this bit of PowerShell code:

$connection = Get-AutomationConnection -Name AzureRunAsConnection
$tenantName = ""
Connect-ExchangeOnline -CertificateThumbprint $connection.CertificateThumbprint -AppId $connection.ApplicationID -Organization $tenantName

In the above code, you would replace “” with your actual tenant name (Tenant ID is not supported!).

But the above code would be worthless without having done the following steps.

Configure the Service Principals permissions for unattended Exchange Online admin access

There are two things that need to be done in order for your Service Principal to have the correct access to Exchange Online PowerShell. If you don’t grant these permissions you will get access denied when connecting with the certificate.

  1. It needs to be a member of the Exchange Administrators Role
  2. It needs to have Application permissions with full access to Exchange Online.

This bit of PowerShell should take care of the permissions – you can run it with the Azure Cloud Shell – like I did!

You will need to have the ID of the App Registration handy and replace it in the code below!

	Script to add service principal to Azure AD Role
	By @michael_mardahl -
	Credits for parts of API Permissions script go to
$appId = "b4xxxxe8-xxee-4a22-axx8-6xxxxxxx0d50"
$servicePrincipal = Get-AzureADServicePrincipal -Filter "AppID eq `'$appId'"
#Adding Role membership
$roleDefinition = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq 'Exchange Administrator'}
Add-AzureADDirectoryRoleMember -ObjectId $roleDefinition.ObjectId -RefObjectId $servicePrincipal.ObjectId
#Adding API Permissions
$api = (Get-AzureADServicePrincipal -Filter "AppID eq '00000002-0000-0ff1-ce00-000000000000'")
$permission = $api.AppRoles | Where-Object { $_.Value -eq 'Exchange.ManageAsApp' }
$apiPermission = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId  = $api.AppId ;
    ResourceAccess = [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = $permission.Id ;
        Type = "Role"
$Application = Get-AzureADApplication | Where-Object {$_.AppId -eq $appId}
$Application | Set-AzureADApplication -ReplyUrls 'http://localhost'
$Application | Set-AzureADApplication -RequiredResourceAccess $apiPermission

After the code has been run, you must go to the App registration page in Azure AD and grant admin consent so you can successfully connect to Exchange Online.

That’s it for getting up and running with Azure Automation against Exchange Online!

How to – Scheduled Tasks

If you are still running some of these scripts from On-Prem, I beg you to consider using Azure Automation. It even has a free option that is usually good enough for most small to midsize companies.

If you must use the task scheduler – for whatever reason. You must create the App Registration manually in Azure AD, and assign the permissions to it as described in the above PowerShell script.

You would then need to upload a certificate to be used during authentication. Luckily this can be a self-signed certificate!

This script can create the certificate on your local machine and upload it to the App Registration. You would then have access to Exchange Online PowerShell from your local machine because you need the corresponding private key. The Private key will be placed on the computer generating the certificate (you can export this to another machine if you like, using certlm.msc).

Create a self-signed certificate for app registration authentication

    Script to create self-signed 10 year valid cert and upload to App Registration
	By @Michael_Mardahl -

$appId = "b4xxxxe8-xxee-4a22-axx8-6xxxxxxx0d50" # App Id from Azure AD that needs certificate auth
$PfxCertPath = '.\MyAppAuth.pfx' #Place to store temporary cert file
$CertificatePassword = '1234SecurePassword' #A password you choose to save the cert with
$certificateName = 'AZEXOAutomateCert' #A certificate name you choose
$ErrorActionPreference = 'Stop'

#Requires -Module Az
if (-not (Get-AzSubscription)){ try { Connect-AzAccount } catch { $_; exit 1 } }
try {
#Creating secure password string
$SecurePassword = ConvertTo-SecureString -String $CertificatePassword -AsPlainText -Force
#Creating 10 year valid self-signed cert
$NewCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My `
                                     -DnsName $certificateName `
                                     -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider' `
                                     -KeyAlgorithm RSA `
                                     -KeyLength 2048 `
                                     -NotAfter (Get-Date).AddYears(10)
#Exporting cert to file
Export-PfxCertificate -FilePath $PfxCertPath `
                      -Password $SecurePassword `
                      -Cert $NewCert -Force
} catch {
    exit 1
#Configure required flags on the certificate
$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable `
    -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
    -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet
# Load the certificate into memory
$PfxCert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($PfxCertPath, $CertificatePassword, $flags) -ErrorAction Stop
#Upload cert to Azure App Registration
$binCert = $PfxCert.GetRawCertData() 
$certValue = [System.Convert]::ToBase64String($binCert)
New-AzADAppCredential -ApplicationId $appId -CertValue $certValue -StartDate $PfxCert.NotBefore -EndDate $PfxCert.NotAfter

You will need the thumbprint of the certificate to add to the following script, which should be at the start of your Scheduled Task PowerShell script. The thumbprint can be viewed in many ways, but the easy way to make sure you have the right one is to just look in the App Registration in Azure, and click on the “Certificates and Secrets” menu item – it will be right there.

$tenantName = ""
#Requires -Module ExchangeOnlineManagement
Connect-ExchangeOnline -CertificateThumbprint $certThumbprint -AppId $appId -Organization $tenantName

That’s it for tips on how to get this working with Scheduled tasks!

Final words

Microsoft has come along way with the new PowerShell modules. However, granularity on the backend is still missing, and the true graph experience for Exchange Automation is still not there yet… We can only dream for now!

Please don’t hesitate to follow and reach out on LinkedIn and Twitter to discuss this and more automation goodness.

Thanks for reading!

Michael Mardahl

Michael Mardahl is a seasoned IT pro with over 25 years of experience under his belt. He's a Microsoft Certified Cloud Architect at APENTO in Denmark, where he helps customers move from traditional infrastructure to the cloud while keeping security top of mind. When he's not working, Michael's either spending time with his family and friends or passionately blogging about Microsoft cloud technology. His expertise in this area has even earned him the prestigious title of Microsoft Most Valuable Professional (MVP) in both the Enterprise Mobility and Security categories. In short, Michael is the IT equivalent of a rockstar, but don't expect him to act like one - he's way too down-to-earth for that.


  • dont have role Exchange Service Administrator in my tenant. What role should i be using?

    • Hi Nima,
      I know these names can be confusing because they are not always aligned throughout the services. So if you don’t see Exchange Service Administrator, you should see Exchange Administrators instead.

  • Hi Michael
    Great article!
    I have question about Exchange Hybrid.
    Do we have any smarter option than to use the Task Scheduler approach?
    I don’t see any, but maybe there is something I’m missing 🙂

    • I have begun using Azure Automation extensively, the free allowance is enough in many cases.
      Extending it with a hybrid worker can make it fit into almost any scenario!

  • Sweet, and if I understand correctly each cmdlet/module (Connect-exchangeonline, connect-msgraph, connect-azuread and so on) needs to have corresponding -CertificateThumbprint as an option for this to work? I mean, even though this works unattended for Exchange online today if I wanted to do the same with the Intune Powershell SDK or AzureAD the engineers at MS would need to incorporate support for certbased auth there aswell?


Categories use cookies to ensure that we give you the best experience on our website.