Bulk Move Messages from Archive MB to Primary MB

The Problem

Recently I had the need to move all contents of users' Archive Mailboxes back to the Primary Mailboxes in Office 365 in order to disable those Archive Mailboxes. In Exchange On-Premise, this is a piece of cake. Simply do a Mailbox export of the Archive to PST via the New-MailboxExportRequest PowerShell Exchange Cmdlet, then import the PST back into the Primary Mailbox using New-MailboxImportRequest. Unfortunately in Office 365, there is no option to perform a Mailbox export to PST. So this left me with the conundrum of how do I script this with PowerShell without having manually touch everyone's Outlook? Immediately my mind went to EWS (Exchange Web Services) as I knew there had to be some kind of mechanism built into the API.


The Epiphany

Rather than reinventing the wheel, I figured I'd look around first since there's a lot of great scripts already out there and I don't know a lot about coding using EWS. That's when I stumbled upon David Barrett's Merge-MailboxFolder script. I couldn't believe my luck, the script did EXACTLY what I was looking to do and worked with Office 365 and Archive Mailboxes.

The Process

Since I needed to move everything from the Archive Mailbox and the Merge-MailboxFolder requires you to specify the folders you want to move (both source and target folder names) and I wanted to mirror the folders, I  wrote another small script called Batch-MergeMBF.ps1 to grab all folders in the user's Archive Mailbox for all of the users and call the Merge-MailboxFolder script using that data. To run the script, you will need to place in the same folder, the Merge-MailboxFolders.ps1 script, the Batch-MergeMBF.ps1 (code below) and users.txt (text file containing user SMTP addresses). You will need an account that has either FullAccess to all of the Mailboxes or one that has Impersonation. If you are going to use impersonation, you will need to add the Impersonate parameter to the line running the Merge-MailboxFolder script. Here is that code:

$UserCredential = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $UserCredential -Authentication Basic -AllowRedirection
Import-PSSession $Session

# TXT file of user SMTP Addresses
$Users = Get-Content .\users.txt

ForEach ($User in $Users)
{
$Merge = @()
[System.Collections.ArrayList]$RootFolder = @()
$Folders = Get-MailboxFolderStatistics $User -Archive | ? {$_.ItemsInFolder -gt 0} | select FolderPath
ForEach ($Folder in $Folders)
{$RootFolder.Add($Folder.FolderPath.Split("/")[1])}
$RootFolder = $RootFolder | Select -Unique
ForEach ($Root in $RootFolder)
{
# Ignore the system folders because the script isn't capable of copying them to the correct place and the data isn't needed in most cases
If (($Root -ne "Top of Information Store") -and ($Root -ne "Recoverable Items") -and ($Root -ne "Calendar Logging") -and ($Root -ne "Deletions") -and ($Root -ne "Purges") -and ($Root -ne "Versions") -and ($Root -ne "Conversation Action Settings") -and ($Root -ne "Quick Step Settings"))
{
$objMerge = New-Object System.Object
$objMerge | Add-Member -Type NoteProperty -Name Name -Value $Root
$objMerge | Add-Member -Type NoteProperty -Name Value -Value $Root
$Merge += $objMerge
}
}

# Call the script with the source and target MB being the user, specify the source is an Archive, process all subfolders since the above code collects only the root folders, allow insecure redirection is needed for O365
.\Merge-MailboxFolder.ps1 -SourceMailbox $User -SourceArchive -TargetMailbox $User -ProcessSubfolders -MergeFolderList $Merge -AllowInsecureRedirection -Verbose -Credentials $UserCredential
}

The Gotchas

Throttling

The first and what I consider biggest thing to watch out for with this script was something that slowed me a down a lot and there is currently no good way to work around it. The Merge-MailboxFolders script does not take throttling into account. I found that after processing either large Archives or a lot of Archives, I started getting errors of "No Client Access Servers available to process my request" and a few other similar ones. This was a result of Office 365 throttling me. The throttling would start gradually, occurring every once in a while and culminate by repeatedly occurring, not moving any more data until the script completed (looped through all of the folders). When this happened, I began working around it by immediately force stopping the script and running it again with a second set of credentials and I would alternate back and forth between the two. I also found I would have to run it against the users a second time because once the throttling starts, the messages it is currently trying to move fail and remain in the Archive and get passed over. Impersonation did not seem to help the issue, and instead actually appeared to affect the end users more by having them be throttled. I brought the issue up to the script creator who said he would consider it for future development. 

To verify if an Archive has any items left in the folders, you can run the following command:
Get-MailboxFolderStatistics "User@contoso.com" -Archive | ? {($_.ItemsInFolder -gt 0) -and ($_.Name -ne "Top of Information Store") -and ($_.Name -ne "Recoverable Items") -and ($_.Name -ne "Calendar Logging") -and ($_.Name -ne "Deletions") -and ($_.Name -ne "Purges") -and ($_.Name -ne "Versions") -and ($_.Name -ne "Conversation Action Settings") -and ($_.Name -ne "Quick Step Settings")} | ft FolderPath,Name,FolderSize,ItemsInFolder -AutoSize

Message Moves

By default, the script moves the messages (e.g. removes it from the source after being copied). This is just something to be aware of which you can mitigate by using the Copy parameter if you want.

Missing Target Folders

Because I was using the script to move items from the Archive that were originally moved out of the Primary because of retention policies, most of the mailboxes I processed already had all of the source (Archive) folders in the target (Primary). If you receive an error that a folder of the same name doesn't exist in the target, then you will have to add the CreateTargetFolder parameter. I didn't add this by default out of an overabundance of caution, but for the few mailboxes I did have to use it on, I experienced no issues.

The Script

Finally, I present to you a link to the script page. I can't emphasize enough my gratitude to David for writing this script and I hope this blogs help it reach a few more people who can use it.

PowerShell: Merge mailbox folders using EWS

Labels: , , , ,