Microsoft 365

Run unattended Exchange Online PowerShell with app-only certificate auth

Set up certificate-based app-only authentication for Exchange Online PowerShell: app registration, Exchange.ManageAsApp, a CSP certificate, least-privilege roles and Azure Automation runbooks.

11 min read
On this page

To run Exchange Online PowerShell scripts without a user account, register an application in Microsoft Entra ID, give it the Exchange.ManageAsApp application permission with admin consent, upload the public key of a CSP-based certificate, and assign it an Exchange-capable role. The script then connects with Connect-ExchangeOnline -AppId <id> -CertificateThumbprint <thumbprint> -Organization contoso.onmicrosoft.com (or -Certificate with a certificate object), with no password, no MFA prompt and no service account.

Who this is for and what you will have at the end

This guide is for administrators and automation engineers who still run scheduled Exchange scripts under a user account with a stored password, or whose jobs broke when MFA or Web Account Manager reached them. By the end you will have:

  • An app registration with only the permission Exchange Online PowerShell needs.
  • A certificate that Exchange app-only authentication accepts, with its private key kept where the script runs.
  • A least-privilege role assignment, either a built-in Entra role or a scoped Exchange role group.
  • Working connection commands for a Windows server, for any platform with a certificate object, and for an Azure Automation runbook.
  • A list of the limitations and errors to expect.

How app-only authentication works

The Exchange Online PowerShell module requests an app-only token using the application ID, the tenant (the Organization parameter) and the certificate. The application has a directory role assigned to it, the role appears in the access token, and Exchange Online uses that role information to build the session's role-based access control. There is no user in the flow, so there is no password to store, no MFA prompt, and no Web Account Manager broker, which the module uses only for user sign-ins.

MethodWhere it runsSecret to manageNotes
Certificate thumbprintWindows onlyPrivate key in the user certificate storeSimplest for an on-premises server
Certificate object (-Certificate)Any platformCertificate fetched at run timeWorks with Azure Automation certificate assets
Certificate file (-CertificateFilePath)Any platform.pfx file plus its passwordMicrosoft notes there is no automated and secure way to supply the password
Managed identityAzure Automation, Azure VMsNoneNo certificate at all

Version requirements are modest: Exchange Online PowerShell app-only connections need module 2.0.4 or later, and Security & Compliance PowerShell needs 3.0.0 or later. In practice, use a current module that matches your PowerShell version; Connect-ExchangeOnline errors and fixes has the support matrix.

Prerequisites

  • Permission to register applications in Microsoft Entra ID and to grant tenant-wide admin consent.
  • Permission to assign Microsoft Entra roles, or Exchange Online permissions to manage role groups if you plan to use custom role groups.
  • The ExchangeOnlineManagement module, plus PowerShellGet and PackageManagement, on the machine that runs the script.
  • Your tenant's primary .onmicrosoft.com domain. App-only connections must use it as the Organization value.
  • Microsoft Graph PowerShell if you want to assign roles from the command line.

Step 1: Register the application

  1. In the Azure portal, search for App registrations and select New registration.
  2. Enter a descriptive Name, for example ExO PowerShell CBA.
  3. Under Supported account types, keep Accounts in this organizational directory only (single tenant). Choose multitenant only for partner scenarios where the same app manages Exchange Online in customer tenants.
  4. Leave Redirect URI empty unless you need it, and select Register.
  5. On the Overview page, copy the Application (client) ID. This is the AppId value for the connection.

Step 2: Grant Exchange.ManageAsApp

  1. In the app, open API permissions and select Add a permission.
  2. On the APIs my organization uses tab, search for and select Office 365 Exchange Online. For Security & Compliance PowerShell, select Microsoft Exchange Online Protection instead; if the app connects to both, add both.
  3. Select Application permissions, expand Exchange, select Exchange.ManageAsApp, and then Add permissions.
  4. Select Grant admin consent for your organization and confirm. The status changes to Granted.
  5. For the default Microsoft Graph > User.Read delegated permission, select ... > Revoke admin consent. The app doesn't need it.

Microsoft 365 GCC High and DoD organizations should add the permission by editing the app manifest instead. The requiredResourceAccess entry for Exchange Online PowerShell looks like this:

