Exchange Online Migration Reporting

Getting requests for reporting while doing migrations is nothing new. While working with a customer, I wrote some Powershell scripts to automate making emailed reports. These looked great when I first passed them around that day to get feedback. I almost put my initial reports (with some very nicely formatted tables) into production, but I looked at them in the evening with my smartphone. These reports were extremely hard to read as they were all formatted for a desktop email client. Given that the customer wanted these as hourly status reports, these reports were more likely to be consumed via smartphone than using the full Outlook client. The tables had to go.

The next morning, I rebuilt the scripts, with formatting optimized for a smartphone.

A later optimization I made (tens of thousands of mailboxes later), was to add a section for any mailboxes that got into a queue at the Office 365 tenant’s MRS servers. This rarely happens, but putting this in the report make this really easy to find and explain when it happens. What you would see is a difference between the get-migrationuser and get-moverequest counts. Digging down into the get-moverequeststatics, you’d see a queue length if this is happening. Putting this into the report gives you a great idea of what’s going on at 3AM when you really don’t want to be running Powershell scripts by hand. (Well, maybe scripting guy Ed Wilson does, but nobody else!)

So, finally, here’s the code:

#migration-report.ps1 2014-02-22

$Date = Get-Date


Import-Module MSOnline

#Get password value and make it into a securestring – adjust for proper location.

$newString = gc g:\powershell_scripts\provisioning\encrypted.txt | ConvertTo-SecureString


$Livecred = new-object -typename System.Management.Automation.PSCredential -argumentlist "",$newString


Connect-MsolService -Credential $LiveCred


$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri -Credential $LiveCred -Authentication Basic -AllowRedirection

Import-PSSession $Session


$Subject = 'Office 365 Migration progress at ' + $Date

$Subject > $Body

$Separator = '========================'


$Separator >> $Body

$migusers = Get-MigrationUser -Resultsize 'Unlimited'

$Batchname = 'Office 365 Migration Progress Report'

$Batchname >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Completed'}).Count

$mailboxessynced = 'Mailboxes Completed: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'CompletedWithWarning'}).Count

$mailboxessynced = 'Mailboxes Completed with Warning: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'CompletionInProgress'}).Count

$mailboxessynced = 'Mailboxes Completing: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Synced'}).Count

$mailboxessynced = 'Mailboxes Synced: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Syncing'}).Count

$mailboxessynced = 'Mailboxes Syncing: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Queued'}).Count

$mailboxessynced = 'Mailboxes Queued: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Validating'}).Count

$mailboxessynced = 'Mailboxes Validating: ' + $mailboxes

$mailboxessynced >> $Body

$mailboxes = ($migusers | ? {$_.Status -eq 'Failed'}).Count

$mailboxessynced = 'Mailboxes Failed: ' + $mailboxes

$mailboxessynced >> $Body

$separator >> $Body

echo "Detailed Migration Batch Status:" >> $Body

Get-MigrationBatch | Select Identity,Status,StoppedCount,SyncedCount,ActiveCount,FinalizedCount,FailedCount,TotalCount | fl >> $Body

$Separator >> $Body

#Do we have any move requests that are queued? Let's find these and report on them.

echo "Detailed Move Request Queue Information:" >> $Body

Get-MoveRequest -ResultSize 'unlimited' -MoveStatus 'Queued' | Get-MoveRequestStatistics | Sort -Property @{Expression="BatchName";Ascending=$true},@{Expression="DisplayName";Ascending=$true} | Select DisplayName,BatchName,RemoteHostname,TotalQueuedDuration,PositionInQueue >> $Body

$Separator >> $Body


#Module to send mail. Need $Date, $Subject, and $Body from above.

$EmailFrom = ""

$EmailTo = ""


#No attachments here - this report is optimized for mobile devices

$Msg = New-Object System.Net.Mail.MailMessage $EmailFrom,$EmailTo,$Subject,$Body


#Adjust for your servername and port for relaying email.

$SMTPServer = "internal.smtp.server"

$SMTPClient = New-Object Net.Mail.SmtpClient($SmtpServer, 587)         


#Use these if you have to authenticate to relay email on your internal SMTP server.

#$SMTPClient.EnableSsl = $true

#$SMTPClient.Credentials = $Ecreds     


#Finally. Send the report already!



Oh, about that encrypted.txt file for the password. There’s another blog posting on how to create that. If you’re even thinking about putting a clear-text password in a script, please stop and read this now!

As with all scripts, please test this first in your development environment. Scripts shouldn’t just be dropped onto your production systems without testing first.

Please don’t hesitate to leave a comment if you have any questions on this or can suggest some enhancements.