Skip to main content

The PowerShell Runbook We Reach For on Every Email Migration

Nick Ross1 min read
Best Powershell Scripts for Migrations

TL;DR

  • Email migrations can take weeks, so keeping a PowerShell runbook for both the source and destination environment saves hours of repeated manual work.
  • Connecting to Exchange Online, bulk-importing users with passwords, and bulk-updating UPNs are the scripts you run first on the destination tenant.
  • Disabling the Exchange throttling policy and setting ApplicationImpersonation on the admin account are the source-side scripts that unblock a third-party migration tool.
  • Bulk scripts cover distribution lists with members, shared mailboxes, mailbox permissions, alias add and remove, and mailbox send/receive size changes.
  • Get-mailbox piped to get-mailboxstatistics reports every user's mailbox size so you can scope the migration before it starts.
PowerShell migration scripts

Email migrations can run for weeks, and the way we claw back time is by keeping a PowerShell runbook for both the source and the destination environment. Below is the set of scripts we reach for most often during a migration. Treat it as a copy-and-adapt reference: swap in your own domains, file paths, and admin accounts. If you have a favorite that is not here, we want to hear it.

Connect to an Exchange Online account

The session every other destination-side script depends on:

powershell
$credential = Get-Credential

Import-Module MsOnline
Connect-MsolService -Credential $credential

$exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" -AllowRedirection
Import-PSSession $exchangeSession -DisableNameChecking

Bulk import users with passwords into 365

Create a CSV with the headers: UserPrincipalName, FirstName, LastName, DisplayName, Password.

text
Import-Csv -Path 'FilePath.csv' | foreach {New-MsolUser -UserPrincipalName $_.UserPrincipalName -FirstName $_.FirstName -LastName $_.LastName -DisplayName $_.DisplayName -Password $_.Password -ForceChangePassword $False}

Bulk update UPNs in 365

Moves users from the .onmicrosoft.com domain to the real domain, skipping the admin account:

powershell
$UserCredential = Get-Credential
Connect-MsolService -Credential $UserCredential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session -AllowClobber
$testpath = test-path c:\temp; If ($testpath -eq $false) {new-item -type directory c:\temp}; $dataout = @();
Get-MsolUser -All | ? {$_.UserPrincipalName -match "domain.onmicrosoft.com" -and $_.UserPrincipalName -notmatch "admin"} | % {Set-MsolUserPrincipalName -ObjectId $_.objectId -NewUserPrincipalName ($_.UserPrincipalName.Split("@")[0] + "@domain.com"); $dataout += "$($_.UserPrincipalName)" ; $_.UserPrincipalName };$dataout | out-file c:\temp\UPNChangeOutput.txt

Add an alias to all users in 365

