Exchange 2010 Migration Script Part 2

Do you need an automated process to migrate from a legacy Exchange environment to Exchange 2010?  This article is step two of a two step migration process that involves separate PowerShell scripts to accomplish an Exchange 2010 migration.  This is the second script  that resumes the migration of legacy Exchange mailboxes and distributes them evenly across existing Exchange 2010 databases.  The bulk of this script deals with reporting the results of the migration for later review and analysis.  I’ll break down the script in detail so that you can understand what is happening.   (Part 1 can be found here)

The script  executes the following:

  1. Selects the pending move requests based on a batch name.
  2. Verifies that status of move requests are acceptable and then resumes the migration.
  3. Waits until all resumed migrations are completed or completed with warnings before starting to build a report.
  4. Creates a report about all the mailboxes moved.
  5. Writes report to CSV and sends the report via email.



Add the Exchange 2010 PowerShell snapin just in case the script is run as a scheduled task.

[powershell]
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction "SilentlyContinue"
[/powershell]

Set some of the variables to be used later in the script.  Create a batch name that is the same name as your source distribution group for tracking purposes.  Gather all the users in the batched migration and set the path for writing log files.

[powershell]
$BatchName = "Group01"
$Batchedusers = Get-MoveRequest -resultsize unlimited -BatchName $BatchName
$LogPath = "C:\MigrationLogs"
[/powershell]

Bump out any existing CSV files from previous migrations of the same day and move them to a sub folder named “archive”.

[powershell]
Get-ChildItem $LogPath -Filter *.CSV | Move-Item -Destination $LogPath\archive
[/powershell]

This next block will pause the script by continually checking the status of the move requests by looping until all the move request status are not equal to autosuspended or failed.  This is a counter intuitive loop but essentially as the new move requests process, their status will change to either failed or autosuspended.  Evenually the variable $MoveRequestStatusAutoSuspended will empty of objects and become null.  Once this happens the script continues.

[powershell]
$MoveRequestStatusAutoSuspended = Get-MoveRequest -resultsize unlimited -BatchName $BatchName
| Where-Object {$_.Status -ne "Failed" -and $_.Status -ne "AutoSuspended"}

If ($MoveRequestStatusAutoSuspended -ne $Null) {
Do {
Start-Sleep -seconds 30
$MoveRequestStatusAutoSuspended = Get-MoveRequest -resultsize unlimited

-BatchName $BatchName | Where-Object {$_.Status -ne "AutoSuspended"}
| where-object {$_.Status -ne "Failed"}
}
Until ($Null -eq $MoveRequestStatusAutoSuspended)
}Else{
}
[/powershell]

Once the move requests have reached the state of autosuspended or failed the move requests are resumed.

[powershell]
Get-MoveRequest -resultsize unlimited -BatchName $BatchName -MoveStatus "AutoSuspended" | Resume-MoveRequest
[/powershell]

With the move requests resumed and finalizing the script loops until all the mailboxes contain a status of either completed or completedwithwarning.

[powershell]
Do {
Get-MoveRequest -resultsize unlimited -BatchName $BatchName
Start-Sleep -seconds 30
$MoveRequestStatusCompleted = Get-MoveRequest -resultsize unlimited -BatchName $BatchName

| Where-Object {$_.Status -ne "completed" -and $_.Status -ne "CompletedWithWarning"}
}
Until ($Null -eq $MoveRequestStatusCompleted)
[/powershell]

Throw in a little bit of command line notification and a sleep timer to give Active Directory a breather before tying to generate a report.

[powershell]
Write-Host -ForegroundColor Green "All jobs have reported a Completed status!"
Start-Sleep -Seconds 60
[/powershell]

Now build a massive data table to populate data from the migration.

