Exchange 2007 Advanced Move Mailbox Powershell commands

By now, you should be familiar with the day to day Powershell commands, so here is one that uses the foreach command.  Foreach is used to loop through a given set of items (an array), and perform an action on each one.

As an example, let's say that you want to perform a Cross-Org mailbox move, but you want to use Get-Mailbox first to get a list of mailboxes.  Just using the standard filtering, you have limited options.  You can filter 1. By a specific Organizational Unit; 2. By a specific Exchange server; 3. Don't filter at all - return all users; 4. Specify an identity to only return a single mailbox.  Now, that's nice that these filters exist, and they are pretty easy to use.  You can reference the help for Get-Mailbox for more details (get-help get-mailbox -full).  However, what if you only want to move a handful of mailboxes?  You need to do some more work.

First, you'll need to define an array by specifying a variable.  You also need to know the alias of the users that you are wanting to move.

$mboxes = "user1","user2","user3","user4"

You have now successfully created an array.  Now, you can perform an action on that array using the Foreach command.  The basic usage of the foreach command is as follows.

foreach ($<item> in $<collection>) {<command_block>}

One thing to remember is that $<item> also references a variable, but it is dynamically created, and you can call it whatever you want.  In our case, the resulting command would look something like this.  I've chosen to use $mb for the$<item> variable.  We'll start out by simply getting the mailbox information for those users to make sure it works, then we'll move on to the more complex move portion.

Note that unless your current account has rights in both forests, you will still need to pass the -credential string, and you will need to define a variable for the credentials using get-credential.  To make things easier, I always use $s for the Source Forest credentials, and $t for the Target Forest credentials.

foreach ($mb in $mboxes) {get-mailbox $mb -DomainController <source DC FQDN> -Credential $s}

You should now have a list on your screen that looks something like this.

Name Alias ServerName ProhibitSendQuota
User 1 user1 2k3servername unlimited
User 2 user2 2k3servername unlimited
User 3 user3 2k3servername unlimited
User 4 user4 2k3servername unlimited

Now that we are sure that the results are being returned properly, let's include the move-mailbox command.  First, I'll show you what doesn't work.

foreach ($mb in $mboxes) {get-mailbox $mb -DomainController <source DC FQDN> -Credential $s} | move-mailbox -TargetDatabase "Server\Storage Group\Database" -GlobalCatalog <target GC FQDN> -SourceForestCredential $s -SourceForestGlobalCatalog <source GC FQDN> -TargetForestCredential $t -AllowMerge -SourceMailboxCleanupOptions deletesourcemailbox

You might think this would work, because previous experience with Powershell has taught you about Pipelining, but it will return an error indicating you have an empty pipe element, which is not permitted.  In order to get this to work, you actually have to include the Pipeline within the command block.  I'm going to move the end brace } to the end of the statement.

foreach ($mb in $mboxes) {get-mailbox $mb -DomainController <source DC FQDN> -Credential $s | move-mailbox -TargetDatabase "Server\Storage Group\Database" -GlobalCatalog <target GC FQDN> -SourceForestCredential $s -SourceForestGlobalCatalog <source GC FQDN> -TargetForestCredential $t -AllowMerge -SourceMailboxCleanupOptions deletesourcemailbox}

The above command will loop through each user in your array, and perform the move-mailbox command.  You should note that the command I used assumes that the user account has already been migrated to the target domain by using a migration tool such as the Active Directory Migration Tool, and that you have migrated SID History (The SID is used to match the source mailbox to a target account).  The other option would be to include the -NTAccountOU parameter, and have Exchange 2007 create a disabled user account, but this is much less desirable, as it ends up creating a linked mailbox instead of a normal user account.

[update]
Thanks to comments from some of you, who pointed out that using foreach, while it *does* work as it is shown above, it isn't optimal because it passes the results serially.  In other words, it means that it will pass the first result to move-mailbox, then it will wait to pass the next result until the first loop completes.  This will result in move-mailbox being called 4 times (for the example above), more if you have a larger list.  It also doesn't take advantage of the multi-threading capability of move-mailbox.

In order to make it work properly, you can either simply specify the array, or you can pass the array into the foreach loop (instead of specifying each item in the array).  Examples below.

$mboxes | get-mailbox $mb -DomainController <source DC FQDN> -Credential $s | move-mailbox -TargetDatabase "Server\Storage Group\Database" -GlobalCatalog <target GC FQDN> -SourceForestCredential $s -SourceForestGlobalCatalog <source GC FQDN> -TargetForestCredential $t -AllowMerge -SourceMailboxCleanupOptions deletesourcemailbox

foreach { $mboxes } | get-mailbox $mb -DomainController <source DC FQDN> -Credential $s | move-mailbox -TargetDatabase "Server\Storage Group\Database" -GlobalCatalog <target GC FQDN> -SourceForestCredential $s -SourceForestGlobalCatalog <source GC FQDN> -TargetForestCredential $t -AllowMerge -SourceMailboxCleanupOptions deletesourcemailbox

What I'm learning here is that there is a LOT to learn about Powershell!!!

Thanks to Devin Ganger for pointing out the limitation, and thanks to Evan Dodds for helping with the correct usage.  I'll be posting a followup to this with some more information on moving mailboxes using a csv file, and using other filters to get the list you want.