Evenly Distributing Mailbox During Migrations (Part 2)

If you would like to read the first part in this article series please go to Evenly Distributing Mailbox During Migrations (Part 1).

Introduction

In the first part of this article we validated all the possible sources and targets, and created a list of target databases that will be used for the moves. It is now time to write the core of the script which includes the moveUser and the produceReport functions.

For the moveUser function, we start by getting the smallest DB in the array with the help of the GetSmallestDB function. After that, it is just a case of incrementing the DB size in the array and issue the move request using the New-MoveRequest cmdlet:

# Return the position of the smallest DB in the array

FunctionGetSmallestDB

{

      [Int] $intSmallestSize=1

     

      # Go through the arrSizes array to get the smallest DB

      For ([Int] $x= 0; $x-lt$intNumberDBs; $x++)

      {

            If (($intSmallestSize-eq1) -or ($arrDBSizes[$x, 1] -lt$intSmallestSize))

            {

                  $intSmallestSize=$arrDBSizes[$x, 1]

                  $intSmallestPos=$x

            }

      }

      # Return the position on arrDBSizes array of the smallest DB

      Return$intSmallestPos

}

FunctionmoveUser ($user)

{

      # Get the smallest DB to move the user to and update the arrDBSizes array

      $intSmallestPos=GetSmallestDB

      $arrDBSizes[$intSmallestPos, 1] += (Get-MailboxStatistics $user).TotalItemSize.Value.ToMB()

      Write-Host“Moving $user to”, $arrDBSizes[$intSmallestPos, 0]

     

      If ($BadItemLimit-gt 50)

      {

            New-MoveRequest -Identity $user -TargetDatabase $arrDBSizes[$intSmallestPos, 0] -BadItemLimit $BadItemLimit -AcceptLargeDataLoss -SuspendWhenReadyToComplete:$SuspendWhenReadyToComplete -Confirm:$false

      } Else {

            New-MoveRequest -Identity $user -TargetDatabase $arrDBSizes[$intSmallestPos, 0] -BadItemLimit $BadItemLimit -SuspendWhenReadyToComplete:$SuspendWhenReadyToComplete -Confirm:$false

      }

# If we have already moved as many users as we wanted, then stop

      $global:intCount++

      If ($global:intCount-eq$NumberOfMoves) { finishMigration }

      Start-Sleep-S 1

}

You might have noticed that I didn’t use PowerShell Splatting to deal with the BadItemLimit IF statement. To be honest, I didn’t see a need for it. This IF is necessary because if we use a BadItemLimit greater than 50, we also have to use the –AcceptLargeDataLoss parameter.

Waiting to Finish

Now that all mailboxes are in the process of being moved, we could simply exit and wait for all the moves to be completed. However, let’s wait for everything to finish (successfully or not) and then produce a report with the overall result.

As you saw in the first part of this article, the report includes the number of successful moves, how many failed and how many got skipped (for example, if the account is disabled or if the mailbox had a quota of 0KB in which case Exchange can’t move it). It will also show a list of all the mailboxes that we moved (or tried to move), the status of the move and how many bad items were found.

To wait for all the move requests to finish we simply use a While and check for any move requests in the InProgress status. If we find any, we wait for 5 minutes and then try again. Once there are no more mailboxes being moved, we create the report and send it to the Administrator(s).

FunctionfinishMigration

{

      #Write-Host “`nFinal Result:`n” -ForegroundColor Green

      #printDBSizesArray

     

      $moveRequests= Get-MoveRequest -ResultSize Unlimited

      While (($moveRequests | ? {$_.Status -match“InProgress”}) -or ($moveRequests | ? {$_.Status -eq“Queued”}))

      {

            # Sleep for 5 minutes

            Start-Sleep-Seconds 300

            $moveRequests= Get-MoveRequest -ResultSize Unlimited

      }

      produceReport

# We can also export the report to a file

      #$global:moveReport | Out-File “move_report.html”

      Send-MailMessage-From[email protected]-To[email protected]-Subject“Move Complete!”-Body$global:moveReport-BodyAsHTML-SMTPserver“smtp.letsexchange.com”-DeliveryNotificationOptiononFailure

      Exit

}

Building the Report

The function to produce the report is more HTML code than actually Exchange cmdlets. To start building it, we need to get a list of all the move requests. We could simply do:

$moveRequests= Get-MoveRequest -ResultSize Unlimited | SortDisplayName

which would include all the moves in the report. However, what if you don’t clear the moves from the previous day? You would end up with a huge report with half the users being from the day before… To improve this, we need to complicate it a bit. Notice that in the beginning of the script (in part 1) we saved the date and time the script started ([DateTime] $global:startDate=Get-Date). So, all we have to do is get a list of all move requests that were issued after this date:

$moveRequests= (Get-MoveRequest -ResultSize Unlimited) | ? {(Get-Date (Get-MoveRequestStatistics $_).StartTimeStamp) -ge$global:startDate} | SortDisplayName

