What I wanted to showcase here were a couple of sample PowerShell one-liners and scripts used to move Public Folder mailboxes and Public Folders as well as monitoring the progress of these moves.
Move Public Folder Mailbox
This is an easy one to work with. If we have a Public Folder mailbox and we need to move this mailbox to another database, possible on another DAG in the Exchange environment, the move cmdlet is very similar to that of a regular mailbox move:
New-MoveRequest -Identity PFMailbox01 -TargetDatabase PF1 -SuspendWhenReadyToComplete -BadItemLimit 10
Now, in my case, we were having issues with moving mailboxes and database corruption and thus wanted these moves to go as fast as possible. This required an additional parameter which adjusts the job priority to a state of ‘Emergency, like so:
New-MoveRequest -Identity PFMailbox01 -TargetDatabase PF1 -Priority emergency -SuspendWhenReadyToComplete -BadItemLimit 10
Additionally, if you want to ignore any corrupt items and there are a lot of these, we can raise the BadItemLimit to 50 possibly. If the BadItemLimit is raised to far we also need to add a switch called ‘AcceptLargeDataLoss’ which would look like this:
New-MoveRequest -Identity PFMailbox01 -TargetDatabase PF1 -Priority emergency -SuspendWhenReadyToComplete -BadItemLimit 100 -AcceptLargeDataLoss
Move Public Folders
Now while we can move entire mailboxes, sometimes we need to move just folders. Within Modern Public Folders there is a set of PowerShell cmdlets to handle this. One thing to keep in mind is that there can only be one Public Folder move request at a time in Exchange. You need to wait for that request to complete before it can be deleted and a new one can be created. There is also a limit to how many Public Folders that can be moved at a time as well. The move limit is ambiguous, but when you reach the limit, you will know:
I was not able to find exactly the limit, but this error occurred with 1700 and 3000 folders chosen for a single move. I eventually was able to reduce the size of the request and got past this. How did I do this? By selectively moving Public Folders by name like so:
$Folders = get-publicfolder '\' -recurse | where {$_.ContentMailboxName -eq 'PFMailbox01'} | Where {$_.Name -like "a*"} New-PublicFolderMoveRequest -TargetMailbox PFMailbox05 -Folders $Folders -BadItemLimit 10
After cycling though the letters of the alphabet, the folders that were left numbered around 1000 and then I was able to take all of the folders left in the Public Folder mailbox and moved them. Don’t forget to clear out old Public Folder move requests:
Get-PublicFolderMoveRequest | Remove-PublicFolderMoveRequest
Public folder Migration Script
If you want a more visual way to monitor the progress, you can use a script like this:
CLS $Counter = 1 Do { $Stats = Get-PublicFolderMoveRequest | Get-PublicFolderMoveRequestStatistics $Status = $Stats.Status $StatusDetail = $Stats.StatusDetail $SyncStage = $Stats.SyncStage $BadItems = $Stats.BadItemsEncountered $BadItemLimit = $Stats.BadItemLimit If ($Status -eq 'Completed') { Write-Host ' ' Write-Host 'COMPLETED!!' -ForegroundColor Green Write-Host ' ' Exit } If ($Status -eq 'Failed') { Write-host ' ' Write-host 'FAILED!' -ForegroundColor Red Write-host ' ' Get-PublicFolderMoveRequest | Get-PublicFolderMoveRequestStatistics | ft status*,Failure* -auto Write-host ' ' Exit } $Report = Get-PublicFolderMoveRequest | Get-PublicFolderMoveRequestStatistics -IncludeReport | Select Report $Test = $Report.Report.Entries $Test2 = $Test.LocalizedString $AllEntries = $Test2.LocalizedString | select LocalizedString #Last Lines $LastEntry = $AllEntries[-1] $AllSplit = $LastEntry -Split(' ') Write-Host 'Current Status:' -ForegroundColor Green $Value1 = $AllSplit[8] $Value2 = $AllSplit[9] $Value3 = $AllSplit[-3] $Value4 = $AllSplit[-2] $Test = $Value1 -match "[0-9]\.[0-9]*" # Write-Host "Value1 is $Value1" # Write-Host "Test = $Test" # $Test = $True If ($Test) { Write-Host "$Status --> $StatusDetail" -ForegroundColor Cyan Write-Host "Data Copied: $Value1 $Value2" -ForegroundColor White Write-Host "Folder progress: $Value3 $Value4" -ForegroundColor White Write-Host "Bad Items Found: $BadItems" -ForegroundColor Yellow } If (!$Test) { $EndValueCheck = '*Percent complete: 95.' $EndValueCheck2 = '*Final sync has started.' If (($LastEntry -like $EndValueCheck) -or ($LastEntry -like $EndValueCheck2)) { Write-Host 'Migration at 95%. Folder migrations are finalizing....' -ForegroundColor Yellow Write-Host "$Status --> $StatusDetail" -ForegroundColor Cyan $NFCounter = 0 $FCounter = 0 $FolderList = (Get-PublicFolderMoveRequest |Get-PublicFolderMoveRequestStatistics ).FolderList $TotalFolders = $FolderList.Count Foreach ($Line in $FolderList) {$TestValue = $Line.IsFinalized;If ($TestValue -like 'Tr*') {$FCounter++}} Foreach ($Line in $FolderList) {$TestValue = $Line.IsFinalized;If ($TestValue -like 'Fa*') {$NFCounter++}} Write-Host "$FCounter / $TotalFolders Folders are completed ..." -ForegroundColor Green Write-Host "$NFCounter folders remain to be completed ..." -ForegroundColor Yellow } Else { Write-Host "$Status --> $StatusDetail" -ForegroundColor Cyan Write-Host 'Waiting to begin processing folders....' -ForegroundColor Yellow } } $InnerCounter = 0 # Time indicator: Do { Write-Host '.' -NoNewline -ForegroundColor Yellow Start-sleep 1 $InnerCounter++ } While ($InnerCounter -lt 30) $Time = $Counter*30 Write-Host "$Time Seconds have passed." -ForegroundColor Green $Counter++ } While ($Counter -lt 10000)
This provides visual elements like the below:
Watching the end of the migration, with the various stages and status messages that are relayed: