PowerShell: Batch moving/merging mailboxes

The Merge-MailboxFolder script is very powerful in that it can move data between mailboxes (even entire mailboxes, if desired).  It can also be used to process lots of mailboxes at once.  This blog post is going to run through in detail the procedure to move all items from the archive mailbox into the main mailbox for a large number of users.

Set up a service account with ApplicationImpersonation rights

To start with, I set up an account that has rights to access all the mailboxes.  This is done by granting ApplicationImpersonation role to the account.  While the script will work with FullAccess rights, you don't want to access mailboxes this way as you will be throttled much more (all the activity will be budgeted against your service account, rather than the mailboxes being accessed).

Install the EWS Managed API

The Merge-MailboxFolder script uses EWS to access Exchange.  To do this, it needs the EWS Managed API.  If you install it using default options (i.e. to the usual location), then the script will automatically find it.  You can download the EWS Managed API from here: https://www.microsoft.com/en-us/download/details.aspx?id=42951

Create the batches of mailboxes to process

Next, I am going to create a set of text files that list all the mailboxes I want to process.  I've written a script that can do this easily, so we'll use that: Create-MailboxBatches.ps1

To make this example simple, I am going to process all mailboxes in my organisation.  I could limit using a filter or Organisational Unit (the parameters being the same as used in Get-Mailbox).  So, to create my batch files I call the script like this:

.\Create-MailboxBatches.ps1 -Credentials (Get-Credential) -ExportBatchPath "c:\temp\mailbox_batch" -BatchSize 50

When you run the above, you'll be prompted for credentials.  You need to enter the credentials of an account that can connect to Exchange Remote PowerShell (in this case it will connect to Office 365) and run Get-Mailbox.  If you are using this in an on-premise environment, then supply the correct PowerShell Url using the -PowerShellUrl parameter.

Depending upon the number of users in your organisation, the script may take a little while to run, but in the end you'll end up with a bunch of text files in c:\temp\mailbox_batch that each contain a list of 50 mailboxes.

Use the batch files to process the mailboxes

Now we can start the main work.  As we are processing lots of mailboxes, I'm going to process 5 at a time (or more accurately, start the Merge-MailboxFolder script going against five of the batch files concurrently - this will speed up the whole process significantly).  To do this, I need a little extra PowerShell to spawn the jobs and keep track of them.  The below will do this:

# The next three lines may need changing depending upon your environment
$credentials = Get-Credential # Enter the credentials that have permissions to access all the mailboxes (ApplicationImpersonation)
$batchListFolder = "c:\temp\mailbox_batch"
$location = Get-Location # This needs to point to the location of the Merge-MailboxFolder script.  In this case, it is in the current folder of the PowerShell session
$ewsUrl = 'https://outlook.office365.com/EWS/Exchange.asmx'

# Everything that follows can be left as it is
$scriptBlock = {
 param($path, $sourcemailbox, $ewsurl, $credentials, $logfile)
 cd $path
 .\Merge-MailboxFolder.ps1 -SourceMailbox $sourcemailbox -SourceArchive -Copy -ProcessSubfolders -CreateTargetFolder -Impersonate -EwsUrl $ewsurl -Credentials $credentials -LogFile $logfile

Get-ChildItem $batchListFolder -Filter *.txt | 
Foreach-Object {
 Start-Job -ScriptBlock $scriptBlock -ArgumentList @($location.Path, "$batchListFolder\$($_.Name)",$ewsUrl, $credentials, "$batchListFolder\%mailbox%.log")
 $activeJobs = @(Get-Job | Where-Object { $_.State -eq 'Running' })
 while ( $activeJobs.Count -gt 4)
  Write-Progress -Activity "Waiting for job to finish" -Status "($($activeJobs.Count) jobs currently running)"
  Start-Sleep -s 5
  $activeJobs = @(Get-Job | Where-Object { $_.State -eq 'Running' })
 Write-Progress -Activity "Waiting for job to finish" -Completed

while ( $activeJobs.Count -gt 0)
 Write-Progress -Activity "All jobs started, waiting for them to complete" -Status "($($activeJobs.Count) jobs currently running)"
 Start-Sleep -s 5
 $activeJobs = @(Get-Job | Where-Object { $_.State -eq 'Running' })
Write-Progress -Activity "All jobs started, waiting for them to complete" -Completed
Write-Host "Complete!" - ForegroundColor Green


If you are running this against an on-premise Exchange server, you'd need to change the EWS Url to that for your environment (or set it to "" to use autodiscover).

That should be it.  The script will create a log for each mailbox merge in the same folder as the batch files are located (you could change this if you wanted), and the PowerShell window will let you know when everything is complete.  Note that this could take a very long time depending upon the size of your organisation.

You can adjust the above process to suit your needs - changing what is moved (e.g. to limit it to certain folders) would require updating the $ScriptBlock variable (i.e. adjust the parameters being passed into Merge-MailboxFolder.ps1).

Comments (1)

  1. I really needed this script about 2 months ago! The use case for me was large shared mailboxes that exceed 500 folders or 100,000 items per folder, causing Outlook Cached Mode limits to be hit and then client-side issues as a result.

    Happy I found it, in case the need ever pops up again. Thanks!

Skip to main content