powershell
$LiveCred = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $LiveCred -Authentication Basic -AllowRedirection
Import-PSSession $Session
$users = Get-Mailbox
foreach ($a in $users) {$a.emailaddresses.Add("$($a.alias)@domain.com")
$users | %{Set-Mailbox $_.Identity -EmailAddresses $_.EmailAddresses}

Bulk upload distribution lists with members

Create two CSVs: one with the distribution group name and primary SMTP address, and one with the group name and the members of that group.

text
Import-Csv -Path 'FilePath.csv' | foreach {New-Distributiongroup -Name $_.Name -PrimarySmtpAddress $_.Address }

Import-Csv 'FilePath.csv' | foreach {Add-DistributionGroupMember -Identity $_.DL -Member $_.Alias}

Bulk upload shared mailboxes

Create a CSV with the headers: DisplayName, Userprincipalname.

text
Import-Csv -Path 'FilePath.csv' | foreach-object { New-Mailbox -Name $_.DisplayName -primarysmtpaddress $_.Userprincipalname -Shared }

Bulk upload mailbox permissions

Create a CSV with the headers: Identity (the person you are giving permissions to, format user@domain.com) and User (the person whose mailbox the Identity user will be able to view).

text
Import-Csv -Path 'FilePath.csv' | foreach {Add-MailboxPermission -Identity $_.Identity -User $_.User -AccessRights FullAccess -InheritanceType All}

View users' mailbox size in 365/Exchange

powershell
$UserCredential = Get-Credential

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection

Import-PSSession $Session


get-mailbox | get-mailboxstatistics | ft displayname, totalitemsize

Disable the throttling policy on Exchange

powershell
New-ThrottlingPolicy MigrationPolicy

Set-ThrottlingPolicy MigrationPolicy -RCAMaxConcurrency $null -RCAPercentTimeInAD $null -RCAPercentTimeInCAS $null -RCAPercentTimeInMailboxRPC $null -EWSMaxConcurrency $null -EWSPercentTimeInAD $null -EWSPercentTimeInCAS $null -EWSPercentTimeInMailboxRPC $null -EWSMaxSubscriptions $null -EWSFastSearchTimeoutInSeconds $null -EWSFindCountLimit $null -CPAMaxConcurrency $null -CPAPercentTimeInCAS $null -CPAPercentTimeInMailboxRPC $null -CPUStartPercent $null

Set-Mailbox "<Admin User>" -ThrottlingPolicy MigrationPolicy

Set impersonation on Exchange/365

powershell
Set-ExecutionPolicy Unrestricted

$LiveCred = Get-Credential

$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic -AllowRedirection

Import-PSSession $Session

Enable-OrganizationCustomization

New-ManagementRoleAssignment -Role "ApplicationImpersonation" -User admin@domain.com

Change mailbox receive/send size in 365

powershell
$credential = Get-Credential

Import-Module MsOnline
Connect-MsolService -Credential $credential

$exchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://outlook.office365.com/powershell-liveid/" -Credential $credential -Authentication "Basic" -AllowRedirection
Import-PSSession $exchangeSession -DisableNameChecking

Get-Mailbox | Set-Mailbox -MaxReceiveSize 150MB -MaxSendSize 150MB

Remove an alias from users

powershell
$Records = Get-mailbox -ResultSize Unlimited| where {$_.emailaddresses -like "smtp:*@domain.com"} | Select-Object DisplayName,@{Name="EmailAddresses";Expression={$_.EmailAddresses |Where-Object {$_ -like "smtp:*domain.com"}}}

foreach ($record in $Records)
{
write-host "Removing Alias" $record.EmailAddresses "for" $record.DisplayName
Set-Mailbox $record.DisplayName -EmailAddresses @{Remove=$record.EmailAddresses}
}

Remove groups with a certain domain from 365

powershell
Get-MsolGroup -all | ?{$_.emailaddress -match "domain.com"} | Remove-MsolGroup -force

Frequently asked questions

Why disable the throttling policy before a migration?

Exchange throttling policies cap concurrent connections, which slows or stalls a migration tool pulling many mailboxes at once. Creating a MigrationPolicy with the limits set to null and assigning it to the admin account removes that ceiling for the duration of the move.

What does ApplicationImpersonation do for a migration?

It lets a single admin account access all user mailboxes without collecting credentials for each one, which is how migration tools read source mailboxes. New-ManagementRoleAssignment -Role ApplicationImpersonation grants it.

How do I check mailbox sizes before migrating?

Connect to Exchange Online and run get-mailbox | get-mailboxstatistics | ft displayname, totalitemsize. This gives you a per-user size list so you can scope bandwidth and timeline before kicking anything off.

From migration scripts to a standing security baseline

PowerShell runbooks move the data. Keeping the destination tenant secure afterward is the recurring job. CloudCapsule checks 250+ controls across every tenant you manage in about 60 seconds each, no scripts to maintain.

Run a free scan
Nick Ross

Written by

Nick Ross

CEO · Microsoft MVP · Founder, T-Minus 365

Nick is not just a CEO, he's a respected thought leader and influencer in the MSP space. Tens of thousands of MSPs learn through his YouTube channel, T-Minus365. Nick has been honored as a three-time Microsoft MVP for his educational content; his expertise and influence are the backbone of our mission, ensuring that you are in the best hands when it comes to security.

Nick joined Pax8 in 2017, where he would ultimately oversee product management for PSA and Microsoft integrations. Following his tenure at Pax8, Nick has continued to demonstrate his leadership prowess as an executive at various MSPs, culminating in his most recent role at Sourcepass.

Nick holds a Bachelor's Degree in Business Management from Florida State University, as well as a Minor Degree in Entrepreneurship. In his free time, Nick is an avid hiker, reader, and fitness-junkie.

Keep reading