"requiredResourceAccess": [
    {
        "resourceAppId": "00000002-0000-0ff1-ce00-000000000000",
        "resourceAccess": [
            {
                "id": "dc50a0fb-09a3-484d-be87-e023b12c6440",
                "type": "Role"
            }
        ]
    },
    {
        "resourceAppId": "00000003-0000-0000-c000-000000000000",
        "resourceAccess": [
            {
                "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
                "type": "Scope"
            }
        ]
    }
],

00000002-0000-0ff1-ce00-000000000000 is the Office 365 Exchange Online resource and dc50a0fb-09a3-484d-be87-e023b12c6440 is the Exchange.ManageAsApp role; both GUIDs are the same in every tenant. Grant admin consent after saving the manifest.

Step 3: Create a certificate Exchange will accept

Exchange app-only authentication doesn't support Cryptography Next Generation (CNG) certificates, and modern Windows creates CNG certificates by default. You need a certificate whose key comes from a CSP provider. A self-signed certificate is fine, as is one from an internal PKI such as AD CS or a commercial CA; the only other requirement is an exportable private key (.pfx) and a public certificate (.cer).

Microsoft's recommended method uses New-SelfSignedCertificate with -KeySpec KeyExchange, run in an elevated session:

$cert = New-SelfSignedCertificate -DnsName 'contoso.com' -CertStoreLocation 'cert:\CurrentUser\My' -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
 
# Private key, password protected, for the machine or service that runs the script
$cert | Export-PfxCertificate -FilePath .\exo-automation.pfx -Password (Get-Credential).Password
 
# Public key, for the app registration
$cert | Export-Certificate -FilePath .\exo-automation.cer
 
$cert.Thumbprint

If the certificate will be uploaded to Azure Automation, note that Automation requires the provider Microsoft Enhanced RSA and AES Cryptographic Provider. New-SelfSignedCertificate accepts that name in its -Provider parameter, so add -Provider 'Microsoft Enhanced RSA and AES Cryptographic Provider' to the command above when you create a certificate for Automation.

Record the expiry date. When the certificate expires, connections fail until you upload a new public key, so put the renewal in your change calendar.

Step 4: Attach the certificate to the app

  1. In the app registration, open Certificates & secrets.
  2. Select Upload certificate, browse to the .cer file, and select Add.
  3. Confirm the certificate appears under Certificates with the thumbprint you recorded.

Only the public key goes to Entra ID. Anyone holding the private key can act as the app with all of its permissions, so protect the .pfx file and its password like a privileged credential and delete working copies once the certificate is installed.

Step 5: Give the app only the permissions it needs

Exchange.ManageAsApp only lets the app connect. What it can do after connecting depends on roles. You have three options.

Option 1: A built-in Microsoft Entra role

Supported roles for Exchange Online PowerShell are Compliance Administrator, Exchange Administrator, Exchange Recipient Administrator, Global Administrator, Global Reader, Helpdesk Administrator, Security Administrator and Security Reader. Exchange Administrator and Global Administrator cover every Exchange Online PowerShell task, including recipient management and anti-spam, anti-malware and anti-phishing settings; Security Administrator doesn't cover those tasks. For read-only reporting, Global Reader is usually enough.

Assign the role in Microsoft Entra roles and administrators by selecting the role name, then Add assignments, then the app. Or use Microsoft Graph PowerShell:

Connect-MgGraph -Scopes RoleManagement.ReadWrite.Directory,Application.Read.All
 
$sp     = Get-MgServicePrincipal -Filter "DisplayName eq 'ExO PowerShell CBA'"
$roleId = (Get-MgRoleManagementDirectoryRoleDefinition -Filter "DisplayName eq 'Global Reader'").Id
 
New-MgRoleManagementDirectoryRoleAssignment -PrincipalId $sp.Id -RoleDefinitionId $roleId -DirectoryScopeId '/'

Option 2: A custom Exchange role group

Use a custom role group when you need to restrict the cmdlets the app can run or limit which recipients it can change with a write scope. Create the role group in Exchange Online first, then link the app's service principal into Exchange and add it:

Connect-MgGraph -Scopes AppRoleAssignment.ReadWrite.All,Application.Read.All
$app = Get-MgServicePrincipal -Filter "DisplayName eq 'ExO PowerShell CBA'"
 
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com
New-ServicePrincipal -AppId $app.AppId -ObjectId $app.Id -DisplayName 'SP for ExO PowerShell CBA'
$sp = Get-ServicePrincipal -Identity 'SP for ExO PowerShell CBA'
 
