Exchange 2010 Migration Script Part 1

Do you need an automated process to migrate from a legacy Exchange environment to Exchange 2010?  This article is step one of a two step migration process that involves separate PowerShell scripts to accomplish an Exchange 2010 migration.  The two part migration process will allow you to begin migrations during the work day and then cut over the mailboxes to Exchange 2010 at a convenient time.  This is the first script  that migrates legacy Exchange mailboxes evenly across existing Exchange 2010 databases.  I’ll break down the script in detail so that you can understand what is happening.  The script  executes the following:

  1. Selects a set of mailboxes to migrate based upon membership of a distribution group.
  2. Selects the Exchange 2010 databases that you want to target.
  3. Creates a data table to hold the mailboxes to be migrated.
  4. Creates a data table of target mailbox databases that will be used to select the optimal database target based on various criteria.
  5. Queries the target Exchange 2010 databases to see how the databases are populated and considers various criteria.
  6. Queries the source mailboxes and assigns them to target Exchange 2010 databases for a move request.
  7. Submits new move requests.
  8. Loops until all mailboxes are in an auto suspended state or failed state.



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]

Create a batch name that is the same name as your source distribution group for tracking purposes.  Then get the membership of your group.

[powershell]
$BatchName = "Group01"
$MovingMailboxes = Get-DistributionGroupMember -Identity $BatchName
-ResultSize unlimited | Get-Mailbox
[/powershell]

I'm using Get-MailboxDatabase to return all the Exchange databases in the Organization then then the where-object filter to sort out databases that I don't want to select for migration.  The code denotes "*DAG*" which represents Exchange 2010 databases and "*MBX*" as legacy Exchange databases.  Of course we want mounted databases and not recovery databases.

[powershell]
$TarDatabases = Get-MailboxDatabase -status | Where {($_.Name -like "*DAG*")

-and ($_.Recovery -eq $False) -and ($_.Mounted -eq "True") -and
($_.Name -notlike "*MBX*")} | Sort-Object Name
[/powershell]

Create an empty data table to be used for new move requests.  It can contain any unique mailbox attribute but I chose to use the PrimarySMTPAddress attribute.  The databasename will be the target Exchange 2010 database.

[powershell]
$MailboxData = New-Object System.Data.DataTable “MailboxData”
$MailboxData.Columns.Add("PrimarySMTPAddress",[String]) | Out-Null
$MailboxData.Columns.Add("DatabaseName",[String]) | Out-Null
[/powershell]

Create another empty data table that will be used to select the optimal migration database for each mailbox based on various criteria.  This data table will be used to hold the gathered criteria about the Exchange 2010 databases and then it will be used to keep track of the distribution of mailboxes across available Exchange 2010 databases.  This is an important part of the script if your intention is to spread out large mailboxes, Blackberry mailboxes and total mailboxes.  The criteria in this example tracks standard sized mailboxes with ActiveSTDMBs (mailbox database defaults), large sized mailboxes with ActiveLRGMBs (non-database defaults), total mailboxes per database with ActiveALLMBs, mailboxes that have blackberries with BlackberryCount (information about mailboxes with blackberries are stored in a custom attribute which is a process not covered in this post).