[powershell]
$MoveResults = New-Object system.Data.DataTable “MoveResults”
$MoveResults.Columns.Add("Status",[String]) | Out-Null
$MoveResults.Columns.Add("Mailbox",[String]) | Out-Null
$MoveResults.Columns.Add("PrimarySMTPAddress",[String]) | Out-Null
$MoveResults.Columns.Add("TargetDatabase",[String]) | Out-Null
$MoveResults.Columns.Add("ErrorCode",[String]) | Out-Null
$MoveResults.Columns.Add("MailboxSizeMB",[String]) | Out-Null
$MoveResults.Columns.Add("ItemCount",[String]) | Out-Null
$MoveResults.Columns.Add("StartTime",[String]) | Out-Null
$MoveResults.Columns.Add("SeedCompleted",[String]) | Out-Null
$MoveResults.Columns.Add("QueuedTime",[String]) | Out-Null
$MoveResults.Columns.Add("FinalSyncTime",[String]) | Out-Null
$MoveResults.Columns.Add("CompletionTime",[String]) | Out-Null
$MoveResults.Columns.Add("OverallDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalFinalizationDuration",[String]) | Out-Null         �
$MoveResults.Columns.Add("TotalSuspendedDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalQueuedDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalInProgressDuration",[String]) | Out-Null
$MoveResults.Columns.Add("MRSServerName",[String]) | Out-Null
$MoveResults.Columns.Add("BadItemsEncountered",[String]) | Out-Null
[/powershell]

Now process each migrated mailbox by using Get-Mailbox and Get-MailboxStatistics with the -IncludeMoveHistory switch.  A little fixup is needed on the TotalMailboxSize attribute as it contains a string that is difficult to deal with in a CSV file.