Add-RoleGroupMember -Identity 'Contoso View-Only Recipients' -Member $sp.Identity

You must be connected to Exchange Online PowerShell when you run New-ServicePrincipal; the command needs the app ID and object ID from Entra ID.

Option 3: Combine both

Role-based access control combines permissions from all sources. You can assign a narrow Entra role such as Exchange Recipient Administrator and add a custom role group for the few extra cmdlets the script needs.

Step 6: Connect from the script

On Windows, with the certificate installed in the current user's store of the account that runs the task:

Connect-ExchangeOnline -CertificateThumbprint 'A1B2C3D4E5F60718293A4B5C6D7E8F9012345678' -AppId '36ee4c6c-0812-40a2-b820-b22ebd02bce3' -Organization 'contoso.onmicrosoft.com' -ShowBanner:$false
 
Get-AcceptedDomain | Format-Table Name
Disconnect-ExchangeOnline -Confirm:$false

-CertificateThumbprint works only on Windows. On Linux or macOS, or whenever the certificate is loaded from somewhere else at run time, pass an X509Certificate2 object with -Certificate. For Security & Compliance PowerShell, use the same parameters with Connect-IPPSSession; note that Connect-IPPSSession isn't currently available in PowerShell 7 on macOS or Linux, so Security & Compliance scripts need to run on Windows. In GCC High or DoD, add -ExchangeEnvironmentName O365USGovGCCHigh or O365USGovDoD to Connect-ExchangeOnline.

In an Azure Automation runbook

Upload the .pfx to the Automation account under Shared Resources > Certificates > Add a certificate, or with New-AzAutomationCertificate. In the runbook, the internal Get-AutomationCertificate cmdlet returns an X509Certificate2 object that you pass straight to -Certificate:

$cert = Get-AutomationCertificate -Name 'ExoAutomation'
 
Connect-ExchangeOnline -Certificate $cert -AppId '36ee4c6c-0812-40a2-b820-b22ebd02bce3' -Organization 'contoso.onmicrosoft.com' -ShowBanner:$false
 
Get-EXOMailbox -ResultSize 10 | Select-Object DisplayName,PrimarySmtpAddress
Disconnect-ExchangeOnline -Confirm:$false

Import the ExchangeOnlineManagement module into the account or runtime environment that the runbook uses. Azure Automation lists module 3.0.0 and later as a known source of job errors unless the PowerShellGet and PackageManagement modules are also imported explicitly, so add those two at the same time. Azure Automation supports PowerShell 7.6, 7.4 and 5.1 runbooks, and the module has its own minimums: 3.10.0 and later need PowerShell 7.6, versions 3.5.0 to 3.9.2 need 7.4, and every version runs on 5.1. Pick the runtime and module pair together.

If the job only ever runs inside Azure, consider a managed identity instead of a certificate. The setup grants the same Exchange.ManageAsApp app role and an Entra role to the Automation account's identity, and the runbook connects with Connect-ExchangeOnline -ManagedIdentity -Organization contoso.onmicrosoft.com. If the Automation account is part of a larger Azure estate, the Azure cloud migration playbook covers landing zone design.

Verification

  1. Run Get-ConnectionInformation after connecting and confirm it returns a connection for your tenant.
  2. Run a cmdlet the role allows, such as Get-AcceptedDomain, and one it shouldn't, such as a Set- cmdlet when you assigned Global Reader. The second should be unavailable or denied.
  3. In the Microsoft Entra sign-in logs, open the Service principal sign-ins log, filter on the app and confirm successful sign-ins.
  4. Remove any old service account the script used, or at least remove its Exchange roles, once the app-only job has run successfully.

Troubleshooting

AADSTS700016 application not found in the directory. The AppId is wrong or the request went to the wrong tenant. Check the client ID and that -Organization is your primary .onmicrosoft.com domain.

AADSTS700027 client assertion failed signature validation. Entra ID couldn't validate the signed request from the app. Confirm that the thumbprint or certificate object in the script is the one whose public key is uploaded to the app, re-upload the .cer if you regenerated the certificate, and check that it hasn't expired.

