To migrate modern public folders from Exchange Server to Exchange Online, you generate a folder-to-mailbox mapping CSV with Microsoft's scripts, create matching public folder mailboxes in Exchange Online, and run a single public folder migration batch with New-MigrationBatch and Start-MigrationBatch. When the batch is Synced, you lock the on-premises folders, run Complete-MigrationBatch, test with a few users, and then switch the organization to Exchange Online public folders with Set-OrganizationConfig. Plan for at least 48 hours of public folder downtime during the final sync.
Who this is for and what you will have at the end
This guide is for Exchange administrators in a hybrid organization whose public folders live on Exchange Server 2016, Exchange Server 2019 or Exchange Server Subscription Edition, and who have already moved, or are about to finish moving, the user mailboxes. Microsoft's guidance is to complete the user migration before migrating public folders, and users who need public folder access should be migrated first.
By the end you will have:
- A validated source hierarchy and snapshots to compare against.
- Public folder mailboxes in Exchange Online sized from your own statistics.
- A completed public folder migration batch, tested and unlocked.
- Mail-enabled public folders routing correctly, and a rollback plan you didn't need.
Legacy public folders on Exchange Server 2010 follow a different procedure and aren't covered here.
Limits and requirements to check first
| Item | Requirement or limit |
|---|---|
| Source version | Exchange Server 2016 CU4 or later, any Exchange Server 2019 version, or Exchange Server Subscription Edition. (The procedure also lists Exchange Server 2013 CU15 or later.) Microsoft recommends installing the latest CU and SU first. |
| Active Directory | After the CU upgrade, prepare Active Directory, or the migration fails and parameters such as PublicFolderMailboxesLockedForNewConnections are missing. |
| Target mailboxes | Up to 100 public folder mailboxes for migration (up to 1,000 can be created afterwards), each up to 100 GB. |
| Total size | 5 TB is the maximum recommended, based on filling each target mailbox to 50 percent. |
| Single folder | Trim or split any single public folder larger than 25 GB (child folders don't count toward it). |
| Batches | Exactly one public folder migration batch. |
| Dumpster | More than 10,000 immediate subfolders under \NON_IPM_SUBTREE\DUMPSTER_ROOT can cause failure. |
| Tools | Exchange Management Shell on-premises and Exchange Online PowerShell. The EAC can't run the migration. |
| Permissions | Organization Management in Exchange Online; Organization Management or Server Management on-premises. |
| MRS Proxy | Enabled on at least one Exchange server that also hosts public folder mailboxes. |
Two settings can delete content before you even start: check Get-OrganizationConfig | Format-List DefaultPublicFolderAgeLimit and the AgeLimit on individual folders, so automatic deletions don't remove content you expect to migrate.
Microsoft recommends against using Outlook's PST export to migrate public folders, and specifically when the on-premises hierarchy is larger than 30 GB. Public folder mailboxes in Exchange Online grow through auto-split, which can't keep up with a sudden bulk import, and you can wait up to two weeks for it to move data out of a full primary mailbox. Permissions are also lost with PST export.
Step 1: Download the scripts and validate the source
Download the scripts from Exchange Server Public Folders Migration Scripts (https://aka.ms/PublicFolderScripts) and the pre-migration scripts (https://aka.ms/ssv2) into one folder, for example C:\PFScripts:
| Script | Purpose |
|---|---|
SourceSideValidations.ps1 | Scans the source public folders and reports issues known to slow or break migration, with fixes. |
Sync-ModernMailPublicFolders.ps1 | Synchronizes mail-enabled public folder objects to Exchange Online. |
Export-ModernPublicFolderStatistics.ps1 | Creates the folder name to size and deleted item size file. |
ModernPublicFolderToMailboxMapGenerator.ps1 | Creates the folder-to-mailbox mapping file. |
SetMailPublicFolderExternalAddress.ps1 | Points on-premises mail-enabled public folders at their Exchange Online counterparts after migration. |
Run SourceSideValidations.ps1 on an on-premises Mailbox server first and fix what it reports. It covers the general prerequisites: no orphaned public folder mail objects in Active Directory, matching SMTP addresses between Active Directory and Exchange, and no duplicate public folder objects.
Step 2: Prepare on-premises
In the Exchange Management Shell:
- Create the well-known accepted domain that keeps mail flowing to mail-enabled public folders while DNS caches catch up after the migration. Use your Exchange Online routing domain, which the Hybrid Configuration Wizard has already configured a send connector for:
New-AcceptedDomain -Name PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99 -DomainName "contoso.mail.onmicrosoft.com" -DomainType InternalRelayIf the domain already exists on-premises, rename it to that name instead of creating a second one.
- Find and rename folders whose names contain a backslash or forward slash, because they may not land in their designated mailbox:
Get-PublicFolder -Recurse -ResultSize Unlimited |
Where {$_.Name -like "*\*" -or $_.Name -like "*/*"} |
Format-List Name, Identity, EntryId
Set-PublicFolder -Identity "<public folder EntryId>" -Name "<new public folder name>"- If this is a repeat attempt, check for flags from a previous migration and reset them only after you confirm that attempt can be discarded:
Get-OrganizationConfig | Format-List PublicFolderMailboxesLockedForNewConnections, PublicFolderMailboxesMigrationCompleteIf either value is True and the earlier attempt can be discarded, reset both:
Set-OrganizationConfig -PublicFolderMailboxesLockedForNewConnections:$false -PublicFolderMailboxesMigrationComplete:$false- Take snapshots for comparison after the migration:
Get-PublicFolder -Recurse -ResultSize Unlimited | Export-CliXML OnPrem_PFStructure.xml
Get-PublicFolderStatistics -ResultSize Unlimited | Export-CliXML OnPrem_PFStatistics.xml
Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderClientPermission |
Select-Object Identity,User,AccessRights -ExpandProperty AccessRights | Export-CliXML OnPrem_PFPerms.xml
Get-MailPublicFolder -ResultSize Unlimited | Export-CliXML OnPrem_MEPF.xml-
If you use Microsoft Entra Connect, open it, select Configure > Customize synchronization options, and on Optional Features make sure Exchange Mail Public Folders is cleared. Clearing it removes the synchronized mail-enabled public folder objects from Entra ID; if more than 500 are removed you may need to allow the deletion past the accidental-delete protection.
-
Record who has Send As and Send on Behalf on mail-enabled public folders, because those permissions aren't migrated:
Get-MailPublicFolder | Get-ADPermission | ?{$_.ExtendedRights -like "*Send-As*"}
Get-MailPublicFolder | ?{$_.GrantSendOnBehalfTo -ne "$null"} | Format-Table Name, GrantSendOnBehalfToStep 3: Prepare Exchange Online
In Exchange Online PowerShell, confirm there is no earlier public folder batch and no existing public folder hierarchy:
Get-MigrationBatch | ?{$_.MigrationType.ToString() -eq "PublicFolder"}
Get-Mailbox -PublicFolder
Get-PublicFolder -RecurseIf the first command returns a failed or abandoned public folder batch, remove it, or your new batch will fail:
Remove-MigrationBatch <name of migration batch> -Confirm:$falseIf public folders already exist in Exchange Online, find out who created them and why before removing anything; the removal commands in Microsoft's procedure permanently delete the content. Also confirm the public folder quotas in Exchange Online aren't below 25 GB. You can raise them with Set-OrganizationConfig and the DefaultPublicFolderIssueWarningQuota and DefaultPublicFolderProhibitPostQuota parameters.
Step 4: Generate the mapping files
On-premises, export the folder statistics. The output has three columns, FolderName, FolderSize and DeletedItemSize, in bytes:
.\Export-ModernPublicFolderStatistics.ps1 stats.csvThen generate the folder-to-mailbox map:
.\ModernPublicFolderToMailboxMapGenerator.ps1 -MailboxSize 50GB -MailboxRecoverableItemSize 1GB -ImportFile .\stats.csv -ExportFile map.csvMailboxSize is the most you want to place in any one target mailbox. The maximum is 100 GB, and Microsoft recommends about half of that to leave room for growth. The recoverable items size is the dumpster quota for the target mailboxes; Microsoft recommends 15 GB or less.
The map isn't a list of every folder. It names the roots of large subtrees and individually large folders; children follow their parent into the same target mailbox unless a separate line sends them elsewhere. The script uses generic target names such as Mailbox1 and Mailbox2. Rename them in Notepad to fit your naming policy, and make sure none collide with existing mailbox names. Keep the total at 100 unique target mailboxes or fewer.
Step 5: Create the target public folder mailboxes
Copy map.csv to the computer where you run Exchange Online PowerShell (the examples use C:\PFScripts). Then create the primary hierarchy mailbox on hold for migration, followed by the rest, all serving the hierarchy:
$mappings = Import-Csv C:\PFScripts\map.csv
$primaryMailboxName = ($mappings | Where-Object FolderPath -eq "\").TargetMailbox
New-Mailbox -HoldForMigration:$true -PublicFolder -IsExcludedFromServingHierarchy:$false $primaryMailboxName
($mappings | Where-Object TargetMailbox -ne $primaryMailboxName).TargetMailbox | Sort-Object -Unique |
ForEach-Object { New-Mailbox -PublicFolder -IsExcludedFromServingHierarchy:$false $_ }Leaving any mailbox with IsExcludedFromServingHierarchy set to $true is a known cause of failed public folder migrations.
Step 6: Create and start the migration batch
- On-premises, from a server hosting public folder mailboxes, sync the mail-enabled public folders to Exchange Online:
.\Sync-ModernMailPublicFolders.ps1 -CsvSummaryFile:sync_summary.csv- On-premises, get the GUID of the primary hierarchy mailbox. It must come from on-premises; a GUID taken from Exchange Online makes the batch fail with a transient error:
(Get-OrganizationConfig).RootPublicFolderMailbox.HierarchyMailboxGuid.GUID- In Exchange Online PowerShell, create the endpoint and the batch, then start it:
$Source_Credential = Get-Credential contoso\pfadmin
$Source_RemoteServer = "mail.contoso.com"
$bytes = [System.IO.File]::ReadAllBytes('C:\PFScripts\map.csv')
$PfEndpoint = New-MigrationEndpoint -PublicFolder -Name PublicFolderEndpoint -RemoteServer $Source_RemoteServer -Credentials $Source_Credential
New-MigrationBatch -Name PublicFolderMigration -CSVData $bytes -SourceEndpoint $PfEndpoint.Identity -SourcePfPrimaryMailboxGuid <GUID from step 2> -NotificationEmails admin@contoso.com
Start-MigrationBatch PublicFolderMigration$Source_RemoteServer is the internet-routable FQDN of your MRS Proxy endpoint. If New-MigrationBatch fails with "Cannot find a recipient that has mailbox GUID" for a public folder mailbox in Exchange Online, Active Directory replication hasn't caught up; wait an hour and retry.
Monitor progress with:
Get-MigrationBatch PublicFolderMigration
Get-PublicFolderMailboxMigrationRequest | Get-PublicFolderMailboxMigrationRequestStatisticsWhen the batch reaches Synced with no errors, run the four on-premises snapshot commands from Step 2 again so your comparison files reflect the current state.
Public folder migrations use the Data Consistency Score. A grade of Investigate requires you to approve skipped items before completion; the TooManyBadItemsPermanentException guide explains how to review and approve them. After the initial copy, the batch syncs from the source once every 24 hours.
Step 7: Lock the on-premises public folders (downtime starts)
Before you lock anything, confirm recent syncs. LastSyncedDate on the batch and LastSuccessfulSyncTimestamp on each request should be within the last seven days:
Get-MigrationBatch | ?{$_.MigrationType -like "*PublicFolder*"} | Format-Table *last*sync*
Get-PublicFolderMailboxMigrationRequest | Get-PublicFolderMailboxMigrationRequestStatistics |
Format-Table TargetMailbox, *last*sync*Rerun Sync-ModernMailPublicFolders.ps1 to pick up new mail-enabled folders, then lock on-premises:
Set-OrganizationConfig -PublicFolderMailboxesLockedForNewConnections $trueUsers are logged off public folders, and mail to mail-enabled public folders queues until the migration finishes. With public folder mailboxes on several servers, wait for Active Directory replication. When Get-PublicFolder \ on-premises returns Couldn't find the public folder mailbox, the folders are locked.
Step 8: Complete the batch
On-premises, make sure no other public folder or public folder mailbox moves exist (Get-MoveRequest, Get-PublicFolderMoveRequest) and remove any that are in progress or Completed. Then, in Exchange Online PowerShell:
Complete-MigrationBatch PublicFolderMigrationThe status goes from Synced to Completing to Completed. It commonly stays on Synced for a few hours, and with many target mailboxes more than 24 hours is normal as long as no request has failed or been quarantined. After completion, nothing more can be synchronized from on-premises.
Step 9: Test, unlock and finalize
- Point a few test users at a migrated public folder mailbox and test in Outlook: view the hierarchy, check permissions, create and delete folders, post and delete items. Changes can take 15 to 30 minutes to appear.
Set-Mailbox -Identity testuser@contoso.com -DefaultPublicFolderMailbox Mailbox1- Unlock public folders in Exchange Online:
Set-OrganizationConfig -RemotePublicFolderMailboxes $Null -PublicFoldersEnabled Local- On-premises, back up queued messages addressed to mail-enabled public folders using the export command in Microsoft's procedure, then stamp the external addresses and mark the migration complete:
.\SetMailPublicFolderExternalAddress.ps1 -ExecutionSummaryFile:mepf_summary.csv
Set-OrganizationConfig -PublicFolderMailboxesMigrationComplete:$true -PublicFoldersEnabled Remote- Re-apply what isn't migrated. Permissions on the root folder and on
\NON_IPM_SUBTREE\EFORMS REGISTRYneedAdd-PublicFolderClientPermission. Send As needsAdd-RecipientPermission -AccessRights SendAs, and Send on Behalf needsSet-MailPublicFolder -GrantSendOnBehalfTo.
Verification
Take the same four snapshots in Exchange Online and compare them with the on-premises files:
Get-PublicFolder -Recurse -ResultSize Unlimited | Export-CliXML Cloud_PFStructure.xml
Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderStatistics | Export-CliXML Cloud_PFStatistics.xml
Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderClientPermission |
Select-Object Identity,User,AccessRights | Export-CliXML Cloud_PFPerms.xml
Get-MailPublicFolder -ResultSize Unlimited | Export-CliXML Cloud_MEPF.xmlKeep the on-premises public folder mailboxes for a few weeks while you monitor. Removing them is irreversible.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Error creating a second public folder batch | Only one public folder batch is allowed | Remove the old batch with Remove-MigrationBatch first. |
| Jobs stalled or failing intermittently | Too many jobs in parallel | Set-MigrationEndpoint PublicFolderEndpoint -MaxConcurrentMigrations 30 -MaxConcurrentIncrementalSyncs 20 -SkipVerification |
| "Dumpster of the Dumpster folder" | Known issue | Stop the batch and restart it. |
| Request quarantined: "The given key wasn't present in the dictionary" | Corrupt item in a folder | Stop the batch, move the affected folder to the primary public folder mailbox on-premises with New-PublicFolderMoveRequest, remove the move request, restart the batch. |
PublicFolderMailboxesLockedForNewConnections not recognized | Active Directory not prepared after the CU upgrade | Prepare Active Directory and domains. |
| External mail to a mail-enabled public folder fails with 5.7.13 or 5.4.1 | Anonymous can't create items, or Directory-Based Edge Blocking rejects it | Grant anonymous CreateItems and disable DBEB for that domain. |
| Final sync very slow | Large changes, or many corrupt ACLs not cleaned up | Plan for at least 48 hours; fix the issues SourceSideValidations.ps1 reports before you start. |
Rolling back
If testing fails before you go live, unlock on-premises with Set-OrganizationConfig -PublicFolderMailboxesLockedForNewConnections:$false -PublicFolderMailboxesMigrationComplete:$false -PublicFoldersEnabled Local (the unlock can take several hours), revert the ExternalEmailAddress values using OnPrem_MEPF.xml or the script summary, remove the Exchange Online public folders and mailboxes, and run Set-OrganizationConfig -PublicFoldersEnabled Remote in Exchange Online. Anything added in Exchange Online after the migration is lost unless you export it first.
Checklist
- Users migrated first; source on a supported CU with Active Directory prepared.
- Validation script clean; no folder over 25 GB; no slashes in names; age limits checked.
- Snapshots and Send As / Send on Behalf lists saved.
- Map generated at about 50 percent mailbox fill, 100 targets or fewer.
- One batch created with the on-premises hierarchy GUID; skipped items reviewed.
- Recent syncs confirmed, folders locked, batch completed.
- Tested with a few users, unlocked, external addresses stamped, migration marked complete.
- Snapshots compared; missing permissions re-applied.
References
- Batch migrate Exchange Server public folders to Microsoft 365
- Roll back a public folder migration from Exchange Server to Exchange Online
- Track and prevent migration data loss in Exchange Online
- Get-PublicFolderMailboxMigrationRequestStatistics
- New-MigrationBatch
- Complete-MigrationBatch
- Set-OrganizationConfig