Microsoft 365

How to: Connect to Microsoft 365 Exchange Online with PowerShell

April 15, 2025
17 min read
#Administrative Access#API Permissions#Azure#Certificate-Based Authentication#Cloud Security#Exchange Online PowerShell#Microsoft 365#Microsoft Exchange Online#Microsoft EXO#Microsoft Graph PowerShell SDK#Microsoft Graph PowerShell SDK#Microsoft Teams PowerShell#Multi-Factor Authentication#PnP PowerShell#PowerShell#Script Automation#Security#Security Compliance#SharePoint Online#Unattended Scripts

Microsoft 365 Exchange Online PowerShell Connection Guide

Table of Contents

Part 1: Exchange Online PowerShell

Part 2: Microsoft Entra ID (Directory Services) ⚠️ CRITICAL

Part 3: Additional Services


Quick Start

Jump to Your Scenario:


Part 1: Exchange Online PowerShell

Summary

This guide covers all modern methods for connecting to Microsoft Exchange Online using the Exchange Online PowerShell V3 module. All legacy authentication methods, including Basic Authentication and Remote PowerShell (RPS), have been deprecated and are no longer supported.

Prerequisites

Before connecting to Exchange Online PowerShell, ensure the following requirements are met:

System Requirements

  • PowerShell Version: Windows PowerShell 5.1 or PowerShell 7.4+ (recommended)
  • Operating Systems: Windows, macOS, or Linux
  • Network: TCP port 443 open to Microsoft 365 endpoints
  • Modules: PowerShellGet and PackageManagement modules (required for REST API connections)

Administrative Requirements

  • Exchange Online administrator credentials or appropriate RBAC role
  • Account must be enabled for PowerShell access
  • For MFA scenarios: Multi-factor authentication configured on the account
  • For automation scenarios: Azure AD app registration with appropriate permissions

Execution Policy

Set your PowerShell execution policy to allow script execution:

Set-ExecutionPolicy RemoteSigned -Scope CurrentUser

Installing the Exchange Online PowerShell Module

Step 1: Check for Existing Module

First, verify if the module is already installed:

Get-Module -ListAvailable -Name ExchangeOnlineManagement

Step 2: Install or Update the Module

To install the latest version of the Exchange Online Management module:

Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber

If the module is already installed, update it to the latest version:

Update-Module -Name ExchangeOnlineManagement

To install for the current user only (no administrator rights required):

Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser -Force

Step 3: Verify Installation

Confirm the module is installed correctly:

Get-InstalledModule -Name ExchangeOnlineManagement

Expected Output:

Version    Name                                Repository           Description
-------    ----                                ----------           -----------
3.9.2      ExchangeOnlineManagement            PSGallery            Exchange Online PowerShell V3 Module

Step 4: Import the Module (Optional)

While the module loads automatically when you run connection commands, you can manually import it:

Import-Module ExchangeOnlineManagement

Connection Methods

Method 1: Interactive Authentication (with MFA Support)

This is the recommended method for manual administrative tasks. It supports multi-factor authentication and provides an interactive sign-in experience.

Basic Interactive Connection

Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com

What happens:

  1. A browser window or authentication prompt appears
  2. Enter your password
  3. Complete MFA verification if enabled (Microsoft Authenticator app, SMS code, etc.)
  4. Upon success, you're connected to Exchange Online PowerShell

Interactive Connection Without Showing Banner

To suppress the connection banner message:

Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com -ShowBanner:$false

Connection with Cmdlet Help Loading

To enable Get-Help for Exchange Online cmdlets (version 3.7.0 or later):

Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com -LoadCmdletHelp

💡 Note: Starting with version 3.7.0, cmdlet help is not loaded by default to improve connection speed. Use -LoadCmdletHelp when needed.

Complete Example Script

# Import the module
Import-Module ExchangeOnlineManagement

# Connect to Exchange Online
Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com -ShowBanner:$false

# Verify connection
Get-ConnectionInformation

# Run Exchange Online commands
Get-Mailbox -ResultSize 10

# Disconnect when finished
Disconnect-ExchangeOnline -Confirm:$false

Method 2: Certificate-Based Authentication (for Automation)

Certificate-based authentication enables unattended scripts and automation scenarios without storing user credentials. This is the recommended approach for automated tasks, scheduled scripts, and CI/CD pipelines.

Prerequisites for Certificate-Based Authentication

  1. Microsoft Entra ID (Azure AD) app registration
  2. Self-signed or CA-issued certificate
  3. Exchange Administrator role assigned to the app
  4. API permissions configured

Step 1: Create a Self-Signed Certificate

Generate a self-signed certificate using PowerShell:

# Define certificate parameters
$CertificateName = "ExchangeOnlineAutomation"
$CertificateSubject = "CN=$CertificateName"
$ExpirationYears = 2