[powershell]
ForEach ($BatchedUser in $BatchedUsers){

$BatchedUserMailbox = $BatchedUser | get-mailbox | select DisplayName,PrimarySMTPAddress
$BatchUserSMTP =  $BatchedUserMailbox.primarysmtpaddress.tostring()
$BatchedUserMoveHistory = (Get-MailboxStatistics -Identity $BatchUserSMTP -IncludeMoveHistory).MoveHistory | select -first 1

$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistory.TotalMailboxSize -replace (".*\(","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize -replace (" bytes\)","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize -replace (",","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize /1024 /1024
$BatchedUserMoveHistoryMBsize = [math]::round($batcheduserMovehistoryMBsize,2)

$NewDTRow = $MoveResults.NewRow()
$NewDTRow.Mailbox = $BatchedUserMailbox.DisplayName
$NewDTRow.Status = $BatchedUserMoveHistory.Status
$NewDTRow.PrimarySMTPAddress = $BatchedUserMailbox.PrimarySmtpAddress
$NewDTRow.TargetDatabase = $BatchedUserMoveHistory.TargetDatabase
$NewDTRow.ErrorCode = $BatchedUserMoveHistory.FailureCode
$NewDTRow.MailboxSizeMB = $BatchedUserMoveHistoryMBsize
$NewDTRow.ItemCount = $BatchedUserMoveHistory.TotalMailboxItemCount
$NewDTRow.QueuedTime = $BatchedUserMoveHistory.QueuedTimestamp
$NewDTRow.StartTime = $BatchedUserMoveHistory.StartTimeStamp
$NewDTRow.SeedCompleted = $BatchedUserMoveHistory.InitialSeedingCompletedTimestamp
$NewDTRow.FinalSyncTime = $BatchedUserMoveHistory.FinalSyncTimestamp
$NewDTRow.CompletionTime = $BatchedUserMoveHistory.CompletionTimestamp
$NewDTRow.OverallDuration = $BatchedUserMoveHistory.OverallDuration
$NewDTRow.TotalFinalizationDuration = $BatchedUserMoveHistory.TotalFinalizationDuration
$NewDTRow.TotalSuspendedDuration = $BatchedUserMoveHistory.TotalSuspendedDuration
$NewDTRow.TotalQueuedDuration = $BatchedUserMoveHistory.TotalQueuedDuration
$NewDTRow.TotalInProgressDuration = $BatchedUserMoveHistory.TotalInProgressDuration
$NewDTRow.MRSServerName = $BatchedUserMoveHistory.MRSServerName
$NewDTRow.BadItemsEncountered = $BatchedUserMoveHistory.BadItemsEncountered
$MoveResults.Rows.Add($NewDTRow)
}
[/powershell]

Grab the current date and time after the migration work is completed.  The date and time will be used in the email report.  Then create a CSV file and dump the data table into it.

[powershell]
$DateTime = Get-Date -Format "ddd MM/dd/yyyy h:mm tt"

$CSVReport = $LogPath + "\MailboxMove" + (Get-Date -Format "MM-dd-yyyy.h.mmtt") + ".CSV"
$MoveResults | Export-Csv -NoTypeInformation -NoClobber $CSVReport

[/powershell]

We can build in some logic for the email subject and body using the following code.

[powershell]
$EmailSubject = "Mailbox migration for group $BatchName completed at $DateTime."

$ErrorState = "False"
$MoveResults | ForEach {
If ($_.ErrorCode -match "-."){
$ErrorState = "True"
}else{}
}

If ($ErrorState -eq "True") {
$EmailBody = "There was one or more errors in the migration, please check the attached CSV."
} Else {
$EmailBody = "There were no errors in the migration."
}
[/powershell]

Send that email about the successful migration!

[powershell]
Send-MailMessage -Subject $EmailSubject -From ""
-To "" -Body $EmailBody -Attachments $CSVReport -SmtpServer ""
[/powershell]

The entire script is here

[powershell collapse="true" gutter="false"]
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction "SilentlyContinue"
$BatchName = "Group01"
$Batchedusers = Get-MoveRequest -resultsize unlimited -BatchName $BatchName
$LogPath = "C:\MigrationLogs"
Get-ChildItem $LogPath -Filter *.CSV | Move-Item -Destination $LogPath\archive
$MoveRequestStatusAutoSuspended = Get-MoveRequest -resultsize unlimited -BatchName $BatchName

| Where-Object {$_.Status -ne "Failed" -and $_.Status -ne "AutoSuspended"}

If ($MoveRequestStatusAutoSuspended -ne $Null) {
Do {
Start-Sleep -seconds 30
$MoveRequestStatusAutoSuspended = Get-MoveRequest -resultsize unlimited
-BatchName $BatchName | Where-Object {$_.Status -ne "AutoSuspended"}

| where-object {$_.Status -ne "Failed"}
}
Until ($Null -eq $MoveRequestStatusAutoSuspended)
}Else{
}
Get-MoveRequest -resultsize unlimited -BatchName $BatchName -MoveStatus "AutoSuspended" | Resume-MoveRequest
Do {
Get-MoveRequest -resultsize unlimited -BatchName $BatchName
Start-Sleep -seconds 30
$MoveRequestStatusCompleted = Get-MoveRequest -resultsize unlimited -BatchName $BatchName
| Where-Object {$_.Status -ne "completed" -and $_.Status -ne "CompletedWithWarning"}
}
Until ($Null -eq $MoveRequestStatusCompleted)
Write-Host -ForegroundColor Green "All jobs have reported a Completed status!"
Start-Sleep -Seconds 60
$MoveResults = New-Object system.Data.DataTable “MoveResults”
$MoveResults.Columns.Add("Status",[String]) | Out-Null
$MoveResults.Columns.Add("Mailbox",[String]) | Out-Null
$MoveResults.Columns.Add("PrimarySMTPAddress",[String]) | Out-Null
$MoveResults.Columns.Add("TargetDatabase",[String]) | Out-Null
$MoveResults.Columns.Add("ErrorCode",[String]) | Out-Null
$MoveResults.Columns.Add("MailboxSizeMB",[String]) | Out-Null
$MoveResults.Columns.Add("ItemCount",[String]) | Out-Null
$MoveResults.Columns.Add("StartTime",[String]) | Out-Null
$MoveResults.Columns.Add("SeedCompleted",[String]) | Out-Null
$MoveResults.Columns.Add("QueuedTime",[String]) | Out-Null
$MoveResults.Columns.Add("FinalSyncTime",[String]) | Out-Null
$MoveResults.Columns.Add("CompletionTime",[String]) | Out-Null
$MoveResults.Columns.Add("OverallDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalFinalizationDuration",[String]) | Out-Null         �
$MoveResults.Columns.Add("TotalSuspendedDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalQueuedDuration",[String]) | Out-Null
$MoveResults.Columns.Add("TotalInProgressDuration",[String]) | Out-Null
$MoveResults.Columns.Add("MRSServerName",[String]) | Out-Null
$MoveResults.Columns.Add("BadItemsEncountered",[String]) | Out-Null
ForEach ($BatchedUser in $BatchedUsers){

$BatchedUserMailbox = $BatchedUser | get-mailbox | select DisplayName,PrimarySMTPAddress
$BatchUserSMTP =  $BatchedUserMailbox.primarysmtpaddress.tostring()
$BatchedUserMoveHistory = (Get-MailboxStatistics -Identity $BatchUserSMTP -IncludeMoveHistory).MoveHistory | select -first 1

$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistory.TotalMailboxSize -replace (".*\(","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize -replace (" bytes\)","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize -replace (",","")
$BatchedUserMoveHistoryMBsize = $BatchedUserMoveHistoryMBsize /1024 /1024
$BatchedUserMoveHistoryMBsize = [math]::round($batcheduserMovehistoryMBsize,2)

$NewDTRow = $MoveResults.NewRow()
$NewDTRow.Mailbox = $BatchedUserMailbox.DisplayName
$NewDTRow.Status = $BatchedUserMoveHistory.Status
$NewDTRow.PrimarySMTPAddress = $BatchedUserMailbox.PrimarySmtpAddress
$NewDTRow.TargetDatabase = $BatchedUserMoveHistory.TargetDatabase
$NewDTRow.ErrorCode = $BatchedUserMoveHistory.FailureCode
$NewDTRow.MailboxSizeMB = $BatchedUserMoveHistoryMBsize
$NewDTRow.ItemCount = $BatchedUserMoveHistory.TotalMailboxItemCount
$NewDTRow.QueuedTime = $BatchedUserMoveHistory.QueuedTimestamp
$NewDTRow.StartTime = $BatchedUserMoveHistory.StartTimeStamp
$NewDTRow.SeedCompleted = $BatchedUserMoveHistory.InitialSeedingCompletedTimestamp
$NewDTRow.FinalSyncTime = $BatchedUserMoveHistory.FinalSyncTimestamp
$NewDTRow.CompletionTime = $BatchedUserMoveHistory.CompletionTimestamp
$NewDTRow.OverallDuration = $BatchedUserMoveHistory.OverallDuration
$NewDTRow.TotalFinalizationDuration = $BatchedUserMoveHistory.TotalFinalizationDuration
$NewDTRow.TotalSuspendedDuration = $BatchedUserMoveHistory.TotalSuspendedDuration
$NewDTRow.TotalQueuedDuration = $BatchedUserMoveHistory.TotalQueuedDuration
$NewDTRow.TotalInProgressDuration = $BatchedUserMoveHistory.TotalInProgressDuration
$NewDTRow.MRSServerName = $BatchedUserMoveHistory.MRSServerName
$NewDTRow.BadItemsEncountered = $BatchedUserMoveHistory.BadItemsEncountered
$MoveResults.Rows.Add($NewDTRow)
}
$DateTime = Get-Date -Format "ddd MM/dd/yyyy h:mm tt"

$CSVReport = $LogPath + "\MailboxMove" + (Get-Date -Format "MM-dd-yyyy.h.mmtt") + ".CSV"
$MoveResults | Export-Csv -NoTypeInformation -NoClobber $CSVReport
$EmailSubject = "Mailbox migration for group $BatchName completed at $DateTime."

$ErrorState = "False"
$MoveResults | ForEach {
If ($_.ErrorCode -match "-."){
$ErrorState = "True"
}else{}
}

If ($ErrorState -eq "True") {
$EmailBody = "There was one or more errors in the migration, please check the attached CSV."
} Else {
$EmailBody = "There were no errors in the migration."
}
Send-MailMessage -Subject $EmailSubject -From ""

-To "" -Body $EmailBody -Attachments $CSVReport -SmtpServer ""
[/powershell]

1 thought on “Exchange 2010 Migration Script Part 2

Leave a Reply