Short answer: install the Exchange Online PowerShell module with Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser, then run Connect-ExchangeOnline -UserPrincipalName with your admin account and sign in with MFA. For scripts that run unattended, use app-only certificate authentication, never a stored password. Basic authentication remote PowerShell no longer works.
Last reviewed 8 October 2026 by Elliot Munro, CISO.
This guide replaces our original “quick connection to Exchange Online” article. The old Connect-EXOnline profile function used Basic authentication and remote PowerShell, both of which Microsoft has switched off for Exchange Online. The new version uses the ExchangeOnlineManagement module (EXO V3), signs in with MFA and never stores a password.
It’s a familiar story for IT managers: an Exchange script that has run every night for years suddenly won’t even connect. Usually the script starts with a saved username and password, a New-PSSession to outlook.office365.com and an Import-PSSession. That connection method is gone for good, and no setting in the tenant will bring it back. The fix is to connect with the current Exchange Online PowerShell module, using MFA when a person is at the keyboard and a certificate or managed identity when a script runs on its own. This guide walks through both, plus a profile shortcut that makes the everyday connection a single command.
Not technical? Let us help.
The steps below are written for IT administrators. If you would rather have someone else set up secure Exchange Online automation for your business, get in touch with our team.
Who this is for
- Microsoft 365 and Exchange Online administrators who manage mailboxes, mail flow or security settings from PowerShell.
- Anyone following an older GCIT knowledge base article that starts with “connect to Exchange Online via PowerShell” and links to our old quick connection guide.
- Microsoft partners and MSPs who connect to customer tenants through granular delegated admin privileges (GDAP).
- Anyone running scheduled or unattended Exchange Online scripts that still keep a username and password on disk.
What changed
Our original quick connection article added a Connect-EXOnline function to your PowerShell profile. It prompted for a username and password with Get-Credential and opened a remote PowerShell session with -Authentication Basic. That approach no longer works, for three reasons:
- Basic authentication is off. Microsoft removed Basic authentication in Exchange Online for Remote PowerShell (along with ActiveSync, POP, IMAP, EWS and Outlook), and no one, including Microsoft support, can turn it back on. The cut-off date was 1 October 2022 for all cloud environments other than Office 365 operated by 21Vianet. Source: Deprecation of Basic authentication in Exchange Online.
- Remote PowerShell is off. As of October 2023, REST API connections replaced remote PowerShell connections in both Exchange Online PowerShell and Security & Compliance PowerShell. From 1 October 2023 remote PowerShell was disabled for all worldwide tenants and can’t be re-enabled. Sources: About the Exchange Online PowerShell module and the Exchange Team announcement.
- The connection cmdlets changed.
Connect-ExchangeOnlinereplacesNew-PSSessionand the V1 module’sConnect-EXOPSSession.Connect-IPPSSessionin the same module connects to Security & Compliance PowerShell. The-UseRPSSessionswitch is deprecated, so remove it from any script that still has it.
If your saved scripts contain anything like the following, they need updating:
# RETIRED: Basic authentication remote PowerShell. This no longer works in Exchange Online. $UserCredential = Get-Credential $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection Import-PSSession $Session
Two related changes catch people out in the same scripts. If a script also calls Connect-MsolService, the MSOnline module was retired on 30 May 2025 and that part needs moving to Microsoft Graph PowerShell. If you are a partner who used delegated admin privileges (DAP) to reach customer tenants, Microsoft is replacing DAP with GDAP, and Connect-ExchangeOnline -DelegatedOrganization works with a GDAP relationship.
Prerequisites
- PowerShell: Windows PowerShell 5.1 or PowerShell 7. Module versions 3.10.0 and later need PowerShell 7.6 or later (Windows PowerShell 5.1 isn’t affected). In Windows PowerShell 5.1 the module needs .NET Framework 4.7.2, which Windows 11 already includes.
- PowerShellGet and PackageManagement: REST connections need both modules. Keep them current, and avoid preview versions of either.
- Execution policy: PowerShell must be allowed to run scripts. Microsoft’s documented setting is
RemoteSigned. Set it for your own account only (-Scope CurrentUser), which doesn’t need an elevated window and doesn’t change the setting for other users of the computer. - An admin account with MFA: Use a dedicated admin account, not your everyday mailbox account. Give it the smallest Exchange role that does the job (for example, Exchange Recipient Administrator for mailbox work) and, where your licensing allows, make the role eligible through Microsoft Entra Privileged Identity Management rather than permanently assigned.
- Exchange Online PowerShell access: All accounts are allowed to use Exchange Online PowerShell by default, but an admin can switch it off per user. If your account was blocked, see the troubleshooting section.
- For unattended scripts: an administrator who holds, ideally just in time through Privileged Identity Management, Application Administrator (to register the app and grant admin consent for Exchange.ManageAsApp, which isn’t a Microsoft Graph permission) and Privileged Role Administrator (to assign a Microsoft Entra role to the app). Global Administrator isn’t needed. Source: Grant tenant-wide admin consent to an application. If you use the custom role group option in step 6.5, you also need an Exchange admin who can create role groups (Exchange Administrator, which maps to the Organization Management role group) and the
Microsoft.Graph.Applicationsmodule from our Microsoft Graph PowerShell guide. Finally, you need a Windows server or Azure resource to run the script on.
Mac and Linux: the module runs in PowerShell 7 on macOS and Linux, but Connect-IPPSSession (Security & Compliance PowerShell) isn’t available there, and the -CertificateThumbprint parameter only works on Windows.
Step 1: Install or update the Exchange Online PowerShell module
- Open PowerShell (a normal, non-elevated window is fine for a current user install).
- Check whether the module is already installed and which version you have.
- Install it for your account, or update it if it is already there. Use the same scope for updates that you used for the original install.
# Is the module installed, and where? Get-InstalledModule ExchangeOnlineManagement | Format-List Name, Version, InstalledLocation # First install, for the current user only (no elevation needed) Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser # Later updates (use the same scope as the original install) Update-Module -Name ExchangeOnlineManagement -Scope CurrentUser
If PowerShell reports that running scripts is disabled, set the execution policy for your own account. This doesn’t need an elevated window:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
At the time of writing (October 2026), the current release of the module is 3.10.1. Microsoft lists every release in the module release notes.
Step 2: Connect to Exchange Online PowerShell with MFA
- Run
Connect-ExchangeOnlinewith your admin account’s user principal name. - Complete the sign-in prompt, including your MFA prompt. From version 3.7.0 the module uses Web Account Manager (WAM) by default. In PowerShell 7 the prompt opens in your default browser.
- Run a harmless command such as
Get-AcceptedDomainto confirm the connection works.
# Sign in with modern authentication and MFA Connect-ExchangeOnline -UserPrincipalName admin@contoso.onmicrosoft.com # Quick test Get-AcceptedDomain # Show the active connection(s), including when the token expires Get-ConnectionInformation | Format-List Name, UserPrincipalName, TenantID, TokenStatus, TokenExpiryTimeUTC
A few useful variations:
- Hide the banner: add
-ShowBanner:$false. - No browser on the machine (PowerShell 7): run
Connect-ExchangeOnline -Deviceand complete the sign-in at https://microsoft.com/devicelogin on another device. Microsoft recommends blocking device code flow with Conditional Access wherever possible, because attackers use it in phishing. If your tenant blocks it, sign in from an admin workstation that has a browser, or use certificate authentication on servers (step 6). Source: Block authentication flows with Conditional Access policy. - Customer tenants (partners with GDAP): add the customer’s primary .onmicrosoft.com domain, for example
Connect-ExchangeOnline -UserPrincipalName admin@contoso.onmicrosoft.com -DelegatedOrganization customer.onmicrosoft.com. Every command in that session then runs against the customer’s organisation.
Don’t use -Credential or -InlineCredential for admin work. Microsoft documents both as unsuitable for accounts that use MFA, and every admin account should have MFA.
Step 3: Connect to Security & Compliance PowerShell
Retention policies, sensitivity labels, DLP and other Microsoft Purview settings live in Security & Compliance PowerShell. It comes with the same module, but you connect with Connect-IPPSSession:
# Your own tenant Connect-IPPSSession -UserPrincipalName admin@contoso.onmicrosoft.com # Quick test Get-RetentionCompliancePolicy # A customer tenant through GDAP (the authorisation endpoint is required with DelegatedOrganization) Connect-IPPSSession -UserPrincipalName admin@contoso.onmicrosoft.com -DelegatedOrganization customer.onmicrosoft.com -AzureADAuthorizationEndpointUri https://login.microsoftonline.com/customer.onmicrosoft.com
If you plan to run eDiscovery search cmdlets such as New-ComplianceSearch, Microsoft requires module version 3.9.0 or later and the -EnableSearchOnlySession switch on Connect-IPPSSession.
Step 4: Disconnect when you’re finished
Always disconnect. If you close the window without disconnecting, you can use up the sessions available to you and have to wait for them to expire. Disconnect-ExchangeOnline closes both Exchange Online and Security & Compliance connections in the current window:
# Disconnect without a confirmation prompt Disconnect-ExchangeOnline -Confirm:$false
Step 5: Add a quick connect function to your PowerShell profile
This is the modern replacement for the old Connect-EXOnline function. It keeps the same name, so muscle memory still works, but it signs in with MFA and stores nothing except your admin username. It also checks for an existing connection first, so running it twice doesn’t open a second session.
- Create your profile file if it doesn’t exist yet, then open it in Notepad. The “current user, all hosts” profile also loads in Visual Studio Code. Windows PowerShell 5.1 and PowerShell 7 keep separate profile folders, so repeat this in each one you use.
if (!(Test-Path -Path $PROFILE.CurrentUserAllHosts)) {
New-Item -ItemType File -Path $PROFILE.CurrentUserAllHosts -Force
}
notepad $PROFILE.CurrentUserAllHosts
- Paste the following into the profile, change
admin@contoso.onmicrosoft.comto your own admin account, and save the file. - Close and reopen PowerShell so the profile loads.
# ---- Exchange Online quick connect (GCIT) ----
# Modern authentication with MFA. No passwords or tokens are stored in this profile.
$ExoAdminUpn = 'admin@contoso.onmicrosoft.com'
function Connect-EXOnline {
[CmdletBinding()]
param(
[string]$UserPrincipalName = $ExoAdminUpn,
[string]$DelegatedOrganization,
[switch]$Compliance
)
if (-not (Get-Module -ListAvailable -Name ExchangeOnlineManagement)) {
Write-Warning 'ExchangeOnlineManagement is not installed. Run: Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser'
return
}
# Look for existing connections for this account and organisation
$active = @(Get-ConnectionInformation -ErrorAction SilentlyContinue | Where-Object {
$_.State -eq 'Connected' -and
$_.UserPrincipalName -eq $UserPrincipalName -and
"$($_.DelegatedOrganization)" -eq "$DelegatedOrganization"
})
# Exchange Online PowerShell
if (-not ($active | Where-Object { -not $_.IsEopSession })) {
$exoParams = @{
UserPrincipalName = $UserPrincipalName
ShowBanner = $false
}
if ($DelegatedOrganization) {
$exoParams.DelegatedOrganization = $DelegatedOrganization
}
Connect-ExchangeOnline @exoParams
}
else {
Write-Host "Exchange Online is already connected for $UserPrincipalName." -ForegroundColor Yellow
}
# Security and Compliance PowerShell (only when -Compliance is used)
if ($Compliance -and -not ($active | Where-Object { $_.IsEopSession })) {
$sccParams = @{
UserPrincipalName = $UserPrincipalName
ShowBanner = $false
}
if ($DelegatedOrganization) {
$sccParams.DelegatedOrganization = $DelegatedOrganization
$sccParams.AzureADAuthorizationEndpointUri = "https://login.microsoftonline.com/$DelegatedOrganization"
}
Connect-IPPSSession @sccParams
}
}
function Disconnect-EXOnline {
Disconnect-ExchangeOnline -Confirm:$false
}
Now the everyday commands are short:
# Exchange Online only Connect-EXOnline # Exchange Online plus Security and Compliance PowerShell Connect-EXOnline -Compliance # A customer tenant through GDAP Connect-EXOnline -DelegatedOrganization customer.onmicrosoft.com # Close every Exchange connection in this window Disconnect-EXOnline
Step 6: Set up app-only certificate authentication for unattended scripts
A scheduled script has no one to answer an MFA prompt, which is why the old approach stored a password. Don’t do that. Microsoft’s guidance for unattended work is a service principal that signs in with a managed identity or a certificate instead of a user password. The Exchange Online module supports both, so the script never needs a user account, a stored password or a client secret.
- If the script runs in Azure (an Azure Automation runbook, Azure virtual machine, virtual machine scale set or Azure Functions), use a managed identity and connect with
Connect-ExchangeOnline -ManagedIdentity -Organization contoso.onmicrosoft.com. There is no certificate to look after. Follow Microsoft’s managed identity setup guide. Note thatConnect-IPPSSessionhas no managed identity option, so Security & Compliance scripts still need a certificate. - If the script runs on your own Windows server, use the certificate method below.
6.1 Register the application
- In the Azure portal or the Microsoft Entra admin center, open App registrations and select New registration.
- Give it a descriptive name, for example EXO Reporting - CBA.
- Leave Supported account types on the single-tenant option (shown as Single tenant only - <your tenant> in the current Microsoft Entra admin center), then select Register.
- On the app’s Overview page, copy the Application (client) ID. You need it later as the
AppId.
6.2 Grant the Exchange.ManageAsApp permission
- On the app, open API permissions and select Add a permission.
- Select the APIs my organization uses tab, then search for and select Office 365 Exchange Online. (For Security & Compliance PowerShell, select Microsoft Exchange Online Protection instead, or add both if the script connects to both.)
- Choose Application permissions, expand Exchange, tick Exchange.ManageAsApp and select Add permissions.
- Select Grant admin consent for your organisation and confirm. The status should change to Granted.
- Microsoft also recommends revoking admin consent on the default Microsoft Graph, User.Read entry, because this app doesn’t need it.
6.3 Create the certificate on the server that will run the script
Sign in to the automation server as the account that will run the scheduled task (a dedicated, non-admin service account is ideal). Give that account only the Log on as a batch job right, deny it interactive and Remote Desktop sign-in once setup is finished, and give it a long random password stored in your password vault. If the script can run in Azure instead, use a managed identity and skip the server entirely. Creating the certificate while signed in as the service account keeps the private key in that account’s personal store (Cert:\CurrentUser\My), which is one of the stores -CertificateThumbprint checks (CurrentUser or LocalMachine), and only that account can use the key. Exchange app-only authentication doesn’t support CNG certificates, which modern Windows creates by default, so the KeySpec setting below matters.
# Run as the account that will run the scheduled script
$certParams = @{
Subject = 'CN=EXO-Reporting-CBA'
CertStoreLocation = 'Cert:\CurrentUser\My'
KeySpec = 'KeyExchange'
NotAfter = (Get-Date).AddYears(1)
}
$cert = New-SelfSignedCertificate @certParams
# Export the PUBLIC key only (.cer). This file is safe to upload and contains no private key.
Export-Certificate -Cert $cert -FilePath "$HOME\EXO-Reporting-CBA.cer"
# Note the thumbprint for the script
$cert.Thumbprint
Don’t export the private key (.pfx) unless you have a specific reason, such as a documented backup. If you must, store it in Azure Key Vault, never on a file share or in a script folder, and protect it like an admin password, because anyone with the certificate and private key can act as the app.
6.4 Upload the public certificate to the app
- Back on the app registration, open Certificates & secrets.
- Select Upload certificate, browse to the .cer file you exported and select Add.
- Don’t create a client secret. Exchange app-only authentication uses the certificate.
6.5 Give the app the least privilege it needs
The app has no Exchange rights until you assign a role. Pick the smallest supported Microsoft Entra role for the job:
| What the script does | Suggested Microsoft Entra role |
|---|---|
| Read-only reporting (like the forwarding report below) | A custom Exchange Online role group containing only the View-Only Recipients role (steps below). Global Reader also works, but it can read everything a Global Administrator can, so use it only if a role group isn’t practical. |
| Creates or changes mailboxes, groups and contacts | Exchange Recipient Administrator |
| Changes mail flow, anti-spam or other organisation settings | Exchange Administrator |
| Security & Compliance PowerShell tasks | Compliance Administrator (or Security Reader for read-only) |
To assign one of the Microsoft Entra roles in the table:
- Open Microsoft Entra roles and administrators, then select the role by clicking its name (not the check box).
- Select Add assignments, find your app (for example, EXO Reporting - CBA) and select Add.
Never give an automation app Global Administrator. For read-only reports, prefer a custom role group. Microsoft supports adding the app’s service principal to a custom Exchange Online role group (see Create role groups in Exchange Online). If you use this option, skip the Microsoft Entra role assignment above. Run the following as an admin, replacing the app ID with the one you copied in step 6.1. It needs the Microsoft.Graph.Applications module, and the Graph sign-in uses a read-only scope that is cleared when the block finishes:
$AppId = '00000000-0000-0000-0000-000000000000' # Application (client) ID from step 6.1
# 1. Look up the app's service principal in Microsoft Graph PowerShell (read-only)
Connect-MgGraph -Scopes 'Application.Read.All' -ContextScope Process -NoWelcome
try {
$AzureADApp = Get-MgServicePrincipal -Filter "appId eq '$AppId'"
}
finally {
Disconnect-MgGraph | Out-Null
}
if (-not $AzureADApp) {
throw "No service principal found for app ID $AppId. Check the ID and that the app was registered in this tenant."
}
# 2. Create a read-only role group, register the service principal in Exchange Online and add it to the group
Connect-ExchangeOnline -UserPrincipalName admin@contoso.onmicrosoft.com -ShowBanner:$false
try {
New-RoleGroup -Name 'EXO Reporting - View-Only Recipients' -Roles 'View-Only Recipients' -Description 'Read-only recipient access for the EXO Reporting - CBA app'
New-ServicePrincipal -AppId $AzureADApp.AppId -ObjectId $AzureADApp.Id -DisplayName 'SP for EXO Reporting - CBA'
$SP = Get-ServicePrincipal -Identity 'SP for EXO Reporting - CBA'
Add-RoleGroupMember -Identity 'EXO Reporting - View-Only Recipients' -Member $SP.Identity
}
finally {
Disconnect-ExchangeOnline -Confirm:$false
}
The lookup filters on the app ID rather than the display name, because two apps can share a name but never an app ID. Run the report script (step 6.6) once by hand after this. If it returns an access denied error, assign Global Reader instead (as above) and remove the role group.
6.6 The unattended script
This example connects with the certificate, finds every mailbox that forwards mail (a common sign of a compromised account), saves the results to CSV and always disconnects, even if something fails. The app ID, tenant and thumbprint aren’t secrets, so passing them as parameters is fine. The private key never leaves the certificate store.
# .SYNOPSIS
# Exports Exchange Online mailboxes that forward mail, using app-only certificate authentication.
# .DESCRIPTION
# Runs unattended. No passwords or client secrets are stored.
# The certificate's private key stays in the Windows certificate store of the account running this script.
# .EXAMPLE
# .\Export-ExoForwardingReport.ps1 -AppId '00000000-0000-0000-0000-000000000000' -Organization 'contoso.onmicrosoft.com' -CertificateThumbprint 'YOURCERTIFICATETHUMBPRINT'
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$AppId,
# Must be the tenant's primary .onmicrosoft.com domain
[Parameter(Mandatory = $true)]
[string]$Organization,
[Parameter(Mandatory = $true)]
[string]$CertificateThumbprint,
[string]$OutputFolder = 'C:\Reports\Exchange'
)
$ErrorActionPreference = 'Stop'
Import-Module ExchangeOnlineManagement
if (-not (Test-Path -Path $OutputFolder)) {
New-Item -ItemType Directory -Path $OutputFolder | Out-Null
}
$fileName = 'MailboxForwarding-{0}-{1}.csv' -f $Organization, (Get-Date -Format 'yyyyMMdd-HHmm')
$reportPath = Join-Path -Path $OutputFolder -ChildPath $fileName
try {
Connect-ExchangeOnline -AppId $AppId -CertificateThumbprint $CertificateThumbprint -Organization $Organization -ShowBanner:$false
# Minimum + Delivery property sets return the identity and forwarding properties we need
$forwarding = @(Get-EXOMailbox -ResultSize Unlimited -PropertySets Minimum, Delivery |
Where-Object { $_.ForwardingSmtpAddress -or $_.ForwardingAddress } |
Select-Object DisplayName, UserPrincipalName, RecipientTypeDetails, ForwardingSmtpAddress, ForwardingAddress, DeliverToMailboxAndForward)
if ($forwarding.Count -gt 0) {
$forwarding | Export-Csv -Path $reportPath -NoTypeInformation -Encoding UTF8
Write-Output ('{0} mailbox(es) with forwarding found. Report saved to {1}' -f $forwarding.Count, $reportPath)
}
else {
Write-Output 'No mailboxes with forwarding found.'
}
}
finally {
# Always release the session, even if the report failed
Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
}
How to run it
- Create a folder for the script that only administrators can change, for example
C:\Scripts, and a report folder that only the service account and administrators can write to. A new folder under C:\ lets every signed-in user change its contents, so lock both down before you save anything there. Run this from an elevated PowerShell window, replacingCONTOSO\svc-exoreportwith your service account:# SYSTEM and Administrators get full control. The service account can only read and run the script. $scriptFolder = 'C:\Scripts' New-Item -ItemType Directory -Path $scriptFolder -Force | Out-Null icacls $scriptFolder /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' 'CONTOSO\svc-exoreport:(OI)(CI)RX' # The service account can write reports. Other users can't read them. $reportFolder = 'C:\Reports\Exchange' New-Item -ItemType Directory -Path $reportFolder -Force | Out-Null icacls $reportFolder /inheritance:r /grant:r '*S-1-5-18:(OI)(CI)F' '*S-1-5-32-544:(OI)(CI)F' 'CONTOSO\svc-exoreport:(OI)(CI)M'
- Save the script as
Export-ExoForwardingReport.ps1inC:\Scripts. - Signed in as the service account, make sure it can load the module and run scripts. A module you installed with
-Scope CurrentUserunder your own account isn’t visible to the service account, so either install it again while signed in as the service account (Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser) or install it once for all users from an elevated window with-Scope AllUsers. If scripts are blocked, also runSet-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUseras the service account. - Still signed in as the service account, run the script once by hand to confirm it works:
.\Export-ExoForwardingReport.ps1 -AppId 'your-app-id' -Organization 'contoso.onmicrosoft.com' -CertificateThumbprint 'your-thumbprint' - In Task Scheduler, create a task that runs as the same service account, with the program set to
powershell.exe(Windows PowerShell 5.1) orpwsh.exe(PowerShell 7) and the arguments-NoProfile -File "C:\Scripts\Export-ExoForwardingReport.ps1"followed by the three parameters, for example-NoProfile -File "C:\Scripts\Export-ExoForwardingReport.ps1" -AppId 'your-app-id' -Organization 'contoso.onmicrosoft.com' -CertificateThumbprint 'your-thumbprint'. Select Run whether user is logged on or not so it runs without anyone signed in. - Check the output folder after the first scheduled run, and review the report. Any mailbox forwarding to an address you don’t recognise deserves a closer look.
Partners: to run unattended scripts across many customer tenants, Microsoft supports a multitenant version of the same app. Each customer’s admin grants consent to Exchange.ManageAsApp, and you assign the role in each customer tenant. The steps are in the “Exchange Online delegated scenarios” sections of Microsoft’s app-only authentication guide. One certificate then works in every customer that consented, so keep it in Azure Key Vault, give each customer a read-only custom role group rather than Exchange Administrator, and keep any app that changes settings separate. Our GDAP secure application model guide covers this design.
Troubleshooting
- “Files cannot be loaded because running scripts is disabled on this system”: the execution policy is blocking the module. Run
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser. The same policy also stops your profile from loading. - “The term ‘Update-ModuleManifest’ is not recognized” or “Cannot find a cmdlet Update-Manifest”: PowerShellGet or PackageManagement is missing or out of date. Update both as described in Installing PowerShellGet, then close and reopen PowerShell. For app-only connections from the Windows PowerShell SDK, Microsoft also suggests the
-SkipLoadingFormatDataswitch. - Sign-in window errors in Windows: from version 3.7.2 you can add
-DisableWAMtoConnect-ExchangeOnlineto bypass Web Account Manager if you get WAM-related connection errors. - “Could not load file or assembly ‘System.IdentityModel.Tokens.Jwt…'”: another module loaded into the same window conflicts with the Exchange module. Open a new PowerShell window and connect to Exchange Online before importing other modules.
- The module won’t load in PowerShell 7: module 3.10.0 and later need PowerShell 7.6 or later. Update PowerShell 7, or use Windows PowerShell 5.1.
- Connect or disconnect fails for one account only: Microsoft notes that both fail if the account’s profile path contains special PowerShell characters such as
$. Use an account without them. - Connected, but a cmdlet is missing or access is denied: role-based access control decides which cmdlets you see. Check the account’s (or app’s) role. If PowerShell access itself was switched off, an Exchange admin can check it with
Get-User -Identity admin@contoso.onmicrosoft.com | Format-List EXOModuleEnabledand re-enable it withSet-User -Identity admin@contoso.onmicrosoft.com -EXOModuleEnabled $true. - Your profile function isn’t there: check that you edited the profile for the PowerShell version you’re using (5.1 and 7 are separate). Microsoft also warns that redirecting the Documents folder to OneDrive or a network share can stop profiles and modules loading.
- App-only connection fails: confirm that admin consent shows Granted for Exchange.ManageAsApp, that
-Organizationis the tenant’s primary .onmicrosoft.com domain, that the certificate, with its private key, is inCert:\CurrentUser\Myfor the account running the script (or inCert:\LocalMachine\Mywith read access to the private key for that account), that the certificate was created with-KeySpec KeyExchange(CNG certificates aren’t supported), and that it hasn’t expired. - Large bulk changes time out: REST-based cmdlets have a 15-minute timeout. Split very large operations into smaller batches.
- Old scripts still fail after updating the connection: remove
-UseRPSSession,New-PSSession,Import-PSSessionandConnect-EXOPSSessionentirely. They belong to the retired connection method.
Important notes
- No stored passwords. Microsoft itself notes that storing user credentials locally isn’t good security practice. Interactive admins sign in with MFA. Scripts use a certificate or managed identity.
- Certificate files with passwords aren’t a shortcut.
Connect-ExchangeOnlinecan read a .pfx file with-CertificateFilePath, but Microsoft points out that storing the certificate password in a script defeats the purpose of a secure connection method. Prefer the thumbprint method on Windows or a managed identity in Azure. - Plan for certificate expiry. A self-signed certificate created with the command above expires after one year. Diarise the renewal, upload the new public certificate to the app before the old one expires, update the thumbprint in the scheduled task, then delete the old certificate from the app.
- App-only limits. App-only authentication can’t run
New-UnifiedGroup,Remove-UnifiedGroup,Add-UnifiedGroupLinksorRemove-UnifiedGroupLinks(use Microsoft Graph for Microsoft 365 Groups), and it remains unsupported for Microsoft Purview eDiscovery cmdlets. - Least privilege, every time. Avoid Global Administrator for both people and apps. For people, use eligible role assignments in Privileged Identity Management where your licensing allows, so admin rights exist only while they’re in use.
- One app per purpose. A separate app registration for each automation (reporting, onboarding, and so on) keeps permissions narrow and makes it easy to switch one off without breaking the others.
Related resources
Microsoft Learn
- About the Exchange Online PowerShell module
- Connect to Exchange Online PowerShell
- Connect to Security & Compliance PowerShell
- App-only authentication in Exchange Online PowerShell and Security & Compliance PowerShell
- Use Azure managed identities to connect to Exchange Online PowerShell
- Connect-ExchangeOnline cmdlet reference
- Deprecation of Basic authentication in Exchange Online
- Enable or disable access to Exchange Online PowerShell
- Granular delegated admin privileges (GDAP) FAQ
- What is Microsoft Entra Privileged Identity Management?
GCIT knowledge base
- How to detect a breach in Microsoft 365
- Export customers’ Microsoft Secure Scores to CSV and HTML reports
- Exchange Admin Centre not loading for a new Microsoft 365 tenant
- Run Exchange Online PowerShell across customer tenants with GDAP (for Microsoft partners)
- GCIT GDAP partner toolkit on GitHub (open-source PowerShell module with GDAP rewrites of our older Exchange Online scripts)
Need a hand with Exchange Online automation?
GCIT helps Australian businesses replace fragile, password-based scripts with secure, least-privilege automation across Microsoft 365. If you’d like us to review your Exchange Online scripts or set up certificate-based automation for you, get in touch with our team.