# Create the certificate
$Certificate = New-SelfSignedCertificate `
    -Subject $CertificateSubject `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -KeyExportPolicy Exportable `
    -KeySpec Signature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears($ExpirationYears)

# Display certificate thumbprint (save this for later)
Write-Host "Certificate Thumbprint: $($Certificate.Thumbprint)" -ForegroundColor Green

⚠️ Compatibility Note: If you encounter certificate authentication errors, create the certificate with the Microsoft RSA SChannel Cryptographic Provider:

$Certificate = New-SelfSignedCertificate `
    -Subject $CertificateSubject `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -Provider "Microsoft RSA SChannel Cryptographic Provider" `
    -KeyExportPolicy Exportable `
    -KeySpec Signature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears($ExpirationYears)

Step 2: Export the Certificate

Export the certificate for upload to Azure AD:

# Export certificate to .cer format (public key only)
$CertificatePath = "C:\Certificates\ExchangeOnlineAutomation.cer"
Export-Certificate -Cert $Certificate -FilePath $CertificatePath

# Export certificate with private key to .pfx format (for backup or other machines)
$PfxPassword = ConvertTo-SecureString -String "SecurePassword123!" -AsPlainText -Force
$PfxPath = "C:\Certificates\ExchangeOnlineAutomation.pfx"
Export-PfxCertificate -Cert $Certificate -FilePath $PfxPath -Password $PfxPassword

🔐 Security Best Practice: Store the .pfx file securely (Azure Key Vault, encrypted storage) and never commit to source control.

Step 3: Register an Application in Microsoft Entra ID

Option A: Using Azure Portal

  1. Navigate to Azure Portal > Microsoft Entra ID > App registrations
  2. Click New registration
  3. Provide an application name (e.g., "Exchange Online PowerShell Automation")
  4. Select Accounts in this organizational directory only
  5. Click Register
  6. Copy and save the Application (client) ID and Directory (tenant) ID

Option B: Using PowerShell

# Install Microsoft Graph module if not already installed
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "Application.ReadWrite.All"

# Create the application registration
$AppName = "Exchange Online PowerShell Automation"
$App = New-MgApplication -DisplayName $AppName

# Display the Application ID
Write-Host "Application (Client) ID: $($App.AppId)" -ForegroundColor Green
Write-Host "Object ID: $($App.Id)" -ForegroundColor Yellow

# Save for later use
$AppId = $App.AppId

Step 4: Upload Certificate to the Application

Option A: Using Azure Portal

  1. In your app registration, go to Certificates & secrets
  2. Click Upload certificate
  3. Upload the .cer file created in Step 2
  4. Note the certificate Thumbprint displayed

Option B: Using PowerShell

# Load the certificate
$Cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object {$_.Subject -eq "CN=$CertificateName"}

# Get the certificate value
$CertValue = [System.Convert]::ToBase64String($Cert.GetRawCertData())

# Add certificate to application
$KeyCredential = @{
    Type = "AsymmetricX509Cert"
    Usage = "Verify"
    Key = [System.Text.Encoding]::ASCII.GetBytes($CertValue)
}

Update-MgApplication -ApplicationId $App.Id -KeyCredentials @($KeyCredential)

Write-Host "Certificate uploaded successfully" -ForegroundColor Green

Step 5: Configure API Permissions

Option A: Using Azure Portal

  1. In your app registration, go to API permissions
  2. Click Add a permission
  3. Select APIs my organization uses
  4. Search for and select Office 365 Exchange Online (exact name)
  5. Select Application permissions
  6. Expand Exchange and select Exchange.ManageAsApp
  7. Click Add permissions
  8. Click Grant admin consent for [your organization]
  9. Click Yes to confirm

Option B: Using PowerShell

# Connect to Microsoft Graph with appropriate permissions
Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All", "Application.Read.All"

# Get the Office 365 Exchange Online service principal
$ExoServicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'"

# Get the Exchange.ManageAsApp role
$AppRole = $ExoServicePrincipal.AppRoles | Where-Object {$_.Value -eq "Exchange.ManageAsApp"}

# Get your application's service principal
$AppServicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$AppId'"

# Grant the API permission
New-MgServicePrincipalAppRoleAssignment `
    -ServicePrincipalId $AppServicePrincipal.Id `
    -PrincipalId $AppServicePrincipal.Id `
    -AppRoleId $AppRole.Id `
    -ResourceId $ExoServicePrincipal.Id

Write-Host "Exchange.ManageAsApp permission granted successfully" -ForegroundColor Green

Step 6: Assign Exchange Administrator Role

The application needs the Exchange Administrator role to perform administrative tasks.

Option A: Using Azure Portal

  1. Navigate to Microsoft Entra ID > Roles and administrators
  2. Search for and select Exchange Administrator
  3. Click Add assignments
  4. Search for your application name
  5. Select it and click Add

Option B: Using PowerShell

# Install Microsoft Graph module if not already installed
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser -Force

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory"

# Get the service principal for your app
$AppId = "YOUR-APP-ID-HERE"
$ServicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '$AppId'"

# Get the Exchange Administrator role definition
$RoleDefinition = Get-MgRoleManagementDirectoryRoleDefinition `
    -Filter "DisplayName eq 'Exchange Administrator'"

# Check if role needs to be activated first
$Role = Get-MgDirectoryRole -Filter "RoleTemplateId eq '$($RoleDefinition.Id)'"
if (-not $Role) {
    # Activate the role if not already activated
    $Role = New-MgDirectoryRole -RoleTemplateId $RoleDefinition.Id
}

# Assign the role to the service principal
New-MgRoleManagementDirectoryRoleAssignment `
    -DirectoryScopeId "/" `
    -RoleDefinitionId $RoleDefinition.Id `
    -PrincipalId $ServicePrincipal.Id

Write-Host "Exchange Administrator role assigned successfully" -ForegroundColor Green

Step 7: Connect Using Certificate

# Define connection parameters
$AppId = "your-app-client-id"
$TenantId = "yourtenant.onmicrosoft.com"  # or tenant GUID
$CertThumbprint = "your-cert-thumbprint"

# Connect to Exchange Online
Connect-ExchangeOnline -CertificateThumbprint $CertThumbprint `
    -AppId $AppId `
    -Organization $TenantId `
    -ShowBanner:$false

# Verify connection
Get-ConnectionInformation

# Test with a command
Get-Mailbox -ResultSize 5

# Disconnect when finished
Disconnect-ExchangeOnline -Confirm:$false

Method B: Using Certificate Object

# Load certificate from store
$Cert = Get-ChildItem Cert:\CurrentUser\My\<YourCertThumbprint>

# Or find by subject
$Cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object {$_.Subject -eq "CN=ExchangeOnlineAutomation"}

# Connect using certificate object
Connect-ExchangeOnline -Certificate $Cert `
    -AppId $AppId `
    -Organization $TenantId `
    -ShowBanner:$false
# Note: Requires managing certificate password - not ideal for automation
$CertPassword = Get-Credential -UserName "Certificate Password" -Message "Enter certificate password"

Connect-ExchangeOnline -CertificateFilePath "C:\Certs\ExchangeAutomation.pfx" `
    -CertificatePassword $CertPassword.Password `
    -AppId $AppId `
    -Organization $TenantId

Complete Automation Script Example

<#
.SYNOPSIS
    Exchange Online automation script with certificate-based authentication

.DESCRIPTION
    Best practice template with error handling and logging
    
.NOTES
    Author: Your Name
    Date: January 2026
    Version: 1.0
#>

# Import required module
Import-Module ExchangeOnlineManagement -ErrorAction Stop

# Define parameters
$AppId = "YOUR-APP-ID"
$CertificateThumbprint = "YOUR-THUMBPRINT"
$Organization = "contoso.onmicrosoft.com"
$LogFile = "C:\Logs\ExchangeScript_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Function for logging
function Write-Log {
    param([string]$Message)
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = "[$Timestamp] $Message"
    Write-Output $LogMessage
    Add-Content -Path $LogFile -Value $LogMessage
}

