The Surly Admin

Father, husband, IT Pro, cancer survivor

Watch Exchange Mailbox Move Requests

Recently I’ve been involved with upgrading/migrating my Exchange server from 2003 to 2010.  It’s one of those projects where there aren’t going to be a ton of tangible benefits for my users and what with all of the other things going on at work I kept putting off.  But no more.  Let’s wrap this puppy up and move on.  Co-existence was configured for awhile but now I just needed to move the mailboxes in an orderly fashion.  We’re not a big company, but I’ve always kept a pretty liberal email retention policy so I’m paying the price for that now with large mailboxes and a lot of time needed to move things.

Read on to see how I used PowerShell to help me with this.

Mailbox Move Requests

The first thing to realize is that mailbox move requests are very different in Exchange 2010 than they were with Exchange 2003.  In 2003 you did everything from Active Directory Users and Computers (ADUC) and you got a nice progress bar and everything.  Exchange 2010 does it by submitting it as a back ground job which you could than monitor.  And since it’s a PowerShell command that you use to submit the background job it only makes sense to use PowerShell to monitor the progress.  But it wasn’t enough to just do that, my mailbox move process was several steps and I was planning on doing the moves in groups so it was a process that I was going to be repeating over and over again.

Step 1: Who’s moving this time?

This is a pretty simple script where I do a simple lookup on who is on the old server (MFExch1) and isn’t a conference room.  Loop through until we hit a predetermined number and export the results to a CSV so I can open in Excel and do some manual trimming.  Nothing too exciting here, just use Get-Mailbox with some appropriate Where-Object piping to get this information.

Get-Mailbox -Filter 'servername -eq "mfexch1" -and name -notlike "*Conference*"' | Where {$_.distinguishedName -like "*OU=Users*"}

Step 2: Notify the Users

This is an easy script too, I just read the CSV created in CSV back into PowerShell and send all the recipients a simple email telling them what’s going to be happening on Saturday.  Nothing exciting enough to post here.

Step 3: Last notification and make the moves

Last script reads the CSV in again and sends one last email to the Users warning them of the move.  It then sits for 1 hour and then submits all the Exchange move requests.  Here’s the good part:

