Exchange OnlineHow-to

Exchange Online PowerShell V3: connect, automate with certificates, run everyday reports

Install the ExchangeOnlineManagement module, connect interactively or with app-only certificate authentication, and run five quick reports with the fast Get-EXO cmdlets.

If your Exchange Online scripts still talk about remote PowerShell sessions and stored passwords, they are overdue for a refresh. In this post I'll walk through the current ExchangeOnlineManagement module: how to install and connect, how to set up certificate-based app-only authentication for unattended jobs, how the Get-EXO cmdlets and property sets keep bulk queries fast, and five short reports you can reuse straight away.

How this guide is organised: Prerequisites → Step-by-step → Verify → Tips & gotchasFlow diagram of the article's sections in reading order: 1. Prerequisites. 2. Step-by-step (5 steps: Install or update the module; Connect interactively; App-only authentication for unattended scripts; Use the Get-EXO cmdlets and property sets; Five everyday reports). 3. Verify. 4. Tips & gotchas. Toolbox: Get-ConnectionInformation, Connect-IPPSSession, Get-PSSession, Application permissions › Exchange.ManageAsApp, RemoteSigned.1Prerequisites2Step-by-step3Verify4Tips & gotchas1Install or update themodule2Connectinteractively3App-onlyauthentication for…4Use the Get-EXOcmdlets and prop…5Five everydayreportsTOOLBOXGet-ConnectionInformationConnect-IPPSSessionGet-PSSessionApplication permissions › Exchange.Mana…RemoteSignedHow this guide is organised: Prerequisites → Step-by-step → Verify → Tips & gotchasFlow diagram of the article's sections in reading order: 1. Prerequisites. 2. Step-by-step (5 steps: Install or update the module; Connect interactively; App-only authentication for unattended scripts; Use the Get-EXO cmdlets and property sets; Five everyday reports). 3. Verify. 4. Tips & gotchas. Toolbox: Get-ConnectionInformation, Connect-IPPSSession, Get-PSSession, Application permissions › Exchange.ManageAsApp, RemoteSigned.1Prerequisites2Step-by-step1Install or update the module2Connect interactively3App-only authentication for unattended scripts4Use the Get-EXO cmdlets and property sets5Five everyday reports3Verify4Tips & gotchasTOOLBOXGet-ConnectionInformationConnect-IPPSSessionGet-PSSessionApplication permissions › Exchange.ManageAsAppRemoteSigned
At a glance: how this guide is organised · 5 steps · 5 key settings and tools

Prerequisites#

  • PowerShell: the module is supported in PowerShell 7 on Windows, Linux and macOS, and in Windows PowerShell 5.1 on Windows. Module versions 3.10.0 and later need PowerShell 7.6 or later; versions 3.5.0 to 3.9.2 need 7.4 or later. Security & Compliance PowerShell (Connect-IPPSSession) isn't available in PowerShell 7 on macOS or Linux.
  • Execution policy set to RemoteSigned, and current (non-preview) PowerShellGet and PackageManagement modules on Windows.
  • Permissions: an Exchange role that covers what you want to read or change. For reporting, Global Reader or Exchange Recipient Administrator is usually enough.

Since October 2023 every connection is REST-based: no Basic authentication in WinRM, no remote PowerShell runspace, and the UseRPSSession switch is deprecated. The cmdlet names and parameters didn't change, but a few habits did. Invoke-Command doesn't work against the connection, Get-ConnectionInformation replaces Get-PSSession, and REST cmdlets time out after 15 minutes, which matters for very large bulk operations.

Step 1: Install or update the module#

PowerShell
# First install for the current user (no elevation needed)
Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser

# Updates: use the same scope you installed with
Update-Module -Name ExchangeOnlineManagement -Scope CurrentUser

# Which version is installed, and where?
Get-InstalledModule ExchangeOnlineManagement | Format-List Name, Version, InstalledLocation

Step 2: Connect interactively#

PowerShell
# Modern authentication, works with or without MFA
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com -ShowBanner:$false

# No browser on this machine (Server Core, SSH session)? PowerShell 7 only.
Connect-ExchangeOnline -Device

# Import only the cmdlets a script needs: faster and a smaller memory footprint
Connect-ExchangeOnline -UserPrincipalName admin@contoso.com -CommandName Get-EXOMailbox,Get-EXOMailboxStatistics,Get-Mailbox