try {
    Write-Log "Script started"
    
    # Connect
    Write-Log "Connecting to Exchange Online..."
    Connect-ExchangeOnline `
        -AppId $AppId `
        -CertificateThumbprint $CertificateThumbprint `
        -Organization $Organization `
        -ShowBanner:$false `
        -ErrorAction Stop
    
    Write-Log "Connected successfully"
    
    # Perform operations
    Write-Log "Retrieving mailbox information..."
    $Mailboxes = Get-EXOMailbox -ResultSize 100 | 
        Select-Object DisplayName, PrimarySmtpAddress, WhenCreated
    
    Write-Log "Retrieved $($Mailboxes.Count) mailboxes"
    
    # Export results
    $OutputFile = "C:\Reports\Mailboxes_$(Get-Date -Format 'yyyyMMdd').csv"
    $Mailboxes | Export-Csv -Path $OutputFile -NoTypeInformation
    
    Write-Log "Results exported to $OutputFile"
    Write-Log "Operations completed successfully"
    
} catch {
    Write-Log "ERROR: $_"
    Write-Error $_
    exit 1
    
} finally {
    # Always disconnect
    Write-Log "Disconnecting from Exchange Online..."
    Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
    Write-Log "Script completed"
}

Method 3: Managed Identity (for Azure Automation)

Managed Identity provides the most secure authentication method for Azure-hosted resources by eliminating the need to manage certificates or secrets. This is the recommended approach for Azure Automation, Azure Functions, and Azure VMs.

Prerequisites

  • Azure Automation account, Azure VM, Azure Function, or Azure VM Scale Set
  • Exchange Online PowerShell V3 module (Version 3.0.0 or later)
  • System-assigned or user-assigned managed identity enabled on the resource

Step 1: Enable Managed Identity on Azure Resource

For Azure Automation Account

Option A: Using Azure Portal

  1. Navigate to your Automation Account in Azure Portal
  2. Under Account Settings, select Identity
  3. Set System assigned status to On
  4. Click Save
  5. Note the Object (principal) ID

Option B: Using PowerShell

# Connect to Azure
Connect-AzAccount

# Define variables
$ResourceGroupName = "YourResourceGroup"
$AutomationAccountName = "YourAutomationAccount"

# Enable system-assigned managed identity
Set-AzAutomationAccount -Name $AutomationAccountName `
    -ResourceGroupName $ResourceGroupName `
    -AssignSystemIdentity

# Get the managed identity principal ID
$AutomationAccount = Get-AzAutomationAccount -Name $AutomationAccountName `
    -ResourceGroupName $ResourceGroupName
$PrincipalId = $AutomationAccount.Identity.PrincipalId

Write-Host "Managed Identity enabled. Principal ID: $PrincipalId" -ForegroundColor Green

For Azure VM

# Enable system-assigned managed identity on VM
$VM = Get-AzVM -ResourceGroupName "YourResourceGroup" -Name "YourVMName"
Update-AzVM -ResourceGroupName "YourResourceGroup" -VM $VM -IdentityType SystemAssigned

Step 2: Install Exchange Online Module in Automation Account

# Define variables
$ResourceGroupName = "YourResourceGroup"
$AutomationAccountName = "YourAutomationAccount"
$ModuleName = "ExchangeOnlineManagement"

# Add module to Automation Account
New-AzAutomationModule `
    -ResourceGroupName $ResourceGroupName `
    -AutomationAccountName $AutomationAccountName `
    -Name $ModuleName `
    -ContentLinkUri "https://www.powershellgallery.com/api/v2/package/$ModuleName"

# Check module installation status
Get-AzAutomationModule -AutomationAccountName $AutomationAccountName `
    -ResourceGroupName $ResourceGroupName `
    -Name $ModuleName

⏱️ Note: Module installation can take 5-10 minutes. Wait for ProvisioningState to show Succeeded before proceeding.

Step 3: Grant Exchange.ManageAsApp Permission

# Install Microsoft Graph modules
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force

# Connect to Microsoft Graph
Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All", "Application.Read.All"

# Get the managed identity service principal
# For Automation Account:
$ManagedIdentity = Get-MgServicePrincipal -Filter "displayName eq '$AutomationAccountName'"

# For VM:
# $ManagedIdentity = Get-MgServicePrincipal -Filter "displayName eq 'YourVMName'"

# Get Exchange Online service principal
$ExoServicePrincipal = Get-MgServicePrincipal -Filter "AppId eq '00000002-0000-0ff1-ce00-000000000000'"

# Get the Exchange.ManageAsApp role ID (always the same)
$AppRoleId = "dc50a0fb-09a3-484d-be87-e023b12c6440"

# Grant the permission
New-MgServicePrincipalAppRoleAssignment `
    -ServicePrincipalId $ManagedIdentity.Id `
    -PrincipalId $ManagedIdentity.Id `
    -AppRoleId $AppRoleId `
    -ResourceId $ExoServicePrincipal.Id

Write-Host "Exchange.ManageAsApp permission granted" -ForegroundColor Green

Step 4: Assign Exchange Administrator Role

# Install required module
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser -Force

# Connect with appropriate scope
Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory"

# Get Exchange Administrator role
$RoleDefinition = Get-MgRoleManagementDirectoryRoleDefinition `
    -Filter "DisplayName eq 'Exchange Administrator'"

# Check if role is activated
$Role = Get-MgDirectoryRole -Filter "RoleTemplateId eq '$($RoleDefinition.Id)'"
if (-not $Role) {
    $Role = New-MgDirectoryRole -RoleTemplateId $RoleDefinition.Id
}

# Assign role to managed identity
New-MgRoleManagementDirectoryRoleAssignment `
    -PrincipalId $ManagedIdentity.Id `
    -RoleDefinitionId $RoleDefinition.Id `
    -DirectoryScopeId "/"

Write-Host "Exchange Administrator role assigned" -ForegroundColor Green

⏱️ Important: Wait 5-10 minutes after role assignment for permissions to propagate.

Step 5: Create and Test Runbook

Create Runbook in Azure Portal

  1. Navigate to your Automation Account
  2. Go to Process Automation > Runbooks
  3. Click Create a runbook
  4. Name: "Exchange-DailyReport"
  5. Runbook type: PowerShell
  6. Runtime version: 7.2 (recommended)
  7. Click Create

Runbook Script for System-Assigned Managed Identity

<#
.SYNOPSIS
    Exchange Online runbook using managed identity

.DESCRIPTION
    Connects to Exchange Online using the Automation Account's managed identity
    and performs reporting tasks
#>

# Define organization
$Organization = "contoso.onmicrosoft.com"

try {
    Write-Output "Connecting to Exchange Online with Managed Identity..."
    
    # Connect using managed identity
    Connect-ExchangeOnline -ManagedIdentity -Organization $Organization -ErrorAction Stop
    
    Write-Output "Connected successfully"
    
    # Verify connection
    $ConnectionInfo = Get-ConnectionInformation
    Write-Output "Connection Details:"
    Write-Output "  Organization: $($ConnectionInfo.Organization)"
    Write-Output "  State: $($ConnectionInfo.State)"
    
    # Get mailbox statistics
    Write-Output "`nRetrieving mailbox information..."
    $Mailboxes = Get-EXOMailbox -ResultSize 10 | 
        Select-Object DisplayName, PrimarySmtpAddress, @{
            Name='SizeMB'
            Expression={ (Get-EXOMailboxStatistics $_.Identity).TotalItemSize.Value.ToMB() }
        }
    
    Write-Output "`nMailbox Report:"
    $Mailboxes | Format-Table -AutoSize
    
    Write-Output "`nTotal mailboxes retrieved: $($Mailboxes.Count)"
    
} catch {
    Write-Error "An error occurred: $_"
    throw
    
} finally {
    # Disconnect
    Write-Output "`nDisconnecting from Exchange Online..."
    Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
    Write-Output "Script completed"
}

Runbook Script for User-Assigned Managed Identity

# Define parameters
$Organization = "contoso.onmicrosoft.com"
$UserAssignedIdentityClientId = "your-user-assigned-identity-client-id"

try {
    Write-Output "Connecting with User-Assigned Managed Identity..."
    
    # Connect with user-assigned managed identity
    Connect-ExchangeOnline -ManagedIdentity `
        -Organization $Organization `
        -ManagedIdentityAccountId $UserAssignedIdentityClientId `
        -ErrorAction Stop
    
    # Rest of script...
    
} catch {
    Write-Error "An error occurred: $_"
    throw
}

Test the Runbook

  1. Click Test pane in your runbook
  2. Click Start
  3. Monitor the output
  4. Verify successful connection and data retrieval

Advanced: Using Managed Identity from Azure VM

# Connect from Azure VM with managed identity
Connect-ExchangeOnline -ManagedIdentity -Organization "contoso.onmicrosoft.com"

# Verify connection
Get-ConnectionInformation

# Run commands
Get-Mailbox -ResultSize 10

# Disconnect
Disconnect-ExchangeOnline -Confirm:$false

Troubleshooting Managed Identity

Issue: "UnAuthorized" error when connecting

Solutions:

  1. Verify managed identity is enabled on the resource
  2. Confirm Exchange.ManageAsApp permission is granted
  3. Verify Exchange Administrator role is assigned
  4. Wait 5-10 minutes after role assignment
  5. Check Azure AD audit logs for permission changes

Issue: Module not found in runbook

Solutions:

  1. Verify module installation status in Automation Account
  2. Ensure module ProvisioningState is Succeeded
  3. Use PowerShell runtime version 7.2 or later
  4. Re-import the module if necessary

Method 4: Non-Interactive Authentication (Legacy)

⚠️ NOT RECOMMENDED: This method is less secure and should only be used in trusted lab environments. Certificate-based or managed identity authentication should be used instead for production automation.

This method allows connection without interactive prompts using stored credentials. Requirements:

  • MFA must be disabled on the account
  • Account must use password-based authentication
  • Not suitable for production environments

Using Stored Credentials

# Prompt for credentials and store them
$UserCredential = Get-Credential

# Connect using stored credentials
Connect-ExchangeOnline -Credential $UserCredential -ShowBanner:$false
# Convert password to secure string
$Password = ConvertTo-SecureString -String 'YourPassword' -AsPlainText -Force

# Create credential object
$Credential = New-Object System.Management.Automation.PSCredential ('admin@365adviser.com', $Password)

# Connect to Exchange Online
Connect-ExchangeOnline -Credential $Credential -ShowBanner:$false

🔐 Security Warning: Never hardcode passwords in scripts or commit them to source control. Use Azure Key Vault or similar secure credential storage solutions for production environments.


Performance Optimization

Exchange Online PowerShell V3 includes optimized "Get-EXO*" cmdlets that provide significantly better performance for large-scale operations.

Standard vs. Optimized Cmdlets

# Standard cmdlet (slower for large datasets, returns all properties)
$Mailboxes = Get-Mailbox -ResultSize Unlimited

# Optimized EXO cmdlet (faster, returns focused property set)
$Mailboxes = Get-EXOMailbox -ResultSize Unlimited

# Using property sets for specific needs
$Mailboxes = Get-EXOMailbox -PropertySets All -ResultSize Unlimited
$Mailboxes = Get-EXOMailbox -PropertySets Minimum -ResultSize Unlimited

# Retrieve only specific properties needed
$Mailboxes = Get-EXOMailbox -Properties DisplayName, UserPrincipalName, ProhibitSendQuota -ResultSize Unlimited

Available Optimized Cmdlets

Optimized Cmdlet Standard Equivalent Use Case
Get-EXOMailbox Get-Mailbox Large mailbox queries
Get-EXORecipient Get-Recipient Bulk recipient operations
Get-EXOCasMailbox Get-CASMailbox Client access settings
Get-EXOMailboxStatistics Get-MailboxStatistics Mailbox size/stats
Get-EXOMailboxFolderStatistics Get-MailboxFolderStatistics Folder-level stats
Get-EXOMailboxPermission Get-MailboxPermission Permission queries
Get-EXORecipientPermission Get-RecipientPermission Recipient permissions
Get-EXOMobileDeviceStatistics Get-MobileDeviceStatistics Mobile device info

Performance Best Practices

  1. Use optimized cmdlets for operations retrieving 100+ objects
  2. Limit result sets with -ResultSize parameter
  3. Request only needed properties using -Properties parameter
  4. Use property sets for common scenarios (Minimum, Archive, etc.)
  5. Batch operations in automation to reduce API calls
  6. Implement paging for very large datasets

Troubleshooting Common Issues

Issue 1: Module Import Errors

Problem: "The term 'Connect-ExchangeOnline' is not recognized"

Solution:

# Verify module is installed
Get-Module -ListAvailable -Name ExchangeOnlineManagement

# If not found, install it
Install-Module -Name ExchangeOnlineManagement -Force

# Import module manually
Import-Module ExchangeOnlineManagement -Force

# Verify cmdlets are available
Get-Command -Module ExchangeOnlineManagement

Issue 2: Authentication Failures

Problem: "Authorization_RequestDenied" or "Access Denied" errors

Solution:

# Verify account has Exchange admin rights
# Check role assignments in Microsoft Entra admin center

# For interactive: Ensure account can sign in to portal.office.com
# For app-only: Verify API permissions and admin consent
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId

# Check if Exchange Administrator role is assigned
Get-MgRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$PrincipalId'"

Issue 3: Certificate Authentication Failures

Problem: "Certificate not found" or "Keyset does not exist"

Solution:

# Verify certificate is installed in correct store
Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object {$_.Thumbprint -eq "YourThumbprint"}

# Check certificate has private key
$Cert = Get-ChildItem Cert:\CurrentUser\My\YourThumbprint
$Cert.HasPrivateKey  # Should return True

# For scheduled tasks: Verify service account has access to certificate private key
# Run certlm.msc or certmgr.msc and check private key permissions

# Verify certificate hasn't expired
$Cert.NotAfter  # Should be future date

Issue 4: Managed Identity Connection Failures

Problem: "UnAuthorized" when using -ManagedIdentity

Solution:

# Step 1: Verify managed identity is enabled
Get-AzAutomationAccount -Name $AutomationAccountName -ResourceGroupName $ResourceGroupName | 
    Select-Object Identity

# Step 2: Confirm Exchange.ManageAsApp permission is granted
$MI = Get-MgServicePrincipal -Filter "displayName eq '$AutomationAccountName'"
Get-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $MI.Id

# Step 3: Verify Exchange Administrator role is assigned
Get-MgRoleManagementDirectoryRoleAssignment -Filter "principalId eq '$($MI.Id)'"

# Step 4: Wait 5-10 minutes after role assignment for propagation

Issue 5: Connection Timeout

Problem: Connection hangs or times out

Solution:

# Verify network connectivity to Exchange Online
Test-NetConnection outlook.office365.com -Port 443

# Check for proxy requirements
# The module auto-detects system proxy settings

# Clear cached tokens
[Microsoft.Exchange.Management.ExoPowershellSnapin.ConnectionManager]::DisconnectAllConnections()

# Try connecting again
Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com

Issue 6: "Too Many Connections" Error

Problem: "Cannot create more than 3 connections"

Solution:

# Check existing connections
Get-ConnectionInformation

# Disconnect all sessions
Disconnect-ExchangeOnline -Confirm:$false

# In automation scripts, always disconnect in finally block
try {
    Connect-ExchangeOnline -CertificateThumbprint $Thumbprint -AppId $AppId -Organization $Org
    # Your code here
} finally {
    Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
}

Issue 7: PowerShellGet/PackageManagement Errors

Problem: "REST API connections require PowerShellGet"

Solution:

# Update PowerShellGet and PackageManagement
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-Module -Name PowerShellGet -Force -AllowClobber
Install-Module -Name PackageManagement -Force -AllowClobber

# Close and reopen PowerShell window
# Then try installing Exchange Online module again

Best Practices

Security Best Practices

  1. Use certificate-based authentication for all automation scenarios
  2. Enable MFA on all interactive admin accounts
  3. Apply principle of least privilege – assign only necessary permissions
  4. Rotate certificates regularly (recommend 1-2 year expiration)
  5. Store credentials securely using Azure Key Vault or similar solutions
  6. Never hardcode passwords in scripts or commit them to source control
  7. Use managed identities for Azure resources instead of service principals when possible
  8. Monitor and audit all Exchange Online PowerShell connections
  9. Implement conditional access policies for admin accounts
  10. Regularly review API permissions and role assignments

Performance Best Practices

  1. Use Get-EXO cmdlets* for large-scale data retrieval operations
  2. Limit result sets with -ResultSize parameter to avoid memory issues
  3. Use property sets to retrieve only needed properties
  4. Disconnect sessions when finished to free resources
  5. Avoid Get-Mailbox -ResultSize Unlimited unless absolutely necessary
  6. Batch operations in automation scenarios to reduce API calls
  7. Implement error handling with try-catch-finally blocks
  8. Use -ShowBanner:$false to reduce output in automated scripts
  9. Leverage -PropertySets parameter for focused queries
  10. Implement retry logic for transient failures

Scripting Best Practices Template

<#
.SYNOPSIS
    Template for Exchange Online PowerShell scripts

.DESCRIPTION
    Best practice template with error handling, logging, and proper cleanup
    
.PARAMETER ReportPath
    Path where reports will be saved
    
.EXAMPLE
    .\Exchange-Script.ps1 -ReportPath "C:\Reports"
    
.NOTES
    Author: Your Name
    Date: January 2026
    Version: 1.0
    Requires: ExchangeOnlineManagement module 3.9.2+
#>

[CmdletBinding()]
param(
    [Parameter(Mandatory=$true)]
    [string]$ReportPath
)

# Requires statement
#Requires -Modules ExchangeOnlineManagement
#Requires -Version 5.1

# Import required modules
Import-Module ExchangeOnlineManagement -ErrorAction Stop

# Define parameters
$AppId = $env:EXO_APP_ID  # Store in environment variables or secure vault
$CertificateThumbprint = $env:EXO_CERT_THUMBPRINT
$Organization = "contoso.onmicrosoft.com"
$LogFile = Join-Path $ReportPath "ExchangeScript_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"

# Ensure report directory exists
if (-not (Test-Path $ReportPath)) {
    New-Item -Path $ReportPath -ItemType Directory -Force | Out-Null
}

# Logging function
function Write-Log {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$Message,
        
        [Parameter(Mandatory=$false)]
        [ValidateSet('Info','Warning','Error')]
        [string]$Level = 'Info'
    )
    
    $Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    $LogMessage = "[$Timestamp] [$Level] $Message"
    
    # Output to console with color
    switch ($Level) {
        'Warning' { Write-Warning $Message }
        'Error' { Write-Error $Message }
        default { Write-Output $LogMessage }
    }
    
    # Write to log file
    Add-Content -Path $LogFile -Value $LogMessage
}

# Main script logic
try {
    Write-Log "===== Script Started =====" -Level Info
    Write-Log "Report Path: $ReportPath"
    
    # Validate prerequisites
    Write-Log "Validating prerequisites..."
    if (-not $AppId -or -not $CertificateThumbprint) {
        throw "Required environment variables not set"
    }
    
    # Connect to Exchange Online
    Write-Log "Connecting to Exchange Online..."
    Connect-ExchangeOnline `
        -AppId $AppId `
        -CertificateThumbprint $CertificateThumbprint `
        -Organization $Organization `
        -ShowBanner:$false `
        -ErrorAction Stop
    
    Write-Log "Connected successfully"
    
    # Verify connection
    $Connection = Get-ConnectionInformation
    Write-Log "Connection State: $($Connection.State)"
    Write-Log "Connected Organization: $($Connection.Organization)"
    
    # Perform operations
    Write-Log "Retrieving mailbox information..."
    $Mailboxes = Get-EXOMailbox -ResultSize 100 -ErrorAction Stop | 
        Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeDetails, WhenCreated
    
    Write-Log "Retrieved $($Mailboxes.Count) mailboxes"
    
    # Export results
    $OutputFile = Join-Path $ReportPath "Mailboxes_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv"
    $Mailboxes | Export-Csv -Path $OutputFile -NoTypeInformation -ErrorAction Stop
    
    Write-Log "Results exported to: $OutputFile"
    Write-Log "Operations completed successfully" -Level Info
    
} catch {
    Write-Log "FATAL ERROR: $_" -Level Error
    Write-Log "Stack Trace: $($_.ScriptStackTrace)" -Level Error
    throw  # Re-throw to ensure non-zero exit code
    
} finally {
    # Always disconnect and cleanup
    Write-Log "Cleaning up..."
    try {
        Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue
        Write-Log "Disconnected from Exchange Online"
    } catch {
        Write-Log "Error during disconnect: $_" -Level Warning
    }
    
    Write-Log "===== Script Completed ====="
}

Connection Status and Management

Check Current Connection

# View connection details
Get-ConnectionInformation

# Check connection state
$Connection = Get-ConnectionInformation
if ($Connection.State -eq 'Connected') {
    Write-Host "Connected to: $($Connection.Organization)" -ForegroundColor Green
}

Disconnect from Exchange Online

# Disconnect with confirmation prompt
Disconnect-ExchangeOnline

# Disconnect without confirmation (for scripts)
Disconnect-ExchangeOnline -Confirm:$false

💡 Best Practice: Always disconnect in a finally block to ensure cleanup even if errors occur.


Part 2: Microsoft Entra ID (Directory Services)

🚨 CRITICAL: MSOnline/AzureAD Module Retirement

⚠️ URGENT ACTION REQUIRED

MSOnline PowerShell stops working: April - May 2025
AzureAD PowerShell retired after: July 1, 2025

If you're using Connect-MsolService or Connect-AzureAD, you MUST migrate to Microsoft Graph PowerShell SDK immediately.

Retirement Timeline

Date Event
March 30, 2024 Deprecation announced - no new features
Feb-Mar 2025 Temporary outages for MSOnline (testing)
March 30, 2025 End of support for both modules
April-May 2025 MSOnline stops working for ALL tenants
July 1, 2025 AzureAD retirement begins

Why This Matters

If you don't migrate:

  • ❌ Your automation scripts will fail
  • ❌ User management tasks will break
  • ❌ No support from Microsoft
  • ❌ Security vulnerabilities won't be patched

What you need to do:

  1. ✅ Install Microsoft Graph PowerShell SDK
  2. ✅ Update all scripts using MSOnline/AzureAD cmdlets
  3. ✅ Test thoroughly in non-production
  4. ✅ Deploy updated scripts before May 2025

Microsoft Graph PowerShell SDK

Microsoft Graph PowerShell SDK is the official, modern replacement for MSOnline and AzureAD modules. It provides access to the entire Microsoft Graph API, including Microsoft Entra ID (Azure AD), Microsoft 365 services, and more.

Why Microsoft Graph?

Unified API - Single endpoint for all Microsoft 365 services
Modern Authentication - OAuth 2.0, certificate-based, managed identity
Cross-Platform - Windows, macOS, Linux
PowerShell 7 Support - Actively maintained
Comprehensive - Access to 100+ Microsoft services
Secure - Granular permission management

Installation

# Install the complete SDK (recommended for most users)
Install-Module Microsoft.Graph -Scope CurrentUser

# Or install specific sub-modules for targeted needs
Install-Module Microsoft.Graph.Users -Scope CurrentUser
Install-Module Microsoft.Graph.Groups -Scope CurrentUser
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser

# Update existing installation
Update-Module Microsoft.Graph

# Verify installation
Get-InstalledModule Microsoft.Graph

Interactive Authentication

# Connect with delegated permissions (as signed-in user)
Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All"

# Check connection context
Get-MgContext

# Example: Get users
Get-MgUser -Top 10 | Select-Object DisplayName, UserPrincipalName, JobTitle

# Example: Get groups
Get-MgGroup -Top 10 | Select-Object DisplayName, Description, GroupTypes

# Disconnect when finished
Disconnect-MgGraph

Certificate-Based Authentication (App-Only)

Perfect for automation and unattended scripts.

Step 1: Create Certificate

# Create self-signed certificate
$CertName = "GraphAutomation"
$Cert = New-SelfSignedCertificate `
    -Subject "CN=$CertName" `
    -CertStoreLocation "Cert:\CurrentUser\My" `
    -KeyExportPolicy Exportable `
    -KeySpec Signature `
    -KeyLength 2048 `
    -KeyAlgorithm RSA `
    -HashAlgorithm SHA256 `
    -NotAfter (Get-Date).AddYears(2)

# Export certificate
$CertPath = "C:\Certificates"
New-Item -Path $CertPath -ItemType Directory -Force
Export-Certificate -Cert $Cert -FilePath "$CertPath\GraphAutomation.cer"

# Note the thumbprint
$CertThumbprint = $Cert.Thumbprint
Write-Host "Certificate Thumbprint: $CertThumbprint" -ForegroundColor Green

Step 2: Register App and Upload Certificate

Via Azure Portal:

  1. Go to Microsoft Entra admin center (entra.microsoft.com)
  2. Navigate to App registrationsNew registration
  3. Name: "Microsoft Graph PowerShell Automation"
  4. Account type: "Accounts in this organizational directory only"
  5. Click Register
  6. Note the Application (client) ID and Directory (tenant) ID
  7. Go to Certificates & secretsCertificatesUpload certificate
  8. Upload the .cer file
  9. Note the certificate Thumbprint

Step 3: Grant API Permissions

  1. Go to API permissionsAdd a permission
  2. Select Microsoft GraphApplication permissions
  3. Add required permissions:
    • User.Read.All - Read all user profiles
    • Group.ReadWrite.All - Read and write groups
    • Directory.ReadWrite.All - Read and write directory data (use cautiously)
  4. Click Grant admin consent for [organization]

Step 4: Connect Using Certificate

# Define connection parameters
$ClientId = "your-app-client-id"
$TenantId = "your-tenant-id"
$CertThumbprint = "your-cert-thumbprint"

# Connect using certificate thumbprint
Connect-MgGraph -ClientId $ClientId `
    -TenantId $TenantId `
    -CertificateThumbprint $CertThumbprint

# Verify connection
Get-MgContext

# Example operations
Get-MgUser -ConsistencyLevel eventual -Count userCount
Get-MgGroup -Filter "groupTypes/any(c:c eq 'Unified')"

# Disconnect
Disconnect-MgGraph

Alternative: Using Certificate Object

# Load certificate from store
$Cert = Get-ChildItem Cert:\CurrentUser\My\$CertThumbprint

# Connect using certificate object
Connect-MgGraph -ClientId $ClientId `
    -TenantId $TenantId `
    -Certificate $Cert

Managed Identity Authentication

For Azure Automation, Azure Functions, and Azure VMs.

# System-assigned managed identity
Connect-MgGraph -Identity

# User-assigned managed identity
Connect-MgGraph -Identity -ClientId "user-assigned-identity-client-id"

Client Secret Authentication

# Create PSCredential with client secret
$ClientId = "your-app-client-id"
$ClientSecret = "your-client-secret"
$TenantId = "your-tenant-id"

$SecureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
$ClientSecretCredential = New-Object System.Management.Automation.PSCredential($ClientId, $SecureSecret)

# Connect
Connect-MgGraph -TenantId $TenantId -ClientSecretCredential $ClientSecretCredential

🔐 Security Note: Client secrets should be stored securely (Azure Key Vault) and rotated regularly. Certificate-based authentication is more secure for production use.


Migration from MSOnline/AzureAD

Common Cmdlet Migration

MSOnline/AzureAD Cmdlet Microsoft Graph Cmdlet Notes
Connect-MsolService Connect-MgGraph Requires specifying scopes
Connect-AzureAD Connect-MgGraph Requires specifying scopes
Get-MsolUser Get-MgUser May need -ConsistencyLevel eventual
Get-AzureADUser Get-MgUser May need -ConsistencyLevel eventual
New-MsolUser New-MgUser Different parameter names
New-AzureADUser New-MgUser Different parameter names
Set-MsolUser Update-MgUser Uses -UserId parameter
Set-AzureADUser Update-MgUser Uses -UserId parameter
Remove-MsolUser Remove-MgUser Uses -UserId parameter
Remove-AzureADUser Remove-MgUser Uses -UserId parameter
Get-MsolGroup Get-MgGroup Different filtering syntax
Get-AzureADGroup Get-MgGroup Different filtering syntax
Get-MsolGroupMember Get-MgGroupMember Uses -GroupId parameter
Get-AzureADGroupMember Get-MgGroupMember Uses -GroupId parameter
Add-MsolGroupMember New-MgGroupMember Different syntax
Add-AzureADGroupMember New-MgGroupMember Different syntax
Get-MsolDomain Get-MgDomain Similar functionality
Get-AzureADDomain Get-MgDomain Similar functionality

Finding Equivalent Cmdlets

# Find Microsoft Graph command for specific functionality
Find-MgGraphCommand -Command Get-MgUser

# See all available Graph commands
Get-Command -Module Microsoft.Graph.*

# Get help for specific cmdlet
Get-Help Get-MgUser -Full

Migration Example: User Management

Old (MSOnline):

Connect-MsolService
$Users = Get-MsolUser -All
$Users | Where-Object {$_.IsLicensed -eq $true}

New (Microsoft Graph):

Connect-MgGraph -Scopes "User.Read.All"
$Users = Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses
$Users | Where-Object {$_.AssignedLicenses.Count -gt 0}

Old (AzureAD):

Connect-AzureAD
$Group = Get-AzureADGroup -Filter "DisplayName eq 'IT Admins'"
$Members = Get-AzureADGroupMember -ObjectId $Group.ObjectId

New (Microsoft Graph):

Connect-MgGraph -Scopes "Group.Read.All"
$Group = Get-MgGroup -Filter "DisplayName eq 'IT Admins'"
$Members = Get-MgGroupMember -GroupId $Group.Id

Advanced Filtering and Queries

Microsoft Graph requires different syntax for advanced queries:

# Connect with appropriate permissions
Connect-MgGraph -Scopes "User.Read.All"

# Count users (requires ConsistencyLevel)
Get-MgUser -ConsistencyLevel eventual -Count userCount

# Advanced filtering
Get-MgUser -ConsistencyLevel eventual `
    -Filter "startsWith(DisplayName, 'Admin')" `
    -OrderBy DisplayName

# Search (requires ConsistencyLevel)
Get-MgUser -ConsistencyLevel eventual `
    -Search "DisplayName:John" `
    -Count userCount

# Complex filter with multiple conditions
Get-MgUser -ConsistencyLevel eventual `
    -Filter "accountEnabled eq true and userType eq 'Member'" `
    -Select DisplayName, UserPrincipalName, Department

Complete Migration Script Example

<#
.SYNOPSIS
    Migration example from MSOnline to Microsoft Graph

.DESCRIPTION
    Shows how to migrate common user management tasks
#>

# Old way (MSOnline) - DEPRECATED
<#
Connect-MsolService
$Users = Get-MsolUser -All | Where-Object {$_.IsLicensed -eq $true}
foreach ($User in $Users) {
    Set-MsolUser -UserPrincipalName $User.UserPrincipalName -Department "IT"
}
#>

# New way (Microsoft Graph)
try {
    # Connect with required permissions
    Connect-MgGraph -Scopes "User.ReadWrite.All" -ErrorAction Stop
    
    # Get licensed users
    Write-Host "Retrieving licensed users..."
    $Users = Get-MgUser -All -Property DisplayName, UserPrincipalName, AssignedLicenses, Department | 
        Where-Object {$_.AssignedLicenses.Count -gt 0}
    
    Write-Host "Found $($Users.Count) licensed users"
    
    # Update users
    foreach ($User in $Users) {
        Write-Host "Updating: $($User.DisplayName)"
        
        Update-MgUser -UserId $User.Id -Department "IT" -ErrorAction Stop
    }
    
    Write-Host "Migration completed successfully" -ForegroundColor Green
    
} catch {
    Write-Error "Error during migration: $_"
} finally {
    Disconnect-MgGraph
}

Part 3: Additional Services

Security & Compliance PowerShell

Connect to Security & Compliance PowerShell (Microsoft Purview) for eDiscovery, compliance, and data governance tasks.

Prerequisites

  • Exchange Online PowerShell module (same module as Exchange Online)
  • Security & Compliance admin role or appropriate permissions

Interactive Connection

# Connect to Security & Compliance
Connect-IPPSSession -UserPrincipalName admin@365adviser.com

# Verify connection
Get-ConnectionInformation

# Example commands
Get-ComplianceSearch
Get-RetentionCompliancePolicy

# Disconnect
Disconnect-IPPSSession

Certificate-Based Authentication

# Use same certificate setup as Exchange Online
Connect-IPPSSession -CertificateThumbprint $CertThumbprint `
    -AppId $AppId `
    -Organization $TenantId

# Note: REST API mode is default in version 3.2.0+

Managed Identity

# Connect from Azure Automation with managed identity
Connect-IPPSSession -ManagedIdentity -Organization "contoso.onmicrosoft.com"

🔍 Note: Version 3.9.0+ supports -EnableSearchOnlySession switch for eDiscovery scenarios that connect to other M365 services.

REST API vs RPS Mode

# REST API mode (default, recommended)
Connect-IPPSSession -UserPrincipalName admin@365adviser.com

# Legacy RPS mode (deprecated in 3.9.2)
# Connect-IPPSSession -UserPrincipalName admin@365adviser.com -UseRPSSession

⚠️ Deprecation Notice: The UseRPSSession parameter has been deprecated in version 3.9.2. Always use REST API mode.


Hybrid Exchange (Optional)

For organizations with on-premises Exchange servers in hybrid configuration.

Connect to On-Premises Exchange

Using Exchange Management Shell

On the Exchange server, launch Exchange Management Shell - this automatically loads cmdlets.

# Verify Exchange snapin is loaded
Get-PSSnapin | Where-Object {$_.Name -like "*Exchange*"}

# Example commands
Get-Mailbox -ResultSize 10
Get-ExchangeServer

Remote PowerShell Connection

# Define connection parameters
$ExchangeServer = "exchangeserver.contoso.local"
$Credential = Get-Credential  # Domain credentials

# Create remote session
$Session = New-PSSession -ConfigurationName Microsoft.Exchange `
    -ConnectionUri "http://$ExchangeServer/PowerShell/" `
    -Authentication Kerberos `
    -Credential $Credential `
    -ErrorAction Stop

# Import session
Import-PSSession $Session -DisableNameChecking -AllowClobber

# Verify connection
Get-ExchangeServer

# Run commands
Get-Mailbox -Server $ExchangeServer -ResultSize 10

# Disconnect when finished
Remove-PSSession $Session

Hybrid: Connect to Both Cloud and On-Premises

# Connect to Exchange Online with prefix
Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com -Prefix Cloud

# Connect to on-premises with prefix
$OnPremSession = New-PSSession -ConfigurationName Microsoft.Exchange `
    -ConnectionUri "http://exchangeserver.contoso.local/PowerShell/" `
    -Authentication Kerberos `
    -Credential (Get-Credential)

Import-PSSession $OnPremSession -DisableNameChecking -AllowClobber -Prefix OnPrem

# Now use both with prefixes
Get-CloudMailbox -Identity user@365adviser.com
Get-OnPremMailbox -Identity user@contoso.local

# Disconnect both
Remove-PSSession $OnPremSession
Disconnect-ExchangeOnline -Confirm:$false

Additional Resources

Microsoft Official Documentation

Exchange Online PowerShell:

Microsoft Graph PowerShell:

Module Retirement:

Community Resources


Version History

Current Module Versions (January 2026):

  • ExchangeOnlineManagement: 3.9.2 (Latest GA)
  • Microsoft.Graph: 2.x (Latest GA)

Key Updates in ExchangeOnlineManagement V3:

  • REST API-based cmdlets (no WinRM Basic Authentication required)
  • Managed identity support for Azure resources
  • Certificate-based authentication for automation
  • Enhanced performance with optimized Get-EXO* cmdlets
  • Cross-platform support (Windows, macOS, Linux)
  • PowerShell 7.4+ compatibility

Recent Changes:

  • v3.9.2 (January 2026): UseRpsSession parameter deprecated
  • v3.9.0 (August 2025): Added EnableSearchOnlySession switch for Connect-IPPSSession
  • v3.7.0 (2024): Web Account Manager (WAM) integration, LoadCmdletHelp parameter

Deprecated Features:

  • Basic Authentication (permanently disabled October 2022)
  • Remote PowerShell (RPS) protocol (blocked October 2023)
  • MSOnline module (stops working April-May 2025)
  • AzureAD module (retirement begins July 2025)
  • New-PSSession method for Exchange Online
  • UseRpsSession parameter (deprecated in 3.9.2)

Quick Reference Card

Most Common Commands

# Interactive Connection
Connect-ExchangeOnline -UserPrincipalName admin@365adviser.com

# Certificate-Based (Automation)
Connect-ExchangeOnline -CertificateThumbprint $Thumb -AppId $AppId -Organization $Org

# Managed Identity (Azure)
Connect-ExchangeOnline -ManagedIdentity -Organization $Org

# Microsoft Graph (Users/Groups)
Connect-MgGraph -Scopes "User.Read.All", "Group.ReadWrite.All"

# Check Connection
Get-ConnectionInformation

# Disconnect
Disconnect-ExchangeOnline -Confirm:$false
Disconnect-MgGraph

Useful One-Liners

# Get all mailboxes
Get-EXOMailbox -ResultSize Unlimited

# Get mailbox statistics
Get-EXOMailboxStatistics -Identity user@365adviser.com

# Get users with licenses
Get-MgUser -All -Property AssignedLicenses | Where-Object {$_.AssignedLicenses.Count -gt 0}

# Get all groups
Get-MgGroup -All

# Export mailbox list
Get-EXOMailbox -ResultSize Unlimited | Export-Csv C:\mailboxes.csv -NoTypeInformation

Share this article

Help others discover this content

Need Help Implementing This Solution?

Schedule a free 30-minute consultation to discuss your specific Microsoft 365 or Azure needs.

Schedule Free Consultation

Related Articles

How to: Securely Connect to Microsoft 365 and Azure Using PowerShell with MFA
PowerShell

How to: Securely Connect to Microsoft 365 and Azure Using PowerShell with MFA

Microsoft 365 and Azure administrators rely heavily on PowerShell for managing, automating, and reporting on their cloud environments. However, the landscape of PowerShell connectivity to these services has evolved significantly over the past few years, with Microsoft placing a stronger emphasis on security, modern authentication, and consolidation of management tools. This article provides an updated guide on how to securely connect to Microsoft 365 and Azure using PowerShell with Multi-Factor Authentication (MFA) support. Microsoft is implementing mandatory MFA enforcement in phases, with MFA becoming required for the Microsoft 365 admin center beginning in February 2025, and for Azure CLI, PowerShell, and REST API endpoints starting July 1, 2025. Understanding these changes and implementing secure connection methods is critical for all administrators. Problem Definition Administrators face several challenges when connecting to Microsoft 365 and Azure services through :

Apr 30, 2025
7 min
Microsoft 365 Logical Architecture Template
Microsoft 365

Microsoft 365 Logical Architecture Template

Introduction Proper documentation of Microsoft 365 architecture is essential for successful implementation, management, and scalability. Engineers and architects need to create descriptive documentation that accurately reflects both current and future infrastructures. While each organization&#39;s implementation may have unique elements, having standardized templates with common infrastructure components provides an invaluable starting point for planning and communication. This guide explores the key components of Microsoft 365 logical architecture, offering best practices, implementation strategies, and visual templates to help IT professionals effectively design, document, and manage their Microsoft 365 environment. The Problem: Complexity in Modern Cloud Architecture

Dec 20, 2024
7 min
How to: Find the BitLocker Recovery Key in Microsoft Entra ID
Microsoft 365

How to: Find the BitLocker Recovery Key in Microsoft Entra ID

Summary There are two different use cases where either an end-user or a system administrator needs to find the BitLocker recovery key. In addition, Microsoft has multiple user interfaces and administrative portals to navigate in order to find the recovery key. While it is helpful to be able to find the recovery key through different interfaces, this can confuse users and complicate training or documentation. This article documents how to find the BitLocker Recovery Key and the various options available. Understanding BitLocker Recovery Keys in Microsoft Entra ID BitLocker Drive Encryption is a data protection feature that integrates with the operating system and addresses the threats of data theft or exposure from lost, stolen, or inappropriately decommissioned computers. When BitLocker is enabled on a device, the recovery key is automatically saved to Microsoft Entra ID (formerly Azure AD) if the device is joined to Entra ID or if the user signs in with a Microsoft account.

Apr 1, 2025
6 min

Stay Updated

Join IT professionals receiving Microsoft 365 tutorials and insights