$DB = "Mailbox DataStore1"
ForEach ($User in (Import-Csv c:\Users\mpugh\Desktop\EmailMoves.csv))
{   New-MoveRequest $User.Email -TargetDatabase $DB -AcceptLargeDataLoss -BadItemLimit Unlimited
    If ($DB -eq "Mailbox DataStore1")
    {   $DB = "Mailbox DataStore2"
    {   $DB = "Mailbox DataStore1"

I have 2 databases so I just had the script do some simple round robin style balancing.  Not looking at mailbox sizes or anything, just back and forth.  This whole setup is a great example of using PowerShell to automate a repetitive process and to ensure that it gets done the same way for each group of users.  It also made doing this process dead simple as the amount of work I actually had to do was minimal.

The downside to all of this was there was one piece I couldn’t automate and that was modifying my user’s iPhone’s and iPad’s to point to the new Exchange server.  Now, in theory with co-existence I should have been able to point my users to the Exchange 2010 server from the beginning and watch it proxy like a champ.  In the real world this didn’t work for us at all.  I even called Microsoft and had them work on it for several days but it got to the point that it was going to cause more work and downtime to fix this than to just use our MDM software to point to the new server once their mailbox was over there.

But to make this change as soon as I could, to minimize how long someone was without email, I needed to see the moves as they went.  It turns out, seeing this is actually pretty easy:

Get-MoveRequest | Get-MoveRequestStatistics

Is all you really need.  Technically, Get-MoveRequest is all you need as it will show you what’s in queue and if it’s completed or not.  Get-MoveRequestStatistics is nice because it can show you where in the queue someone is, and the % their move is completed once they move to “In Process”.  But, being a true engineer I don’t want to sit there typing this in every so often, checking it against my spreadsheet and then changing the MDM when needed.  I want to sit in my chair and get email alerts when people are done!

Hence, the Watch-MoveRequests.ps1 script was born.

First things first, I wanted the be able to load the Exchange tools in a myriad of ways, including remoting to the Exchange 2010 server.  But this turned out to be a little more complicated than I originally anticipated and I used this code to get there:

If (Get-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010 -Registered -ErrorAction SilentlyContinue)
{   $Snapin = "Microsoft.Exchange.Management.PowerShell.E2010"
ElseIf (Get-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -Registered -ErrorAction SilentlyContinue)
{   $Snapin = "Microsoft.Exchange.Management.PowerShell.Admin"
{   Write-Verbose "Unable to locate Exchange tools on localhost, attempting to remote to Exchange 2010 server..."
    #Try implicit remoting-only works for Exchange 2010
    Try {
        $ExchangeSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri <a href="http://$Exchange2010Server/PowerShell/">http://$Exchange2010Server/PowerShell/</a> -ErrorAction Stop
        Import-PSSession $ExchangeSession -ErrorAction Stop
    Catch {
        Write-Host "Unable to import Exchange tools from $Exchange2010Server, is it running Exchange 2010?" -ForegroundColor Red
        Write-Host "Error:  $($Error[0])" -ForegroundColor Magenta
If ($Snapin)
{   #We know it exists, but is it already loaded?
    If (-not (Get-PSSnapin $Snapin -ErrorAction SilentlyContinue))
    {   Try {
            Add-PSSnapin $Snapin -ErrorAction Stop
        Catch {
            Write-Host "Unable to load $Snapin because $($Error[0])" -ForegroundColor Red

First thing, is Microsoft changed the name of the PowerShell module, which really sucks so I had to try to determine if either were installed and define a variable with that information for later.  If neither were installed it’ll try remoting to the Exchange 2010 server.  I don’t have Exchange 2007 but from the research I did the implicit remoting for Exchange tools wasn’t introduced until Exchange 2010 so I’m assuming that.  If anyone can correct me on that, that’d be great!  Assuming that works OK we’ll move on to the rest of the script.

So, one of the two Exchange tool sets is indeed installed, now we have to figure out if it’s already loaded, if so simply move on.  If not go ahead and load it and if there’s some kind of error loading it we’ll spit out an error and exit out of the script.

Next we need to just display the current Move Requests and try to make the display as pretty as possible:

Get-MoveRequest | Get-MoveRequestStatistics | Sort PositionInQueue,Status,DisplayName | Select DisplayName,@{Name="Size MB";Expression={$_.TotalMailboxSize.ToMB()}},TargetDatabase,PositionInQueue,Status,@{Name="%";Expression={$_.PercentComplete}} | Format-Table -AutoSize

This get’s the requests, than their statistics, executes a sort on it by queue position, status and display name.  Than I use Select-Object to get the properties I want out of all of this while massaging the numbers a little to make it nice.  Last pipe it into Format-Table with -AutoSize to try to fit it all on your screen in a mostly pleasant list.  The PositionInQueue isn’t the best display and in the next version of the script I will try to isolate just the queue depth number to make it look better.

Now, we just need to watch for mailboxes that go to completed–and we have to make sure that we don’t email ourselves with every loop the same mailboxes so we’ll store that data in a hashtable for quick reference–wait a few seconds and loop again.  Once all of the Move Requests are done exit the loop and email again that everything’s done.

Do {
    Get-MoveRequest | Get-MoveRequestStatistics | Sort PositionInQueue,Status,DisplayName | Select DisplayName,@{Name="Size MB";Expression={$_.TotalMailboxSize.ToMB()}},TargetDatabase,PositionInQueue,Status,@{Name="%";Expression={$_.PercentComplete}} | Format-Table -AutoSize

    ForEach ($Move in (Get-MoveRequest | Where {$_.Status -eq "Completed" -or $_.Status -eq "Failed"}))
    {  If ($Email)
       {   If (-not $Done.ContainsKey($Move.DisplayName))
           {   Send-MailMessage -To $To -From $From -Subject "$($Move.DisplayName) Mailbox $($Move.Status)" -Body "$($Move.DisplayName) $($Move.Status) it's move to $($Move.TargetDatabase)" -SmtpServer $SMTPServer

    Start-Sleep -Seconds 30
} Until (@(Get-MoveRequest | Where {$_.Status -eq "Completed" -or $_.Status -eq "Failed"}).Count -eq (@(Get-MoveRequest)).Count)

$Moves = Get-MoveRequest | Get-MoveRequestStatistics | Sort PositionInQueue,Status,DisplayName | Select DisplayName,@{Name="Size MB";Expression={$_.TotalMailboxSize.ToMB()}},TargetDatabase,PositionInQueue,Status,PercentComplete | ConvertTo-Html -Head $Header
Send-MailMessage -To $To -From $From -Subject "Email moves completed at $(Get-Date)" -Body ($Moves | Out-String) -SmtpServer $SMTPServer -BodyAsHtml

Hashtables are great for this kind of random access.  I can simply put the mailbox name in as the Key to the hashtable than check if that key exists.  If it doesn’t send an email and add it to the hashtable.  Next time through the loop the email won’t be sent.

After that it’s just a matter of putting some comment-based help, some parameters for things like To and From for the email and a parameter to determine if you even want the granular emails or if the one at the end is fine.  I didn’t include all that here as it’s in the source code.

This was a fun script to write and proved very helpful for me in the mailbox moves.  Unfortunately that’s all done and I’ll have very little need to use the script again!  Hopefully you might find it useful!

Source Code Here


October 17, 2013 - Posted by | PowerShell | , ,


  1. Were you just having this run as a scheduled task or once you run it, it stays running until all move requests are completed?

    Comment by Chris Rice | December 19, 2013 | Reply

    • Chris, it’s meant to be run interactively. Running it from a scheduled task won’t show you anything!

      Comment by Martin9700 | December 19, 2013 | Reply

      • So if I just run this from one of the exchange servers in power shell after I start the move requests, that power shell window will just stay open and send me an e-mail once the moves are done?

        Comment by Chris Rice | December 19, 2013

      • That’s right.

        Comment by Martin9700 | December 19, 2013

  2. Maring, I was interested in the Hash table and how it notifies the end user after the move is completed. So am I correct in assuming that once the move is completed, it notifies the end user but does NOT remove that particular move request and keeps looping until all moves are completed? Thanks

    Comment by Tash | November 5, 2014 | Reply

    • Yes, I believe that’s correct (been awhile since I’ve looked at the script!)

      Comment by Martin9700 | November 7, 2014 | Reply

  3. sorry, forgot to add

    Meant Martin (typo above).

    Second. Notifies the user once, but the move request stays and as it loops thur, does not keep emailing the end user over and over again.

    Comment by Tash | November 5, 2014 | Reply

    • I’ve been called worse 🙂

      That’s right, just one email notification.

      Comment by Martin9700 | November 7, 2014 | Reply

  4. I didn’t think directly loading the Exchange PS Snap-in was a supported mechanism. Is there a reason you chose to do it this way, instead of Importing the snap-in remotely?

    Comment by seabrawk | February 3, 2016 | Reply

    • Hi Tony, to be honest, at the time, I didn’t even know about implicit remoting. That’s certainly how I would do it now.

      Comment by Martin9700 | February 3, 2016 | Reply

  5. […] Watch Exchange Mailbox Move Requests « The Surly … – Watch Exchange Mailbox Move Requests. Recently I’ve been involved with upgrading/migrating my Exchange server from 2003 to 2010. It’s one of those projects … […]

    Pingback by Import-csv Powershell | | March 5, 2016 | Reply

Leave a Reply to seabrawk Cancel reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: