A comprehensive PowerShell solution for migrating email aliases from on-premise Exchange to Exchange Online safely and efficiently.
Table of Contents
Overview
When migrating from on-premise Exchange to Exchange Online (Office 365), one of the most challenging aspects is preserving email aliases. Users often have multiple email addresses that need to be maintained for business continuity. This PowerShell solution provides a safe, automated way to export aliases from your on-premise environment and import them to Exchange Online.
✅ Safe Operations
Scripts only add aliases, never remove existing ones. Your current Exchange Online configuration remains intact.
Flexible Targeting
Target specific databases, organizational units, or use custom filters to scope your migration precisely.
Preview Mode
WhatIf support lets you preview all changes before applying them, ensuring confidence in your migration.
Progress Tracking
Real-time progress indicators and comprehensive logging help you monitor large migrations.
Structured Export
JSON format provides easy review, backup, and audit trail of your migration data.
Enterprise Ready
Built for production environments with error handling, retry logic, and detailed reporting.
Prerequisites
For On-Premise Exchange (Export)
- PowerShell 5.1 or later – Required for script execution
- Exchange Management Shell – Or remote PowerShell access to Exchange server
- Administrative Permissions – Organization Management or Recipient Management role
- Network Access – Connectivity to Exchange server from script execution location
For Exchange Online (Import)
- PowerShell 5.1 or later – Required for script execution
- ExchangeOnlineManagement Module – Microsoft’s official PowerShell module
- Exchange Online Admin Rights – Global Admin or Exchange Administrator role
- Modern Authentication – Support for OAuth 2.0 and conditional access policies
Installation Guide
Install Exchange Online Management Module
This module is required for connecting to Exchange Online and managing mailboxes.
Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber# Verify installation
Get-Module -ListAvailable -Name ExchangeOnlineManagement
Download the Scripts
Get the latest version of both PowerShell scripts from the GitHub repository.
git clone https://github.com/MSB365/Mail-Alias-Migration.git
cd Mail-Alias-Migration# Or download individual files directly from GitHub
Direct Download Links:
Configure PowerShell Execution Policy
Allow the scripts to run by setting the appropriate execution policy.
# Set execution policy for current user Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser# Verify the setting Get-ExecutionPolicy -List
⚡ Quick Start Guide
Export from On-Premise Exchange
Run the export script on a machine with Exchange Management Shell access.
# Export all mailboxes from a specific database .\Export-OnPremiseMailboxes.ps1 -Database "Mailbox Database 01"# Export with department filter .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Sales'" # Export from specific OU .\Export-OnPremiseMailboxes.ps1 -OrganizationalUnit "OU=Users,DC=contoso,DC=com"
Review the Export Data
Examine the generated JSON file to verify the export captured the expected data.
# Load and review export summary $data = Get-Content .\mailbox-addresses.json | ConvertFrom-Json Write-Host "Exported $($data.Mailboxes.Count) mailboxes"# View detailed information $data.Mailboxes | Format-Table DisplayName, PrimarySMTPAddress, @{Name="Aliases";Expression={$_.EmailAliases.Count}}
Test Import (WhatIf Mode)
Preview what changes will be made without actually applying them.
.\Import-MailboxAliases.ps1 -JsonPath .\mailbox-addresses.json -WhatIf
Perform the Import
Execute the actual import to add aliases to Exchange Online mailboxes.
# Import aliases to Exchange Online .\Import-MailboxAliases.ps1 -JsonPath .\mailbox-addresses.json
Export Script: Export-OnPremiseMailboxes.ps1
This script connects to your on-premise Exchange server and exports mailbox information including primary SMTP addresses and all aliases to a structured JSON file.
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
Server |
String | No | Exchange server FQDN (uses auto-discovery if not specified) |
Database |
String | No | Target specific mailbox database for scoped export |
OrganizationalUnit |
String | No | Target specific organizational unit (OU) path |
Filter |
String | No | Custom PowerShell filter expression for mailbox selection |
OutputPath |
String | No | JSON output file path (default: .\mailbox-addresses.json ) |
Credential |
PSCredential | No | Alternative credentials for Exchange connection |
Usage Examples
Database-Specific Export
# Export all mailboxes from a specific database .\Export-OnPremiseMailboxes.ps1 -Database "Executive Mailbox Database"# Export with custom output location .\Export-OnPremiseMailboxes.ps1 -Database "Sales DB" -OutputPath "C:\Migration\sales-export.json"
Department-Based Export
# Export Marketing department .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Marketing'"# Export multiple departments .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Sales' -or Department -eq 'Marketing'"
# Export with location filter
.\Export-OnPremiseMailboxes.ps1 -Filter “Department -eq ‘IT’ -and Office -eq ‘New York'”
Organizational Unit Export
# Export from specific OU .\Export-OnPremiseMailboxes.ps1 -OrganizationalUnit "OU=VIP,OU=Users,DC=company,DC=com"# Export executives .\Export-OnPremiseMailboxes.ps1 -OrganizationalUnit "OU=Executives,DC=contoso,DC=com"
Common Filter Examples
# Department-based filters -Filter "Department -eq 'IT'" -Filter "Department -like '*Sales*'"# Location-based filters -Filter "Office -eq 'London'" -Filter "Office -ne 'Temporary'"
# Title-based filters
-Filter “Title -like ‘*Manager*'”
-Filter “Title -like ‘*Director*’ -or Title -like ‘*VP*'”
# Exclude test accounts
-Filter “DisplayName -notlike ‘Test*’ -and DisplayName -notlike ‘Demo*'”
# Custom attribute filters
-Filter “CustomAttribute1 -eq ‘VIP'”
-Filter “CustomAttribute2 -ne ‘Exclude'”
# Complex combinations
-Filter “Department -eq ‘Sales’ -and Office -eq ‘NYC’ -and Title -like ‘*Manager*'”
Import Script: Import-MailboxAliases.ps1
This script reads the JSON file created by the export script and safely adds the aliases to corresponding mailboxes in Exchange Online. It only adds aliases and never removes existing ones.
Parameters
Parameter | Type | Required | Description |
---|---|---|---|
JsonPath |
String | Yes | Path to JSON file created by export script |
WhatIf |
Switch | No | Preview changes without applying them |
Credential |
PSCredential | No | Exchange Online credentials (uses interactive auth if not provided) |
TenantId |
String | No | Azure AD Tenant ID for multi-tenant environments |
Usage Examples
Basic Import Operations
# Basic import .\Import-MailboxAliases.ps1 -JsonPath .\mailbox-addresses.json# Preview mode (recommended first step) .\Import-MailboxAliases.ps1 -JsonPath .\mailbox-addresses.json -WhatIf
# Import with progress monitoring
.\Import-MailboxAliases.ps1 -JsonPath .\sales-export.json -Verbose
Multi-Tenant and Credential Scenarios
# Specific tenant .\Import-MailboxAliases.ps1 -JsonPath .\export.json -TenantId "12345678-1234-1234-1234-123456789012"# With stored credentials $cred = Get-Credential -Message "Enter Exchange Online Admin Credentials" .\Import-MailboxAliases.ps1 -JsonPath .\export.json -Credential $cred
# Service principal authentication
$appCred = New-Object System.Management.Automation.PSCredential(“app-id”, $secureSecret)
.\Import-MailboxAliases.ps1 -JsonPath .\export.json -Credential $appCred -TenantId “tenant-id”
Advanced Usage Examples
Example 1: Phased Department Migration
Migrate departments one at a time to minimize risk and allow for validation between phases.
# Phase 1: Sales Department .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Sales'" -OutputPath ".\sales-phase1.json"# Review sales data $salesData = Get-Content .\sales-phase1.json | ConvertFrom-Json Write-Host "Sales users to migrate: $($salesData.Mailboxes.Count)" $salesData.Mailboxes | Select DisplayName, PrimarySMTPAddress | Format-Table
# Test import
.\Import-MailboxAliases.ps1 -JsonPath .\sales-phase1.json -WhatIf
# Execute import
.\Import-MailboxAliases.ps1 -JsonPath .\sales-phase1.json
# Phase 2: Marketing Department
.\Export-OnPremiseMailboxes.ps1 -Filter “Department -eq ‘Marketing'” -OutputPath “.\marketing-phase2.json”
.\Import-MailboxAliases.ps1 -JsonPath .\marketing-phase2.json -WhatIf
.\Import-MailboxAliases.ps1 -JsonPath .\marketing-phase2.json
# Continue with additional departments…
Example 2: VIP User Priority Migration
Prioritize executive and VIP users for early migration to ensure business continuity.
.\Export-OnPremiseMailboxes.ps1 -Filter “Title -like ‘*Director*’ -or Title -like ‘*VP*’ -or Title -like ‘*CEO*’ -or Title -like ‘*President*'” -OutputPath “.\vip-users.json”# Verify VIP list
$vipData = Get-Content .\vip-users.json | ConvertFrom-Json
Write-Host “VIP users identified: $($vipData.Mailboxes.Count)”
$vipData.Mailboxes | Select DisplayName, Title, PrimarySMTPAddress, @{Name=”Aliases”;Expression={$_.EmailAliases.Count}} | Format-Table# Migrate VIP users with extra caution
.\Import-MailboxAliases.ps1 -JsonPath .\vip-users.json -WhatIf
Read-Host “Press Enter to proceed with VIP migration”
.\Import-MailboxAliases.ps1 -JsonPath .\vip-users.json
Example 3: Database-by-Database Migration
Process each mailbox database separately for better control and monitoring.
# Get list of all databases $databases = Get-MailboxDatabase | Select-Object Name, ServerWrite-Host "Found $($databases.Count) mailbox databases:" $databases | Format-Table
foreach ($db in $databases) {
$outputFile = “.\export-$($db.Name -replace ‘ ‘,’-‘ -replace ‘[^a-zA-Z0-9-]’,”).json”
Write-Host “Processing database: $($db.Name)” -ForegroundColor Green
# Export database
.\Export-OnPremiseMailboxes.ps1 -Database $db.Name -OutputPath $outputFile
# Review export
$dbData = Get-Content $outputFile | ConvertFrom-Json
Write-Host ” Exported $($dbData.Mailboxes.Count) mailboxes”
# Test import
Write-Host ” Testing import…” -ForegroundColor Yellow
.\Import-MailboxAliases.ps1 -JsonPath $outputFile -WhatIf
# Prompt for confirmation
$confirm = Read-Host ” Proceed with import for $($db.Name)? (Y/N)”
if ($confirm -eq ‘Y’ -or $confirm -eq ‘y’) {
Write-Host ” Importing aliases…” -ForegroundColor Green
.\Import-MailboxAliases.ps1 -JsonPath $outputFile
Write-Host ” Completed: $($db.Name)” -ForegroundColor Green
} else {
Write-Host ” Skipped: $($db.Name)” -ForegroundColor Yellow
}
Write-Host “”
}
Example 4: Selective Alias Migration
Filter and migrate only mailboxes that have aliases, reducing processing time.
# Export all mailboxes .\Export-OnPremiseMailboxes.ps1 -OutputPath ".\all-mailboxes.json"# Filter to only include mailboxes with aliases $allData = Get-Content .\all-mailboxes.json | ConvertFrom-Json $filteredMailboxes = $allData.Mailboxes | Where-Object { $_.EmailAliases.Count -gt 0 }
Write-Host “Original mailboxes: $($allData.Mailboxes.Count)”
Write-Host “Mailboxes with aliases: $($filteredMailboxes.Count)”
# Create filtered export
$filteredData = @{
ExportInfo = $allData.ExportInfo
Mailboxes = $filteredMailboxes
}
$filteredData.ExportInfo.TotalMailboxes = $filteredMailboxes.Count
$filteredData.ExportInfo.Filter = “Mailboxes with aliases only”
$filteredData | ConvertTo-Json -Depth 10 | Out-File .\filtered-mailboxes.json
# Import filtered data
.\Import-MailboxAliases.ps1 -JsonPath .\filtered-mailboxes.json -WhatIf
.\Import-MailboxAliases.ps1 -JsonPath .\filtered-mailboxes.json
Troubleshooting Guide
Exchange Connection Issues
Solutions:
# Test basic connectivity Test-NetConnection -ComputerName "your-exchange-server.domain.com" -Port 80 Test-NetConnection -ComputerName "your-exchange-server.domain.com" -Port 443# Verify Exchange Management Shell Get-PSSession | Where-Object {$_.ConfigurationName -eq "Microsoft.Exchange"}
# Test Exchange cmdlets
Get-ExchangeServer
Get-MailboxDatabase | Select Name, Server
# Try explicit server connection
.\Export-OnPremiseMailboxes.ps1 -Server “EXCH01.contoso.com”
# Use alternative credentials
$cred = Get-Credential -Message “Enter Exchange Admin Credentials”
.\Export-OnPremiseMailboxes.ps1 -Credential $cred
Exchange Online Module Issues
Solutions:
# Check current module version Get-Module -ListAvailable -Name ExchangeOnlineManagement# Uninstall and reinstall module Uninstall-Module -Name ExchangeOnlineManagement -Force -AllVersions Install-Module -Name ExchangeOnlineManagement -Force -AllowClobber
# Clear cached credentials
Disconnect-ExchangeOnline -Confirm:$false
# Manual connection test
Connect-ExchangeOnline -ShowProgress $true
# Check for multiple versions
Get-Module -ListAvailable -Name ExchangeOnlineManagement | Format-Table Name, Version, Path
Mailbox Matching Problems
Solutions:
# Verify mailbox exists with different address format Get-Mailbox -Identity "[email protected]" -ErrorAction SilentlyContinue# Check accepted domains in Exchange Online Get-AcceptedDomain | Format-Table Name, DomainName, DomainType
# List sample mailboxes for comparison
Get-Mailbox -ResultSize 10 | Select DisplayName, PrimarySmtpAddress, EmailAddresses
# Check for address policy differences
Get-EmailAddressPolicy | Format-Table Name, EnabledEmailAddressTemplates
# Verify domain routing
Get-RemoteDomain | Format-Table DomainName, IsInternal
Permission and Access Issues
Solutions:
# Check current user permissions in Exchange Online Get-RoleGroupMember "Organization Management" | Where-Object {$_.Name -like "*$env:USERNAME*"}# Verify Exchange Online admin roles Get-MsolRole | Where-Object {$_.Name -like "*Exchange*"} Get-MsolRoleMember -RoleObjectId (Get-MsolRole -RoleName "Exchange Service Administrator").ObjectId
# Test with Global Admin account
$globalAdminCred = Get-Credential -Message “Enter Global Admin Credentials”
.\Import-MailboxAliases.ps1 -JsonPath .\export.json -Credential $globalAdminCred
# Check on-premise Exchange permissions
Get-ManagementRoleAssignment -RoleAssignee $env:USERNAME | Select Role, RoleAssigneeName
Performance and Scale Issues
Solutions:
# Process in smaller batches $databases = Get-MailboxDatabase foreach ($db in $databases) { .\Export-OnPremiseMailboxes.ps1 -Database $db.Name -OutputPath ".\batch-$($db.Name -replace ' ','-').json" }# Use more specific filters to reduce dataset size .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Sales' -and Office -eq 'NYC'" # Monitor memory usage during execution Get-Process PowerShell | Select ProcessName, WorkingSet, VirtualMemorySize # Check for Exchange Online throttling # Look for "Request was throttled" messages in output # Implement delays between operations Start-Sleep -Seconds 2 # Add between batches
JSON File Issues
Solutions:
# Validate JSON file structure try { $testData = Get-Content .\mailbox-addresses.json | ConvertFrom-Json Write-Host "JSON file is valid. Contains $($testData.Mailboxes.Count) mailboxes." } catch { Write-Error "JSON file is invalid: $($_.Exception.Message)" }# Check file encoding Get-Content .\mailbox-addresses.json -Encoding UTF8 | Select-Object -First 5 # Repair JSON file if needed $content = Get-Content .\mailbox-addresses.json -Raw $content = $content -replace '[^\x20-\x7E\x0A\x0D]', '' # Remove non-printable characters $content | Out-File .\mailbox-addresses-fixed.json -Encoding UTF8 # Verify file size and structure Get-ChildItem .\mailbox-addresses.json | Select Name, Length
Best Practices
Pre-Migration Planning
Environment Assessment
- Document current Exchange topology
- Identify all accepted domains
- Map organizational units and databases
- Review existing alias patterns
- Identify VIP users and critical mailboxes
Pilot Testing
- Start with 5-10 test users
- Test different mailbox types (regular, shared, distribution)
- Verify alias functionality after migration
- Document any issues encountered
- Validate email delivery to migrated aliases
Backup Strategy
- Export all mailbox data before starting
- Keep multiple copies of JSON files
- Document current Exchange Online configuration
- Plan rollback procedures
- Archive migration logs and reports
Migration Execution
Phased Approach
- Migrate by department or business unit
- Process VIP users first
- Allow time between phases for validation
- Monitor Exchange Online service health
Monitoring and Validation
- Use WhatIf mode extensively
- Spot-check migrated aliases
- Test email delivery to new aliases
- Monitor for any delivery issues
Communication
- Notify users of migration schedule
- Provide timeline for alias availability
- Document any temporary limitations
- Establish support procedures
Security Considerations
Security Best Practices:
- Credential Management: Use secure credential storage, implement least-privilege access
- Data Protection: Encrypt JSON files, limit access to migration files, use secure transfer methods
- Audit and Compliance: Log all activities, maintain audit trails, ensure regulatory compliance
- Access Control: Limit script access to authorized personnel, monitor admin account usage
Performance Optimization
# Optimize for large environments # 1. Use specific filters to reduce dataset size .\Export-OnPremiseMailboxes.ps1 -Filter "Department -eq 'Sales'" -OutputPath ".\sales-only.json"# 2. Process during off-peak hours $currentHour = (Get-Date).Hour if ($currentHour -lt 6 -or $currentHour -gt 22) { Write-Host "Running during off-peak hours" -ForegroundColor Green # Run migration } else { Write-Warning "Consider running during off-peak hours (10 PM - 6 AM)" }
# 3. Implement batch processing with delays
$batchSize = 50
$mailboxes = $data.Mailboxes
for ($i = 0; $i -lt $mailboxes.Count; $i += $batchSize) {
$batch = $mailboxes[$i..($i + $batchSize – 1)]
# Process batch
Start-Sleep -Seconds 30 # Delay between batches
}
# 4. Monitor throttling and adjust accordingly
# Watch for “Request was throttled” messages and increase delays
Key Takeaways
- Always test first: Use WhatIf mode and start with small pilot groups
- Plan in phases: Don’t migrate everything at once – use a phased approach
- Monitor closely: Watch for errors, throttling, and delivery issues
- Keep backups: Maintain copies of all export files and migration logs
- Validate results: Test email delivery to migrated aliases
⚠️ Important Disclaimers
- Testing Required: Always test in a non-production environment first
- Additive Only: These scripts only ADD aliases, they never remove existing ones
- Backup Essential: Keep backups of JSON export files and current configurations
- Service Limits: Monitor Exchange Online throttling and service limits
- Community Support: This is a community-supported project, not an official Microsoft tool