Connection fails with a certificate you just created on Windows 11 or Windows Server. The certificate is probably CNG. Recreate it with -KeySpec KeyExchange (and the Automation provider name if needed) and upload the new public key.

Connect-IPPSSession shows a sign-in prompt with app-only parameters. Microsoft's documented workaround is to run $Global:IsWindows = $true before Connect-IPPSSession.

Cmdlets are missing after a successful connection. RBAC for the session comes from the app's roles, so the role doesn't include them. Add the right Entra role or role group, then disconnect and connect again.

Microsoft 365 Group membership cmdlets fail. New-UnifiedGroup, Remove-UnifiedGroup, Add-UnifiedGroupLinks and Remove-UnifiedGroupLinks don't support app-only authentication. Use Microsoft Graph for those operations.

eDiscovery cmdlets fail in Security & Compliance PowerShell. App-only authentication remains unsupported for Purview eDiscovery cmdlets such as New-ComplianceSearch and Start-ComplianceSearch. Move those automations to Microsoft Graph where APIs exist. For existing automations that can't move yet, Microsoft describes module 3.10.1 or later with the -EnableSearchOnlySession switch on Connect-IPPSSession as a way to keep them working, but the configuration stays unsupported.

The term 'Update-ModuleManifest' is not recognized when connecting from an application that hosts the Windows PowerShell SDK. Add -SkipLoadingFormatData to the connect command.

Checklist

  • Single-tenant app registered; client ID recorded.
  • Exchange.ManageAsApp application permission added from the right API, admin consent granted, unused Graph consent revoked.
  • CSP certificate created, public key uploaded, private key stored only where the job runs, expiry date in the calendar.
  • Least-privilege role assigned: Entra role, scoped role group, or both.
  • Script connects with -AppId, -Organization set to the .onmicrosoft.com domain, and -CertificateThumbprint or -Certificate.
  • Module version matches the PowerShell or Automation runtime version.
  • Service principal sign-ins monitored; legacy service account retired.

References

Questions people ask

What permission does an app need to connect to Exchange Online PowerShell?

The application permission Exchange.ManageAsApp on the Office 365 Exchange Online API, with tenant-wide admin consent. For Security & Compliance PowerShell the same permission comes from the Microsoft Exchange Online Protection API. The app also needs a supported Microsoft Entra role or membership of an Exchange role group to decide what it can run.

Why does app-only authentication fail with my new certificate?

A common cause is a Cryptography Next Generation (CNG) certificate, which modern Windows creates by default but Exchange app-only authentication doesn't support. Create the certificate with a CSP provider, for example New-SelfSignedCertificate with -KeySpec KeyExchange, and upload the new public key to the app registration.

Should I use a certificate or a managed identity in Azure Automation?

Both work. A managed identity removes certificate handling entirely and is the simpler choice when the script runs in an Azure Automation account or Azure VM. Use a certificate when the script runs outside Azure or must use the same app registration from several places.

Which Exchange cmdlets don't work with app-only authentication?

In Exchange Online PowerShell, New-UnifiedGroup, Remove-UnifiedGroup, Add-UnifiedGroupLinks and Remove-UnifiedGroupLinks aren't supported; use Microsoft Graph for those group operations. Microsoft Purview eDiscovery cmdlets in Security & Compliance PowerShell also remain unsupported with app-only authentication.

Exchange Online PowerShellEntra ID app registrationCertificatesAzure AutomationExchangeOnlineManagement
  1. Fix Connect-ExchangeOnline errors: module versions, MFA, WAM and proxies

    Get Exchange Online PowerShell connecting again: match the module to your PowerShell version, fix WAM, MFA and Conditional Access failures, and handle proxies and blocked accounts.

    Microsoft 36512 min read
  2. Calendar permissions in Exchange Online: Add-MailboxFolderPermission guide

    Share calendars, change the organization-wide Default permission and add calendar delegates in Exchange Online with Add-, Set- and Remove-MailboxFolderPermission, including localized folder names.

    Microsoft 3659 min read
  3. Convert a user mailbox to a shared mailbox and remove the license safely

    Keep a leaver's email and calendar in Exchange Online without paying for a license: secure the account, convert the mailbox, grant access, then remove the license in the right order.

    Microsoft 36511 min read