Choose Your Migration Type
Three main approaches — pick based on mailbox count and Exchange version:
- Cutover migration — Move all mailboxes at once. Best for under 150 mailboxes. Requires a maintenance window.
- Staged migration — Batches over weeks. Requires Exchange 2003/2007. Rarely used today.
- Hybrid migration — Deploy Exchange Hybrid, sync directories, migrate mailboxes with zero downtime and coexistence as long as needed. Best for 50+ mailboxes on Exchange 2010+.
This runbook covers hybrid migration — the most common approach for organizations on Exchange 2013, 2016, or 2019.
Pre-Migration Checklist
# Verify your domain is added and verified in M365
# admin.microsoft.com -> Settings -> Domains
# Do NOT set corp.com as the primary domain yet — that changes MX
# Check autodiscover is working externally
# Test at: https://testconnectivity.microsoft.com
# Audit all mailboxes and their email addresses
Get-Mailbox -ResultSize Unlimited |
Select DisplayName, PrimarySmtpAddress,
@{N='Aliases';E={$_.EmailAddresses -join ';'}} |
Export-Csv C:\mailboxes-pre-migration.csv
Checklist before starting:
- Domain verified in M365 (not yet primary)
- Exchange Hybrid Configuration Wizard completed — creates the hybrid org relationship
- Azure AD Connect installed and syncing — users must exist in Azure AD before migrating
- Exchange Online licenses assigned to all migrating users
- Autodiscover working externally (test at testconnectivity.microsoft.com)
- MX record TTL lowered to 300 seconds at least 48 hours before cutover
- SPF record includes both on-prem and M365:
v=spf1 include:spf.protection.outlook.com ip4:[your-ip] -all
Directory Sync Setup
# Install Azure AD Connect on a member server — NOT the Exchange server
# Download: https://www.microsoft.com/en-us/download/details.aspx?id=47594
# After installation, verify sync is running
Get-ADSyncScheduler | Select SyncCycleEnabled, NextSyncCyclePolicyType
# Check that users are appearing in M365 with the right UPN
Connect-MsolService
Get-MsolUser -All | Where-Object {$_.IsLicensed -eq $false} |
Select UserPrincipalName | Sort UserPrincipalName
Mailbox Migration Batches
# Connect to Exchange Online
Connect-ExchangeOnline
# Start a migration batch
New-MigrationBatch -Name "Batch1-Finance" `
-CSVData ([System.IO.File]::ReadAllBytes("C:\finance-users.csv")) `
-TargetDeliveryDomain "corp.mail.onmicrosoft.com" `
-AutoStart
# Monitor migration progress
Get-MigrationBatch -Identity "Batch1-Finance" |
Get-MigrationUser |
Select Identity, Status, PercentageComplete, BytesTransferred |
Format-Table
CSV format for migration batches (one email address per line):
EmailAddress jsmith@corp.com mjones@corp.com alee@corp.com
Users can still send and receive on-prem during migration — mail flow continues. Set the batch status to Complete to finalize each user and cut over their mailbox to Exchange Online.
MX Record Cutover
Once MX points to M365, all new mail routes to Exchange Online. Do this after business hours.
Get-MigrationUser | Where-Object {$_.Status -ne "Completed"}
Should return empty. If not, complete remaining batches first.
Change MX to [tenant].mail.protection.outlook.com with priority 0. Remove the old on-prem MX record. TTL is already 300 from pre-migration prep so propagation is fast.
Point autodiscover.corp.com CNAME to autodiscover.outlook.com. Outlook clients pick up the new server on next profile refresh or restart.
In M365 Admin Center → Settings → Domains → set corp.com as the primary domain. This finalizes mail routing to Exchange Online.
Post-Migration Cleanup
# Verify mail is flowing into M365 # Send a test from an external Gmail/Outlook account and confirm it arrives in Exchange Online # Update SPF after 30 days (to catch any stragglers routing through on-prem) # New SPF record: v=spf1 include:spf.protection.outlook.com -all # Add DKIM in M365 Admin Center -> Security -> Policies -> DKIM # Add DMARC: _dmarc.corp.com TXT "v=DMARC1; p=none; rua=mailto:dmarc@corp.com"