Now let’s start with the actual HTML code and the first little table. In here we will group all move requests by their status and each state will be a row for the table, with one column being the status name and the other its count. I use $AlternateRow so that each line uses a different background color from the ones surrounding it so that it is easier to read:

Figure 2.1: Status Count Table

$global:moveReport=“<HTML> <BODY>”

# Header of the report (title and date)

$global:moveReport=“<font size=””1″” face=””Arial,sans-serif””>

                  <h3>Mailbox Move Report</h3>

                  <h5>$((Get-Date).ToString())</h5>

                  </font>”

# Header of the first table with the Status and Count columns

$global:moveReport+=“<table border=””0″” cellpadding=””3″”>   

                              <tr align=””center”” bgcolor=””#FFD700″”>

                              <th>Status</th>

                              <th>Count</tr>”

# Group all the move requests based on their status and populate the table

$AlternateRow= 1

ForEach ($movein ($moveRequests | GroupStatus))

{

      $global:moveReport+=“<tr”

      If ($AlternateRow)

      {

# If it is an alternate row, we set the background color to grey

            $global:moveReport+=” background-color:#dddddd”””

            $AlternateRow= 0

      } Else

      {

            $AlternateRow= 1

      }

      $global:moveReport+=“><td>$($move.Name)</td>”

      $global:moveReport+=“<td align=””center””>$($move.Count)</td>”

      $global:moveReport+=“</tr>”;

}

Now let’s count how many mailboxes got skipped and, if there were any, add the number to the table. This is simply the difference between the number of move requests issued and the number of move requests actually performed. After this, we “close” the table.

# Check how many mailboxes didn’t get moved

$notMoved=$global:intCount$moveRequests.Count

If ($notMoved-gt 0)

{

      # Write how many skipped mailboxes

      $global:moveReport+=“<tr”

      If ($AlternateRow)

      {

            $global:moveReport+=” background-color:#dddddd”””

            $AlternateRow= 0

      } Else

      {

            $AlternateRow= 1

      }

     

      $global:moveReport+=“><td>Skipped</td>”

      $global:moveReport+=“<td align=””center””>$($notMoved)</td>”

      $global:moveReport+=“</tr>”;

}

$global:moveReport+=“</table><br/>”

If no mailboxes were moved, then we can terminate the script here:

If ($moveRequests.Count -eq$null) { $global:moveReport+=“</body></html>”; Return }

If not, then let’s build another table to list all the mailboxes moved, the status of the move and how many bad items were found during the move. This table uses the same $AlternateRow method to write rows with different background colors. However, if a mailbox failed to be moved, we will write that row with the background color of Red.

For the first column we will use the mailbox’s DisplayName, for the second the Status and for the last one the BadItemsEncountered that we get from the Get-MoveRequestStatistics cmdlet.

$global:moveReport+=“<table border=””0″” cellpadding=””3″” “”>   

                              <tr align=””center”” bgcolor=””#FFD700″”>

                              <th>User</th>

                              <th>Status</th>

                              <th>Bad Items</th></tr>”

$AlternateRow= 1

$moveRequests | ForEach {

      $global:moveReport+=“<tr”

     

      If ($_.Status -match“Failed”)

      {

# If a mailbox move failed, we set the background color to Red

            $global:moveReport+=” background-color:#FF0000″””

      }

      Else

      {

            If ($AlternateRow)

            {

                  $global:moveReport+=” background-color:#dddddd”””

                  $AlternateRow= 0

            } Else

            {

                  $AlternateRow= 1

            }

      }

     

      $global:moveReport+=“><td>$($_.DisplayName)</td>”

      $global:moveReport+=“<td align=””center””>$($_.Status)</td>”

      $global:moveReport+=“<td align=””center””>$((Get-MoveRequestStatistics $_.Identity).BadItemsEncountered)</td>”

      $global:moveReport+=“</tr>”;

}

We now have the final table, so we can “close” the HTML code. Because we are saving the report into a global variable, the function doesn’t need to return anything:

$global:moveReport+=“</table><br/>”

$global:moveReport+=“</BODY></HTML>”

As you saw in the finishMigration function, we simply send the report by e-mail using the Send-MailMessage cmdlet. We specify the body of the e-mail as HTML and include all the HTML code we just produced in the body:

Send-MailMessage-From[email protected]-To[email protected]-Subject“Move Complete!”-Body$global:moveReport-BodyAsHTML-SMTPserver“smtp.letsexchange.com”-DeliveryNotificationOptiononFailure

You could go further and create a CSV file with the list of all users to move and the date you want them to be moved. You then update the script to run every night and read that file looking for users to move on that particular day. However, you should always keep a close eye on your migrations and confirm that everything is ok with the users that just got moved before moving more users.

Conclusion

In this article, we developed a script to help distribute mailboxes evenly based on their size when migrating from Exchange 2007 to 2010. It is crucial to have your user load distributed across all your available servers, and this script helps to achieve this. You can download the full script here.

If you would like to read the first part in this article series please go to Evenly Distributing Mailbox During Migrations (Part 1).

Leave a Comment

Your email address will not be published.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Scroll to Top