Get-ConnectionInformation

Two newer switches are worth knowing. Since version 3.7.0 the module no longer downloads cmdlet help by default; add -LoadCmdletHelp if you want Get-Help to work for Exchange cmdlets. And if sign-in fails with Web Account Manager errors on Windows, -DisableWAM (3.7.2 and later) falls back to the browser flow.

Step 3: App-only authentication for unattended scripts#

Scheduled reports shouldn't depend on a human account or a saved password. Certificate-based authentication (CBA) lets an app registration sign in with a certificate and inherit an Exchange role.

Create a certificate#

Microsoft notes that Cryptography Next Generation (CNG) certificates aren't supported for this scenario, so create a CSP key with -KeySpec KeyExchange:

PowerShell
$cert = New-SelfSignedCertificate -DnsName "exo-automation.contoso.com" `
    -CertStoreLocation "Cert:\CurrentUser\My" -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
$cert | Export-Certificate -FilePath .\exo-automation.cer
$cert.Thumbprint

Register the app and grant the permission#

  1. In the Microsoft Entra admin center, go to App registrations › New registration, keep Accounts in this organizational directory only, and register it.
  2. Under API permissions › Add a permission › APIs my organization uses, choose Office 365 Exchange Online, then Application permissions › Exchange.ManageAsApp, and select Grant admin consent.
  3. Under Certificates & secrets › Upload certificate, upload the .cer file.
  4. Assign a role. The simplest route is Microsoft Entra roles and administrators: open Exchange Administrator (or a narrower supported role such as Exchange Recipient Administrator or Global Reader) and add the app as an assignment. For least privilege, Microsoft also supports adding the app's service principal to a custom Exchange role group with New-ServicePrincipal and Add-RoleGroupMember.

Connect with the certificate#

PowerShell
Connect-ExchangeOnline -AppId "11111111-2222-3333-4444-555555555555" `
    -CertificateThumbprint $cert.Thumbprint -Organization "contoso.onmicrosoft.com" -ShowBanner:$false

Organization must be the tenant's primary .onmicrosoft.com domain. CertificateThumbprint is Windows-only; on other platforms, or when the certificate lives in Azure Key Vault, pass an X509Certificate2 object with -Certificate. In Azure Automation, Functions or VMs you can skip certificates entirely and use -ManagedIdentity -Organization contoso.onmicrosoft.com.

Note: App-only connections can't run the Microsoft 365 Group cmdlets New-UnifiedGroup, Remove-UnifiedGroup, Add-UnifiedGroupLinks and Remove-UnifiedGroupLinks. Use Microsoft Graph for those.

Step 4: Use the Get-EXO cmdlets and property sets#

The module ships nine Get-EXO* cmdlets optimised for bulk retrieval, including Get-EXOMailbox, Get-EXORecipient, Get-EXOCasMailbox, Get-EXOMailboxStatistics, Get-EXOMailboxPermission and Get-EXOMailboxFolderStatistics. Instead of returning well over 200 properties per mailbox like Get-Mailbox, they return a Minimum set by default (identity, display name, addresses, recipient type) and let you add what you need with -PropertySets (for example Delivery, Quota, Hold, Archive) or individual -Properties. Microsoft discourages -PropertySets All because it slows the call and reduces reliability. Filter on the server with -Filter (OPATH syntax) rather than piping everything to Where-Object, and set -ResultSize Unlimited when you really mean every mailbox.

Step 5: Five everyday reports#

1. Largest mailboxes#

PowerShell
Get-EXOMailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox |
    ForEach-Object { Get-EXOMailboxStatistics -Identity $_.ExternalDirectoryObjectId } |
    Select-Object DisplayName, ItemCount,
        @{Name='SizeGB'; Expression={ [math]::Round(([int64]($_.TotalItemSize.ToString() -replace '^.*\(|\s*bytes\)$|,','')) / 1GB, 2) }} |
    Sort-Object SizeGB -Descending | Select-Object -First 25

2. Last logon and last user activity#

PowerShell
Get-EXOMailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox | ForEach-Object {
    $stats = Get-EXOMailboxStatistics -Identity $_.ExternalDirectoryObjectId -Properties LastLogonTime,LastUserActionTime
    [pscustomobject]@{ User = $_.UserPrincipalName; LastLogon = $stats.LastLogonTime; LastUserAction = $stats.LastUserActionTime }
} | Sort-Object LastUserAction | Export-Csv .\mailbox-activity.csv -NoTypeInformation