[powershell]
$MBXDBTable = New-Object system.Data.DataTable “MBXDatabaseTable”
$MBXDBTable.Columns.Add("DataBase") | Out-Null
$MBXDBTable.Columns["DataBase"].Unique = $true
$MBXDBTable.PrimaryKey = $MBXDBTable.Columns["DataBase"]
$MBXDBTable.Columns.Add("ActiveSTDMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("ActiveLRGMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("ActiveALLMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("BlackberryCount",[Int]) | Out-Null
[/powershell]

Now the first loop that will tally up the mailbox criteria so that the optimal target database can be selected later in the script.  The Foreach loop will process the target Exchange 2010 databases that were captured an set into the variable $TarDatabases.  Since the loop takes each database and enumerates the mailboxes on it; the process takes some time so I've added a status bar into the loop on lines 3-7.  Line 8 queries does the work to get all the mailboxes.  We set the initial counters to zero for standard mailboxes, large mailboxes and Blackberries on lines 9 through11.  A nested Foreach loop examines each mailbox returned from the Exchange 2010 database and classifies it, then increments the counters.  The data table is then updated on lines 22-28 with the values gathered.  The Foreach loop continues this process on the next Exchange 2010 mailbox database.  Finally the last line closes the status bar.

[powershell]
$LoopCount=0
Foreach ($TarDatabase in $TarDatabases) {
$PercentComplete = [Math]::Round(($LoopCount++ / $TarDatabases.Count * 100),1)
$CurrentDB = $TarDatabase.Name
Write-Progress -Activity "Mailbox Database Query in Progress" -PercentComplete

$PercentComplete -Status "$PercentComplete% Complete" -CurrentOperation
"Current Database: $CurrentDB"
$TarDBInfo = Get-Mailbox -Database $TarDatabase
$TotalActiveSTDMBs = 0
$TotalActiveLRGMBs = 0
$TotalBlackberryCount = 0
Foreach ($MBXInfo in $TarDBINfo) {
If ($MBXInfo.CustomAttribute7.Length -eq 8) {
$TotalBlackberryCount++
}
If ($MBXInfo.UseDatabaseQuotaDefaults -eq $true) {
$TotalActiveSTDMBs++
} Else {
$TotalActiveLRGMBs++
}
}
$NewDTRow = $MBXDBTable.NewRow()
$NewDTRow.DataBase = $TarDatabase
$NewDTRow.ActiveSTDMBs = $TotalActiveSTDMBs
$NewDTRow.ActiveLRGMBs = $TotalActiveLRGMBs
$NewDTRow.BlackberryCount = $TotalBlackberryCount
$NewDTRow.ActiveALLMBs = ($TotalActiveSTDMBs + $TotalActiveLRGMBs)
$MBXDBTable.Rows.Add($NewDTRow)
}
Write-Progress -Activity "Mailbox Database Query in Progress" -Completed

-Status "Completed"
[/powershell]

The second Foreach loop will take the mailboxes slated for migration, do some sorting based on criteria then make a database assignment for migration.  Within the loop the data table stored in $MBXDBTable will be incremented with the migration assignment so that the mailboxes to be migrated can be spread evenly.  The users to be migrated, stored in $MovingMailboxes is piped into the Foreach loop.  Using IF ELSE and nested IF ELSE statements each mailbox is categorized and then the optimal database is selected with Sort-Object.  The loop makes the assignment once there is a criteria match.  Line 3 evaluates the mailbox as using the the database default limits.  If it is using the defaults then the custom attribute 7 is read for an indication that the mailbox is associated with a Blackberry.  If custom attribute 7 is not equal to character a length of 8 then sort the data table by total mailboxes then by large mailboxes.  The top line after sorting will contain the database with the fewest mailboxes.  The ELSE on line 6 handles those mailboxes that have custom attribute 7 with a character length of 8.  In this case the sort object includes the BlackberryCount column in the data table.  The rest of the loop handles large mailboxes.  Line 17-20 populates the mailbox and database migration table based on selection criteria.  Finally the data table counters are incremented before the next mailbox is evaluated.

[powershell]
$MovingMailboxes | ForEach {
$MovingMailbox = Get-Mailbox $_.Identity
If ($MovingMailbox.UseDatabaseQuotaDefaults -eq $True) {
If ($MovingMailbox.CustomAttribute7.Length -ne 8) {
$OptimalDB = ($MBXDBTable | Sort-Object ActiveALLMBs,ActiveLRGMBs | Select -First 1)
} Else {
$OptimalDB = ($MBXDBTable | Sort-Object BlackberryCount,ActiveALLMBs,ActiveLRGMBs | Select -First 1)
}
} Else {
If ($MovingMailbox.CustomAttribute7.Length -ne 8) {
$OptimalDB = ($MBXDBTable | Sort-Object ActiveLRGMBs,ActiveALLMBs | Select -First 1)
} Else {
$OptimalDB = ($MBXDBTable | Sort-Object BlackberryCount,ActiveLRGMBs,ActiveALLMBs | Select -First 1)
}
}

$NewTBRow = $MailboxData.NewRow()
$NewTBRow.PrimarySMTPAddress = $MovingMailbox.PrimarySMTPAddress.ToString()
$NewTBRow.DatabaseName = $OptimalDB.database.ToString()
$MailboxData.Rows.Add($NewTBRow)

$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveALLMBs++
If ($MovingMailbox.CustomAttribute7.Length -eq 8) {
$MBXDBTable.Rows.Find($OptimalDB.Database).BlackberryCount++
}
If ($MovingMailbox.UseDatabaseQuotaDefaults -eq $true) {
$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveSTDMBs++
} Else {
$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveLRGMBs++
}
}
[/powershell]

The sorting, counting, and database migration assignments are completed so it is time to begin new move requests.  Taking the data table stored in $Mailboxdata, loop through each assignment and submit a new move request.

[powershell]
$MailboxData | ForEach {
$TargetDB = $_.DatabaseName
$ID = $_.PrimarySMTPAddress
New-MoveRequest -identity $ID -TargetDatabase $TargetDB -BadItemLimit 50
-Batchname $BatchName -SuspendWhenReadyToComplete -Confirm:$false
}
[/powershell]

This next part is optional but can be handy if you want to send some sort of notification or automate some sort of action when all the mailboxes have reached the status of failed or autosuspended.  In my case I was only concerned with waiting for mailboxes to end up in a specific state, either failed or autosuspended.

[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]

Using this script or techniques from it should help you automate your migration to Exchange 2010 and spread mailboxes evenly across your Exchange databases.  Next we need to run the second part of the migration process.  On to part 2.

Here is the entire script in one place.

[powershell gutter="false" collapse="true"]
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -ErrorAction "SilentlyContinue"
$BatchName = "Group01"
$MovingMailboxes = Get-DistributionGroupMember -Identity $BatchName

-ResultSize unlimited | Get-Mailbox

$TarDatabases = Get-MailboxDatabase -status | Where {($_.Name -like "*DAG*")
-and ($_.Recovery -eq $False) -and ($_.Mounted -eq "True") -and

($_.Name -notlike "*MBX*")} | Sort-Object Name

$MailboxData = New-Object System.Data.DataTable “MailboxData”
$MailboxData.Columns.Add("PrimarySMTPAddress",[String]) | Out-Null
$MailboxData.Columns.Add("DatabaseName",[String]) | Out-Null

$MBXDBTable = New-Object system.Data.DataTable “MBXDatabaseTable”
$MBXDBTable.Columns.Add("DataBase") | Out-Null
$MBXDBTable.Columns["DataBase"].Unique = $true
$MBXDBTable.PrimaryKey = $MBXDBTable.Columns["DataBase"]
$MBXDBTable.Columns.Add("ActiveSTDMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("ActiveLRGMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("ActiveALLMBs",[Int]) | Out-Null
$MBXDBTable.Columns.Add("BlackberryCount",[Int]) | Out-Null

$LoopCount=0
Foreach ($TarDatabase in $TarDatabases) {
$PercentComplete = [Math]::Round(($LoopCount++ / $TarDatabases.Count * 100),1)
$CurrentDB = $TarDatabase.Name
Write-Progress -Activity "Mailbox Database Query in Progress" -PercentComplete
$PercentComplete -Status "$PercentComplete% Complete" -CurrentOperation

"Current Database: $CurrentDB"
$TarDBInfo = Get-Mailbox -Database $TarDatabase
$TotalActiveSTDMBs = 0
$TotalActiveLRGMBs = 0
$TotalBlackberryCount = 0
Foreach ($MBXInfo in $TarDBINfo) {
If ($MBXInfo.CustomAttribute7.Length -eq 8) {
$TotalBlackberryCount++
}
If ($MBXInfo.UseDatabaseQuotaDefaults -eq $true) {
$TotalActiveSTDMBs++
} Else {
$TotalActiveLRGMBs++
}
}
$NewDTRow = $MBXDBTable.NewRow()
$NewDTRow.DataBase = $TarDatabase
$NewDTRow.ActiveSTDMBs = $TotalActiveSTDMBs
$NewDTRow.ActiveLRGMBs = $TotalActiveLRGMBs
$NewDTRow.BlackberryCount = $TotalBlackberryCount
$NewDTRow.ActiveALLMBs = ($TotalActiveSTDMBs + $TotalActiveLRGMBs)
$MBXDBTable.Rows.Add($NewDTRow)
}
Write-Progress -Activity "Mailbox Database Query in Progress" -Completed
-Status "Completed"

$MovingMailboxes | ForEach {
$MovingMailbox = Get-Mailbox $_.Identity
If ($MovingMailbox.UseDatabaseQuotaDefaults -eq $True) {
If ($MovingMailbox.CustomAttribute7.Length -ne 8) {
$OptimalDB = ($MBXDBTable | Sort-Object ActiveALLMBs,ActiveLRGMBs | Select -First 1)
} Else {
$OptimalDB = ($MBXDBTable | Sort-Object BlackberryCount,ActiveALLMBs,ActiveLRGMBs | Select -First 1)
}
} Else {
If ($MovingMailbox.CustomAttribute7.Length -ne 8) {
$OptimalDB = ($MBXDBTable | Sort-Object ActiveLRGMBs,ActiveALLMBs | Select -First 1)
} Else {
$OptimalDB = ($MBXDBTable | Sort-Object BlackberryCount,ActiveLRGMBs,ActiveALLMBs | Select -First 1)
}
}

$NewTBRow = $MailboxData.NewRow()
$NewTBRow.PrimarySMTPAddress = $MovingMailbox.PrimarySMTPAddress.ToString()
$NewTBRow.DatabaseName = $OptimalDB.database.ToString()
$MailboxData.Rows.Add($NewTBRow)

$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveALLMBs++
If ($MovingMailbox.CustomAttribute7.Length -eq 8) {
$MBXDBTable.Rows.Find($OptimalDB.Database).BlackberryCount++
}
If ($MovingMailbox.UseDatabaseQuotaDefaults -eq $true) {
$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveSTDMBs++
} Else {
$MBXDBTable.Rows.Find($OptimalDB.Database).ActiveLRGMBs++
}
}

$MailboxData | ForEach {
$TargetDB = $_.DatabaseName
$ID = $_.PrimarySMTPAddress
New-MoveRequest -identity $ID -TargetDatabase $TargetDB -BadItemLimit 50

-Batchname $BatchName -SuspendWhenReadyToComplete -Confirm:$false
}

$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]

3 thoughts on “Exchange 2010 Migration Script Part 1

  1. Unexpected token ‘CurrentDB’ in expression or statement.
    At D:\scripts\MailMigrationPS.ps1:19 char:89
    + $PercentComplete = [Math]::Round(($LoopCount++ / $TarDatabases.Count * 100),1)$CurrentDB <<<< = $TarDatabase.Name
    + CategoryInfo : ParserError: (CurrentDB:String) [], ParseException
    + FullyQualifiedErrorId : UnexpectedToken

    I am getting this when I try to run your script.

  2. Hi, just merged both scripts to produce a reporting Exchange 2003 > 2010 migration script. Great work, thank you!

Leave a Reply