{"id":148,"date":"2012-03-15T17:10:57","date_gmt":"2012-03-15T17:10:57","guid":{"rendered":"http:\/\/blogs.oucs.ox.ac.uk\/nexus\/?p=148"},"modified":"2012-03-15T17:21:25","modified_gmt":"2012-03-15T17:21:25","slug":"batch-migrations","status":"publish","type":"post","link":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/2012\/03\/15\/batch-migrations\/","title":{"rendered":"Batch migrations"},"content":{"rendered":"<p>To keep our migrations under control I&#8217;ve been relying very heavily on the use of batched migration runs. \u00a0The process I&#8217;ve followed uses the &#8216;suspendwhenreadytocomplete&#8217; value which performs the normal migration, copying the entire set of users&#8217; mailboxes, but &#8211; crucially &#8211; stops the process just before the final stage where the changes are committed to the directory.<\/p>\n<p>The really great thing about doing things this way is that, almost as an incidental benefit, the vast majority of corrupt mailbox content will be revealed through this exercise: those mailboxes fail to reach that &#8216;autosuspended&#8217; stage. Yet getting to that point doesn&#8217;t \u00a0impact end users at all: they remain active and oblivious on their old server all the way through the job.<\/p>\n<p>I used this feature to benchmark the migration process for one department and get a feel for for how long it might take to do this on a larger scale. When the final stage was ready we could just resume the task, via an overnight scheduled task. The momentary interruption when the mailbox is locked, for that final &#8216;commit&#8217; phase, shouldn&#8217;t affect anyone. Our tests showed that even users actively accessing their mailboxes during the move weren&#8217;t adversely affected. Well, except for Mac users&#8230; More on that later.<\/p>\n<p>Initially we had planned to use this auto-suspend capability to migrate the entire user base of 50,000 mailboxes in one go, but a lack of documentation from anyone else having tried it on that scale caused some raised eyebrows. The compromise required a rethink on mailbox distribution and some careful tweaking of circular logging. I used circular logging to keep log files manageable during the phase where mailboxes get copied. This was followed by a full backup, then circular logging was switched off before finally committing the changes. Although this added extra steps it did ensure that our backups were able to cope with the extra content without contending with vast numbers of logfiles that would&#8217;ve otherwise been generated by the mailbox moves.<\/p>\n<p>There were several distinct phases to our migration which, due to our scale, were to be repeated across twenty separate migration runs. Each one went through these steps:<\/p>\n<ol>\n<li>Export from the GAL a CSV file containing the aliases of the mailboxes to be upgraded. I included primary SMTP address and department data so that a version of this same file could be used for a mail merge in the next step.<\/li>\n<li>Notify users of the upgrade &#8211; one week prior to the planned date.<\/li>\n<li>Create the move requests and launch them, using the &#8216;SuspendWhenReadyToComplete&#8217; option.<\/li>\n<li>Turn off circular logging on the destination databases and back them up.<\/li>\n<li>Schedule an overnight &#8216;resume&#8217; of the autosuspended mailboxes to complete the migration.<\/li>\n<\/ol>\n<p>I came up with a bit of PowerShell (later optimised a little further by a colleague by the addition of logging and setting file attributes) to allow the bulk of the mailbox copying to take place without colleagues needing to remember the exact syntax of the command. It relies on you having a prepared CSV file with at least the following values in it:<\/p>\n<blockquote><p>Alias,TargetDB<br \/>\nUserAlias1,Database1<br \/>\nUserAlias2,Database2<\/p><\/blockquote>\n<p>The script assumes you&#8217;ll have saved the file as &#8216;c:\\MIGRATION.CSV&#8217;.<\/p>\n<p>All you need to do then is run the following as a PS1 file, remembering to include a name for the batch at the end:<\/p>\n<blockquote><p>param<br \/>\n(<br \/>\n$batchName = $(throw &#8220;Please specify a batch name.&#8221;),<br \/>\n$migrationCsv = &#8216;c:\\migration.csv&#8217;<br \/>\n)<\/p>\n<p>## Capture the batch name in a file (for the other scripts):<br \/>\n$batchNameFile = &#8216;c:\\batchname.txt&#8217;<br \/>\n# If it already exists make it writable<br \/>\nif (Test-Path $batchNameFile)<br \/>\n{<br \/>\nSet-ItemProperty $batchNameFile -name isreadonly -value $false<br \/>\n}<br \/>\n# Overwrite the file (if it exists) and means the batch name is all it contains:<br \/>\n$batchName &gt; $batchNameFile<br \/>\n# Make it read-only<br \/>\nSet-ItemProperty $batchNameFile -name isreadonly -value $true<\/p>\n<p>#Load snap-in to support use of Exchange Commands:<br \/>\nAdd-PSSnapin Microsoft.Exchange.Management.Powershell.E2010 -erroraction silentlyContinue<br \/>\nimport-csv $migrationCsv |foreach {get-mailbox $_.alias |new-moverequest -suspendwhenreadytocomplete -batchname $batchName -targetdatabase $_.targetdb}<\/p><\/blockquote>\n<p>The job of completing these part-finished moves was left for an overnight scheduled task. To ensure that it only completed the moves for that night&#8217;s batch of users it would use the batch name that was created earlier:<\/p>\n<blockquote><p>#Load snap-in to support use of Exchange Commands:<br \/>\nAdd-PSSnapin Microsoft.Exchange.Management.Powershell.E2010 -erroraction silentlyContinue<br \/>\n# Get the batch name<br \/>\n$batchNameFile = &#8216;c:\\batchname.txt&#8217;<br \/>\n$batchName = Get-Content $batchNameFile<br \/>\n$TIMESTAMP_SUFFIX = &#8220;{0:dd-MMM-yyyy-HHmm}&#8221; -f (Get-Date)<br \/>\n$logFile = &#8220;C:\\PS_LOGS\\commit_$batchName_$TIMESTAMP_SUFFIX.txt&#8221;<\/p>\n<p>&#8220;This script started executing at {0:G}.&#8221; -f (Get-Date) &gt;&gt; $logFile<br \/>\n&#8220;About to start processing the commits for batch: &#8216;$batchname&#8217;.&#8221; &gt;&gt; $logFile<br \/>\n## Resume and commit<br \/>\nGet-MoveRequest -resultsize unlimited -MoveStatus &#8216;AutoSuspended&#8217; -BatchName $batchName | Resume-MoveRequest<br \/>\n#Resume any other suspended moves associated with this batch name:<br \/>\nGet-MoveRequest -resultsize unlimited -MoveStatus &#8216;Suspended&#8217; -BatchName $batchName | Resume-MoveRequest<\/p>\n<p>&#8216;(Check the other logs and e-mails for precise timings &amp; statistics).&#8217; &gt;&gt; $logFile<br \/>\n&#8220;This script exited at {0:G}.&#8221; -f (Get-Date) &gt;&gt; $logFile<\/p><\/blockquote>\n<p>That took care of the heavy lifting but obviously I wanted to know what had happened when I arrived the next day, so yet another bit of scheduled PowerShell ran the following command and emailed me the output:<\/p>\n<blockquote><p>Get-MoveRequest -BatchName $batchName -MoveStatus Completed | Get-MoveRequestStatistics |ft alias, TotalItemSize, TotalMailboxItemCount, PercentComplete, BytesTransferred, ItemsTransferred -auto<\/p><\/blockquote>\n<p>In fact I ran several variations on that, with different status values, so I&#8217;d also be told about failed migrations and anything that was still suspended.<\/p>\n<p>Now this is all well and good but we&#8217;ve now done nine nights of mass-migrations, as well as several early adopter and test ones, so after a while it&#8217;s possible that you&#8217;d lose track of all of those batch names you&#8217;d used. The problem is exacerbated because the graphical interface doesn&#8217;t even show them.<\/p>\n<p>Luckily there&#8217;s another bit of PowerShell which can reveal what batch names are still lurking on your system:<\/p>\n<blockquote><p>Get-MoveRequest \u2013ResultSize Unlimited | Sort-Object \u2013Property batchname | Select batchname | Get-Unique \u2013AsString<\/p><\/blockquote>\n<p>We&#8217;re about to cross the milestone of the halfway stage &#8211; we&#8217;ll have migrated approximately 25,000 mailboxes at some point in the early hours of tomorrow morning &#8211; so from that moment over half of the university will be running on Exchange 2010.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>To keep our migrations under control I&#8217;ve been relying very heavily on the use of batched migration runs. \u00a0The process I&#8217;ve followed uses the &#8216;suspendwhenreadytocomplete&#8217; value which performs the normal migration, copying the entire set of users&#8217; mailboxes, but &#8211; &hellip; <a href=\"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/2012\/03\/15\/batch-migrations\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":107,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-148","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/posts\/148","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/users\/107"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/comments?post=148"}],"version-history":[{"count":7,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/posts\/148\/revisions"}],"predecessor-version":[{"id":152,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/posts\/148\/revisions\/152"}],"wp:attachment":[{"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/media?parent=148"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/categories?post=148"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs-new.it.ox.ac.uk\/nexus\/wp-json\/wp\/v2\/tags?post=148"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}