LastUserActionTime is the better signal: background processes can touch LastLogonTime on mailboxes nobody uses.

3. Mailboxes with forwarding configured#

PowerShell
Get-EXOMailbox -ResultSize Unlimited -PropertySets Delivery `
    -Filter 'ForwardingSmtpAddress -ne $null -or ForwardingAddress -ne $null' |
    Select-Object DisplayName, PrimarySmtpAddress, ForwardingSmtpAddress, ForwardingAddress, DeliverToMailboxAndForward

This covers admin-configured forwarding only. User-created Inbox rules need Get-InboxRule per mailbox, or the Auto forwarded messages report in the Exchange admin center.

4. Shared mailboxes and who has Full Access#

PowerShell
Get-EXOMailbox -ResultSize Unlimited -RecipientTypeDetails SharedMailbox | ForEach-Object {
    $shared = $_
    Get-EXOMailboxPermission -Identity $shared.ExternalDirectoryObjectId |
        Where-Object { $_.AccessRights -contains 'FullAccess' -and -not $_.IsInherited -and $_.User -ne 'NT AUTHORITY\SELF' } |
        Select-Object @{Name='SharedMailbox'; Expression={ $shared.PrimarySmtpAddress }}, User
}

5. Mailboxes with no user activity for 90 days, and compliance inactive mailboxes#

PowerShell
# Dormant but still active mailboxes (reuse the CSV from report 2)
Import-Csv .\mailbox-activity.csv |
    Where-Object { -not $_.LastUserAction -or [datetime]$_.LastUserAction -lt (Get-Date).AddDays(-90) }

# "Inactive mailboxes" in the compliance sense: soft-deleted but preserved by a hold
Get-EXOMailbox -InactiveMailboxOnly -ResultSize Unlimited -PropertySets SoftDelete,Hold |
    Select-Object DisplayName, PrimarySmtpAddress, WhenSoftDeleted, LitigationHoldEnabled, InPlaceHolds

Verify#

After connecting, Get-ConnectionInformation should list one connection with a valid token; for app-only, the AppId and Organization you passed appear in that object. Run each report with -ResultSize 10 first. If a cmdlet returns "isn't recognized", the role assigned to your account or app doesn't include it; if the result set is suspiciously small, you forgot -ResultSize Unlimited (the default is 1,000).

Tips & gotchas#

  • Throttling: the fastest way to be throttled is Get-Mailbox -ResultSize Unlimited | Where-Object. Use the Get-EXO* cmdlets, request only the properties you need, and filter server-side. If you see throttling or delay warnings, run fewer sessions in parallel and let the built-in retries work.
  • Memory leak: Microsoft warns that repeatedly connecting and disconnecting inside one script can leak memory. Connect once, limit the imported cmdlets with -CommandName, disconnect at the end.
  • 15-minute timeout: split very large bulk changes (for example updating thousands of group members) into smaller batches.
  • Multiple connections: the Get-EXO* cmdlets always bind to the most recent Exchange Online connection in the window.
  • Clean up: finish scripts with Disconnect-ExchangeOnline -Confirm:$false. It removes the temporary module files and the cached access token; with several connections open, -ConnectionId disconnects just one.

References#

Written and checked against current Microsoft Learn documentation. Test changes with a pilot group before rolling them out to everyone, and if an admin center path has moved since, search for the setting name instead.

Spotted a mistake, or did this fix work differently for you? Email me or message me on LinkedIn — corrections are credited in the article.

OE
Written by

Omer Eltayeb

Independent Microsoft Intune consultant in Cairo, Egypt, former Microsoft Cloud Solutions Architect, Microsoft Certified Trainer and Microsoft Innovative Educator Expert (2024–26) and Microsoft Elevate Educator Expert (2026–27). I share practical, step-by-step guides, study plans, scripts and toolkits for Microsoft Intune, Microsoft Entra ID, Microsoft Defender and Exchange Online with the community.

Microsoft Certified Trainer (MCT) 2026Microsoft Innovative Educator Expert 2025–2026Microsoft Elevate Educator Expert 2026–2027ISC2 Certified Information Systems Security Professional (CISSP)Microsoft 365 Certified: Enterprise Administrator Expert (MS-102)