Migrating Hybrid Public Folders to Office 365

Migrating Hybrid Public Folders to Office 365

4.8 / 5 ( 20 votes )

Update: Shameless plug: I’ve written more extensively about public folder migrations from both the 2007/2010 and 2013/2016 perspectives in the book, “Office 365 Administration: Inside Out,” available at http://aka.ms/thebookonit.

So, tonight I started the last phase of one of my longest-running projects since joining Microsoft–an Exchange Online migration for a school district that I began nearly a year and a half ago.  40,000 mailboxes down and 13,000 public folders remaining.

One of the things that we recommend for Hybrid Public Folders is that you run the Sync-MailPublicFolders.ps1 script daily.  This can be a pain (as someone has to remember do it), and because it requires an Office 365 credential, you have two options: modify and hard code it in the script or store it in a more secure location, such as a service account’s local registry.

// I will digress here momentarily

While we don’t advocate storing passwords in plain-text in a script, I will show you how it’s done for this script for purposes of education.  I also have code here that will show you how to do the same procedure, only encoding the data in the registry under the service account’s context (which is what we did in this case).

To make this change for the purpose of testing automation:

  • Comment out the Credential parameter (lines 47 and 48)
  • Set the CsvSummaryFile output (line 52) to a file that will just get overwritten every day.
  • Add a code block for the credential (insert the following after the param() block and before the WriteInfoMessage() function)
# Password/credential
$userAdmin = "tenantadmin@tenant.onmicrosoft.com"
$userAdminPass = "Password123"
$securePassword = ConvertTo-SecureString $userAdminPass -AsPlainText -Force
$global:Credential = New-Object -TypeName System.Management.Automation.PSCredential -argumentlist $userAdmin,$securePassword

It will look something like this:

This will now allow the Sync-MailPublicFolders.ps1 script to run as a scheduled task.  Please, for the love all that is holy, don’t do this in production–use a more secure method to store credentials in an encrypted fashion (such as my method of storing a password in the registry).

Now, back to our regularly scheduled post.



  • Planning for the time to do an event like this is really a crap shoot.  This customer moved 40,000 mailboxes with 2TB of data over the course of about 40 days.  This public folder data migration had 131GB of data and took 6 full days.  Since legacy public folders are migrated into Public Folder Mailboxes (modern public folders), they behave more like mailbox migrations and can be simultaneous.  Your biggest bottlenecks will be read I/O performance on your source environment and the bandwidth + latency to get to an Office 365 datacenter.
  • Every time I tell you to run something from your Exchange On-Premises organization, I mean “run this from the Exchange Management Shell in on your Public Folder server, unless otherwise specified.”
  • Every time I tell you to do something in your Exchange Online or Office 365 tenant, I mean “run this from a PowerShell session connected to Office 365 FROM YOUR EXCHANGE ON-PREMISES SERVER.”  You can do it otherwise, but this way will save you a lot of copying and pasting and frustration.
    • If your on-premises organization is 2007, you’ll need to just connect to the Exchange Online endpoint (you won’t need to nor will you be able to install the Windows Azure AD PowerShell for the MSOL cmdlets, so use this three-liner to get connected to Exchange Online directly:
      $o365Cred = Get-Credential
      $o365Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid -Authentication Basic -AllowRedirection -Credential $o365Cred
      Import-PSSession $o365Session

Preparing the Environment

  1. When I set up my customer for Hybrid Public Folders, I use a script that I put together to identify public folders with invalid characters.  This has helped many customers avoid nasty scenarios.   I stole the idea for the name (IDFix) from our tool of a similar name. You should run it in your environment to see what you get back. You can get it here: IDFix for Public Folders.
  2. Before I even began the night’s activities, I needed to make sure the SyncMailPublicFolders.ps1 script I had automated was stopped.  No sense ruining things before I even start.
  3. The first thing I had to do is make sure that my customer had no existing Public Folder migrations.  Just because I didn’t start one doesn’t mean they didn’t.
    Run from both Exchange Online and your Exchange On-Premises Servers
    Get-OrganizationConfig | FL Public*Migration*
    PublicFoldersLockedForMigration        : False
    PublicFolderMigrationComplete          : False
    PublicFolderMailboxesMigrationComplete : False
  4. If you have True reported for any of those, STOP.  Do not pass GO, do not collect $200.  You’ll have to do some cleanup first.
    Run this from your Exchange On-Premises Org.
    Set-OrganizationConfig -PublicFoldersLockedforMigration:$false -PublicFolderMigrationComplete:$false
  5. Run this from your Office 365 tenant to make sure no one has tried monkeying around with public folder migrations behind your back.  The first command will delete any public folder migration requests.  The following commands will look for public folder content.  If existing public folders are found, you may want to back them up.  The commands listed below will remove existing public folder content in your Office 365 tenant, so you may want to run them individually to see if anything exists, reserving the Remove-* cmdlets. Or, you may not care.
    Get-PublicFolderMigrationRequest | Remove-PublicFolderMigrationRequest -Confirm:$False
    $PFMigrationBatch = Get-MigrationBatch | ? { $_.MigrationType.ToString() -eq "Public Folder" }
    $PFMigrationBatch | Remove-MigrationBatch -Confirm:$False
    Get-Mailbox -PublicFolder -ea SilentlyContinue
    Get-PublicFolder -ea SilentlyContinue
    Get-MailPublicFolder | where {$_.EntryId -ne $null}| Disable-MailPublicFolder -Confirm:$false 
    Get-PublicFolder -GetChildren \ | Remove-PublicFolder -Recurse -Confirm:$false
    $hierarchyMailboxGuid = $(Get-OrganizationConfig).RootPublicFolderMailbox.HierarchyMailboxGuid
    Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -ne $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false
    Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -eq $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false
  6. Now that we’re moderately confident that we can move forward, we need to make sure that we don’t exceed the folder quota for any posts that might be migrating in.
    Get-OrganizationConfig | fl *quot*
    DefaultPublicFolderIssueWarningQuota : 1.7 GB (1,825,361,920 bytes)
    DefaultPublicFolderProhibitPostQuota : 2 GB (2,147,483,648 bytes)

    No, that definitely won’t do.

  7. So, we update the Public Folder quotas.
    Set-OrganizationConfig -DefaultPublicFolderIssueWarningQuota 9.5GB -DefaultPublicFolderProhibitPostQuota 10GB
  8. Next, it’s time to gather some data before we begin.  Since my customer’s source environment was Exchange 2007, I needed to log into one of those servers and launch the Exchange Management Shell there.  I like to do belt and suspenders, so I ran some extra data gathering commands.  You can run this if you like.  Your mileage may vary, but I’ve found it helpful in the past to have backups of your backups.
    Get-PublicFolder -Recurse -ResultSize Unlimited | Export-Clixml .\LegacyPFStructure.xml
    Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderStatistics | Export-Clixml .\LegacyPFStatisticsRecurse.xml
    Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderClientPermission | Select-Object Identity,User -ExpandProperty AccessRights | Export-Clixml .\LegacyPFPerms.xml
  9. But, I also grabbed our Public Folder Migration Scripts and saved them to my customer’s Exchange 2007 server in C:\PFScripts (just like the document says, because I’m lazy).
  10. The next step is an internal relay domain with a well-known name that we can use to route mail during the transition.  Run this from your Exchange On-Premises Org.
    New-AcceptedDomain -Name "PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99" -DomainName tenant.onmicrosoft.com -DomainType InternalRelay
  11.  Just in case we missed anything in the Public Folder names, we’re going to check and make sure.  If you’re still unlucky enough to be running Exchange 2007 like this customer is, this is the command that you’ll run.  Run this from your Exchange On-Premises Org.
    Get-PublicFolderDatabase | ForEach {Get-PublicFolderStatistics -Server $_.Server | Where {$_.Name -like "*\*"}}

    If you’re in the modern era and have upgraded to Exchange 2010, you can use this command:

    Get-PublicFolderStatistics -ResultSize Unlimited | Where {($_.Name -like "*\*") -or ($_.Name -like "*/*") } | Format-List Name, Identity
  12. One of the meh things about Public Folder migrations is that Send-As and Send-On-Behalf permissions aren’t migrated.  So, we’re going to grab them as well so we can apply them later.  Run this from your Exchange On-Premises Org.
    Get-MailPublicFolder -ResultSize Unlimited | Get-ADPermission | ? {($_.ExtendedRights -Like "Send-As") -and ($_.IsInherited -eq $False) -and -not ($_.User -like "*S-1-5-21-*")} | Select Identity,User | Export-Csv Send_As.csv -NoTypeInformation


    Get-MailPublicFolder | Select Alias,PrimarySmtpAddress,@{N="GrantSendOnBehalfTo";E={$_.GrantSendOnBehalfTo -join "|"}} | Export-Csv GrantSendOnBehalfTo.csv -NoTypeInformation
    $File = Import-Csv .\GrantSendOnBehalfTo.csv
    $Data = @()
    Foreach ($line in $File)
        If ($line.GrantSendOnBehalfTo)
            Write-Host -ForegroundColor Green "Processing Public Folder $($line.Alias)"
            [array]$LineRecipients = $line.GrantSendOnBehalfTo.Split("|")
            Foreach ($Recipient in $LineRecipients)
                Write-Host -ForegroundColor DarkGreen "     $($Recipient)"
                $GrantSendOnBehalfTo = (Get-Recipient $Recipient).PrimarySmtpAddress
                $LineData = New-Object PSCustomObject
                $LineData | Add-Member -Type NoteProperty -Name Alias -Value $line.Alias
                $LineData | Add-Member -Type NoteProperty -Name PrimarySmtpAddress -Value $line.PrimarySmtpAddress
                $LineData | Add-Member -Type NoteProperty -Name GrantSendOnBehalfTo -Value $GrantSendOnBehalfTo
                $Data += $LineData
    $Data | Export-Csv .\GrantSendOnBehalfTo-Resolved.csv -NoTypeInformation
  13. Now, for the funsies.  I’m going to make a new sub-directory to store my working files in.  Again, for reference, I’ve saved all of the Microsoft-provided Public Folder scripts in C:\PFScripts.  Run this on your Exchange On-Premises Org.
    cd \PFScripts
    Mkdir C:\PFScripts\PFMigrate
    .\Export-PublicFolderStatistics.ps1 C:\PfScripts\PFMigrate\PFStatistics.csv publicfolderserver.domain.com
  14.  While processing, I saw this interesting bit of information:
    [4/8/2017 3:31:30 AM] Enumerating folders under IPM_SUBTREE...
    [4/8/2017 3:42:04 AM] Enumerating folders under IPM_SUBTREE completed...13039 folders found.
    [4/8/2017 3:42:04 AM] Enumerating folders under NON_IPM_SUBTREE...
    [4/8/2017 3:42:06 AM] Enumerating folders under NON_IPM_SUBTREE completed...118 folders found.
    [4/8/2017 3:42:06 AM] Retrieving statistics from server PUBFOLD01
    [4/8/2017 3:42:12 AM] Retrieving statistics from server PUBFOLD01 complete...13064 folders found.
    [4/8/2017 3:42:13 AM] Total unique folders found : 13064.
    [4/8/2017 3:42:13 AM] Retrieving statistics from server PUBFOLD02
    [4/8/2017 3:42:20 AM] Retrieving statistics from server PUBFOLD02 complete...13050 folders found.
    [4/8/2017 3:42:21 AM] Total unique folders found : 13072.
    [4/8/2017 3:42:21 AM] Exporting statistics for 13072 folders
    [4/8/2017 3:42:23 AM] Folders processed : 10000.
    [4/8/2017 3:42:24 AM] Exporting folders to a CSV file

    What am I supposed to do with this information?  The script clearly says that it found 13,072 unique folders, but this screen output makes it seem like it’s only processing 10,000.  And not like it ran into a problem 10,000.  This seems like a value that someone thought would be enough.  That doesn’t really sound like it’s going to work for my customer.

    “Hey, I saw you had 13,072 public folders, but I only decided to grab 10,000 of them.  Good luck!”

    Just let me know if you want any other advice for helping you free up your future to plan for another career.

    So, I did what any consultant would do, and decided to tinker.  I opened Export-PublicFolderStatistics.ps1 and searched for 10000.  Found it on line 154 inside of the CreateFolderObjects() function. Made a copy of the line, commented it out, and updated the number of 100000 and saved.  Yes, I know it’s supposed to chunk through them, but for some reason, it didn’t do it reliably for my cust

  15. Reran the command and received what I deemed to be better output.
    [4/8/2017 3:47:27 AM] Enumerating folders under IPM_SUBTREE...
    [4/8/2017 3:57:53 AM] Enumerating folders under IPM_SUBTREE completed...13039 folders found.
    [4/8/2017 3:57:53 AM] Enumerating folders under NON_IPM_SUBTREE...
    [4/8/2017 3:57:55 AM] Enumerating folders under NON_IPM_SUBTREE completed...118 folders found.
    [4/8/2017 3:57:55 AM] Retrieving statistics from server PUBFOLD01
    [4/8/2017 3:53:02 AM] Retrieving statistics from server PUBFOLD01 complete...13064 folders found.
    [4/8/2017 3:53:02 AM] Total unique folders found : 13064.
    [4/8/2017 3:53:02 AM] Retrieving statistics from server PUBFOLD02
    [4/8/2017 3:53:09 AM] Retrieving statistics from server PUBFOLD02 complete...13050 folders found.
    [4/8/2017 3:53:10 AM] Total unique folders found : 13072.
    [4/8/2017 3:53:10 AM] Exporting statistics for 13072 folders
    [4/8/2017 3:53:13 AM] Exporting folders to a CSV file
  16. Now it’s time to generate the Public Folder map file, which iterates through the CSV you just created and figures out how to distribute the public folders into Public Folder Mailboxes in Office 365.  In this case, I told the script to break the public folders at 10GB boundaries:
    .\PublicFolderToMailboxMapGenerator.ps1 10000000000 C:\pfscripts\PFMigrate\PFStatistics.csv C:\pfscripts\PFMigrate\PFMap.csv

    Data is returned that looks acceptable:

    [4/8/2017 4:02:22 AM] Reading public folder list...
    [4/8/2017 4:02:22 AM] Loading folder hierarchy...
    [4/8/2017 4:02:24 AM] Allocating folders to mailboxes...
    [4/8/2017 4:02:24 AM] Trying to accomodate folders with their parent...
    [4/8/2017 4:02:24 AM] Exporting folder mapping...

    In case you’re like me and always curious, you can look inside the PFMap.csv file (or whatever you call it, but hopefully you’re smart enough to figure that out without me explicitly pointing it out) to see what kind of data it generated:

    As you can see, it’s got two columns–one named “TargetMailbox” (which will be the Public Folder Mailbox in Office 365 into which that particular branch of the public folder tree will be placed) and the “FolderPath” (which is, not surprisingly, the aforementioned particular branch).  Sorry if I spoiled the surprise.  You didn’t come here for surprises.

Starting the Migration

  1. Switching over to the PowerShell session that is connected to Office 365, I run the following:
    cd \PFScripts
    .\Create-PublicFolderMailboxesForMigration.ps1 -FolderMappingCsv C:\pfscripts\PFMigrate\PFMap.csv -EstimatedNumberOfConcurrentUsers 40000

    I chose 40,000, since that’s the number of users in the organization that will most likely be connecting to and browsing public folders.  The script returns some data:

    Public Folder mailbox updates.
    Creating 14 Public Folder mailbox(es) and updating 0. Total mailboxes to serve hierarchy will be 20. Would you like to proceed?
    [Y] Yes  [N] No  [?] Help (default is "Y"):

    Well, duh. Of course I want to continue. I didn’t stay up until 4am to cancel a migration.

    [falls asleep]

    Ok, I’m back.  The recommended number of users per hierarchy-serving mailbox is 2,000.  Since I entered 40,000 users, the script has determined that I need at least 20 mailboxes to serve the public folder hierarchy.

    Carry on.

  2. After I hit Yes, the Create-PublicFolderMailboxesForMigration.ps1 script will go about its business of creating Public Folder mailboxes.  Run this from your Exchange Online Tenant.
    And now we wait.
  3. When it gets to the end, you can see that it created additional mailboxes:

    Onward and upward.
  4. Run the SyncMailPublicFolders.ps1 script one last time to make sure everything is working. If you already have hybrid public folders enabled, you know how to do this.  If you don’t:
    .\SyncMailPublicFolders.ps1 -Credential (Get-Credential) -CSVSummarFile:csvoutput.csv-Credential is your Office 365 global admin.
  5. Now, we have some additional information to gather.  You can save this into variables.  Run this from your Exchange on-premises org in the Exchange Management Shell.
    (Get-Mailbox <admin user>).legacyExchangeDN | Out-File .\MailboxLegacyExchangeDN.txt
    (Get-ExchangeServer <public folder server>).ExchangeLegacyDN | Out-File .\ServerExchangeLegacyDN.txt
    $OAEndpoint = ((Get-ExchangeServer | ? { $_.ServerRole -match "ClientAccess"})[0]|Get-OutlookAnywhere).ExternalHostName
    $OAEndpoint | Out-File .\OAEndpoint.txt

    That last one might be tricky if your org has multiple endpoints or if, in my customer’s case, you never updated external Autodiscover to the hybrid endpoint per our recommendation.  What you’re really after is the Autodiscover endpoint (2013 or later) that Office 365 will use to contact your on-premises environment.  In my customer’s environment, it’s hybrid.domain.edu, as is referenced in the Org Relationship:

    Get-OrganizationRelationship | fl TargetAutoDiscoverEpr
    TargetAutodiscoverEpr : https://hybrid.domain.edu/autodiscover/autodiscover.svc/WSSecurity

    So, I need *that* value instead.

    $OAEndpoint = "hybrid.domain.edu"
    $OAEndPoint | Out-File .\OAEndPoint.txt
  6. Now, you need to switch back over to the PowerShell prompt that is connected to Exchange Online (hopefully you’re running it from your PF server like I already told you to, or you’re going to have some copying and pasting to do).
    cd \PFScripts
    $OAEndopint = gc .\OAEndpoint.txt
    $MailboxLegacyExchangeDN = gc .\MailboxLegacyExchangeDN.txt
    $ServerExchangeLegacyDN = gc .\ServerExchangeLegacyDN.txt
    $Credential = Get-Credential <domain\admin user>
  7. Now, it’s time to create the Public Folder Migration Endpoint.  Run this in your Exchange Online Organization.
    $PFEndpoint = New-MigrationEndpoint -PublicFolder -Name PublicFolderEndPoint -RpcProxyServer $OAEndPoint -Credentials $Credential -SourceMailboxLegacyDN $MailboxLegacyExchangeDN -PublicFolderDatabaseServerLegacyDN $ServerExchangeLegacyDN -Authentication Basic
  8. Once the endpoint is created, I create the migration batch.
    New-MigrationBatch -Name PublicFolderMigration -CSVData (Get-Content .\PFMigrate\PFMap.csv -Encoding Byte) -SourceEndpoint $PFEndpoint.Identity -NotificationEmails <my email address>

    It processes for a minute, and then gives me some feedback.

    Identity                        Status                    Type                           TotalCount
    --------                        ------                    ----                           ----------
    PublicFolderMigration           Stopped                   PublicFolder                   14
  9. Time to get down to brass tacks.  I cross my fingers and start the migration batch.
    Start-MigrationBatch PublicFolderMigration

    And without any fanfare at all–no ribbons, no streamers, no Def Leppard songs over giant speakers–my migration begins.

  10. Before leaving my workstation to grab a Diet Coke (to celebrate my apparent victory over Public Folders), I decided run a quick check (Run this from your Exchange Online Org):
    Get-MigrationUser -BatchID PublicFolderMigration
    Identity                                 Batch                          Status                    LastSyncTime
    --------                                 -----                          ------                    ------------
    Mailbox1                                 PublicFolderMigration          Syncing                   4/8/2017 12:17:01 AM
    Mailbox2                                 PublicFolderMigration          Syncing                   4/8/2017 12:17:09 AM
    Mailbox3                                 PublicFolderMigration          Syncing                   4/8/2017 12:17:18 AM
    Mailbox4                                 PublicFolderMigration          Syncing                   4/8/2017 12:17:31 AM
    Mailbox5                                 PublicFolderMigration          Syncing                   4/8/2017 12:17:41 AM
    Mailbox6                                 PublicFolderMigration          Syncing
    Mailbox7                                 PublicFolderMigration          Validating
    Mailbox8                                 PublicFolderMigration          Validating
    Mailbox9                                 PublicFolderMigration          Validating
    Mailbox10                                PublicFolderMigration          Validating
    Mailbox11                                PublicFolderMigration          Validating
    Mailbox12                                PublicFolderMigration          Validating
    Mailbox13                                PublicFolderMigration          Validating
    Mailbox14                                PublicFolderMigration          Validating

    Sweet. Diet Coke it is.  I might even take some bourbon with it.

  11. Once all of the migration requests are underway, I tell the customer that Public Folders will be unavailable.  I lock the Public Folders so users can’t make changes.  Run this in the Exchange On-Premises Org.  Note: I did this early here because the customer requested it.  As soon as you lock the public folders, users will be disconnected.  You don’t need to lock them until cutover (later in the process).  Since the migration is using MRS, it can perform the sync online and keep users working until you get to the “Complete” stage.  As you’ll see, I had to troubleshoot this migration further, so I ended up having to unlock them anyway.  This step is not necessary at this point–and you’ll see, as I had to back it out later because of some problems discovered.
    Set-OrganizationConfig -PublicFoldersLockedForMigration:$true
  12. At this point, it’s going to be watching some paint dry, but I like seeing pretty statistics, so I’ll show you some more.  Run this from the Exchange Online Org.
    Get-MigrationUser -BatchId PublicFolderMigration | Get-MigrationUserStatistics | FT -auto
    Identity  Batch                 Status  Items Synced Items Skipped
    --------  -----                 ------  ------------ -------------
    Mailbox1  PublicFolderMigration Syncing 0            0
    Mailbox2  PublicFolderMigration Syncing 0            0
    Mailbox3  PublicFolderMigration Syncing 0            0
    Mailbox4  PublicFolderMigration Syncing 8279         0
    Mailbox5  PublicFolderMigration Syncing 0            0
    Mailbox6  PublicFolderMigration Syncing 0            0
    Mailbox7  PublicFolderMigration Syncing 0            0
    Mailbox8  PublicFolderMigration Syncing 0            0
    Mailbox9  PublicFolderMigration Syncing 2874         0
    Mailbox10 PublicFolderMigration Syncing 38535        0
    Mailbox11 PublicFolderMigration Syncing 0            0
    Mailbox12 PublicFolderMigration Syncing 3304         0
    Mailbox13 PublicFolderMigration Syncing 0            0
    Mailbox14 PublicFolderMigration Syncing 0            0

    There’s actually a bit more useful data that can be gathered, so I grabbed a few of the extra fields from Get-MigrationUserStatistics:

    Get-MigrationUser -BatchId PublicFolderMigration | Get-MigrationUserStatistics | Select Identity,Status,SyncedItemCount,SkippedItemCount,BytesTransferred,PercentageComplete | Sort -prop Status,PercentageComplete | Ft -auto
    Identity  Status  SyncedItemCount SkippedItemCount BytesTransferred               PercentageComplete
    --------  ------  --------------- ---------------- ----------------               ------------------
    Mailbox1  Syncing           32553              176 2.092 GB (2,246,307,901 bytes)                 40
    Mailbox2  Syncing           95156                0 5.021 GB (5,391,041,974 bytes)                 94
    Mailbox3  Syncing           82711                0 2.987 GB (3,207,776,110 bytes)                 47
    Mailbox4  Syncing           23625                0 8.373 GB (8,990,905,121 bytes)                 20
    Mailbox5  Syncing            1584                0 1.99 GB (2,136,923,781 bytes)                  94
    Mailbox6  Syncing           51243                0 2.562 GB (2,750,709,675 bytes)                 70
    Mailbox7  Syncing           17659                0 3.26 GB (3,500,029,634 bytes)                  47
    Mailbox8  Syncing            6472                0 7.234 GB (7,767,318,075 bytes)                 94
    Mailbox10 Syncing          281718                1 3.198 GB (3,434,352,125 bytes)                 37
    Mailbox11 Syncing          146567                0 4.09 GB (4,391,621,840 bytes)                  50
    Mailbox12 Syncing          102574                0 6.194 GB (6,650,300,317 bytes)                 94
    Mailbox13 Syncing           25702                0 3.951 GB (4,242,569,651 bytes)                 59
    Mailbox14 Syncing           29478                0 3.448 GB (3,702,518,318 bytes)                 48
    Mailbox9  Synced             2874                0 4.444 GB (4,771,626,709 bytes)                 95


  13. The next morning, I was monitoring the status of the migration and saw this error:
    Get-MigrationUser -BatchId PublicFolderMigration | Get-MigrationUserStatistics | Select Identity,Status,SyncedItemCount,SkippedItemCount,BytesTransferred,PercentageComplete | Sort -prop Status,PercentageComplete | Ft -auto
    WARNING: The subscription for the migration user Mailbox2 couldn't be loaded. The following error was encountered: A subscription wasn't found for this user..
    Identity  Status  SyncedItemCount SkippedItemCount BytesTransferred                PercentageComplete
    --------  ------  --------------- ---------------- ----------------                ------------------
    Mailbox7  Syncing          128906                0 10.06 GB (10,804,408,110 bytes)                 20
    Mailbox10 Syncing          579557                1 7.109 GB (7,633,236,986 bytes)                  54
    Mailbox1  Failed          196706              176 9.482 GB (10,180,836,340 bytes)                 94
    Mailbox2  Failed            95156                0
    Mailbox11 Synced           488624                0 11.87 GB (12,750,050,794 bytes)                 95
    Mailbox9  Synced             2874                0 4.444 GB (4,771,626,709 bytes)                  95
    Mailbox12 Synced           102898                0 6.299 GB (6,763,569,925 bytes)                  95
    Mailbox14 Synced           104686                0 10.79 GB (11,583,208,485 bytes)                 95
    Mailbox13 Synced            65194                0 8.003 GB (8,592,870,685 bytes)                  95
    Mailbox4  Synced            23625                0 8.414 GB (9,034,298,415 bytes)                  95
    Mailbox3  Synced           219617                0 9.275 GB (9,958,462,334 bytes)                  95
    Mailbox5  Synced             1597                0 2.025 GB (2,173,804,244 bytes)                  95
    Mailbox8  Synced             6472                0 7.276 GB (7,812,204,276 bytes)                  95
    Mailbox6  Synced            57514                0 3.712 GB (3,985,266,258 bytes)                  95

    Mailbox2 had failed.  Ugh.  No one likes to see that, so you know what I did?  I checked to see details about the failure.

    Get-MigrationUser mailbox2 | Select -ExpandProperty ErrorSummary
    Couldn't find a request that matches the information provided. Reason: No such request exists.

    So, no move request exists? Awesome.  No idea how that happened; perhaps there was a database failover in the background and the underlying move request failed.  I decided to not give it too much thought and just restarted the migration batch.  This will not affect existing migration users (and their underlying move requests), but it will restart failed migrations.

    What about the other one?

    Get-MigrationUser mailbox1 | Select -ExpandProperty ErrorSummary
    Error: This mailbox exceeded the maximum number of corrupted items that were specified for this move request.

    176 items? Seems like we have a pretty low threshold for failure. The only way to resolve this error is to either A) remove the offending items from the source public folder or B) increase the error threshhold.  Not being in the mood to search for items to delete, I confirm the choice with my customer and run:

    Set-MigrationBatch -BadItemLimit 10000

    And then:

    Start-MigrationBatch PublicFolderMigration

    After a few minutes, I decided to check the status again:

    Get-MigrationUser -BatchId PublicFolderMigration | Get-MigrationUserStatistics | Select Identity,Status,SyncedItemCount,SkippedItemCount,BytesTransferred,PercentageComplete | Sort -prop Status,PercentageComplete | Ft -auto
    Identity  Status  SyncedItemCount SkippedItemCount BytesTransferred                PercentageComplete
    --------  ------  --------------- ---------------- ----------------                ------------------
    Mailbox2  Syncing               0                0 16.24 MB (17,031,786 bytes)                     10
    Mailbox7  Syncing          128906                0 10.06 GB (10,804,408,110 bytes)                 20
    Mailbox10 Syncing          609683                1 7.454 GB (8,003,508,841 bytes)                  55
    Mailbox1  Syncing          196706              176 9.482 GB (10,180,836,340 bytes)                 94
    Mailbox11 Synced           488624                0 11.87 GB (12,750,050,794 bytes)                 95
    Mailbox9  Synced             2874                0 4.444 GB (4,771,626,709 bytes)                  95
    Mailbox12 Synced           102898                0 6.299 GB (6,763,569,925 bytes)                  95
    Mailbox14 Synced           104686                0 10.79 GB (11,583,208,485 bytes)                 95
    Mailbox13 Synced            65194                0 8.003 GB (8,592,870,685 bytes)                  95
    Mailbox4  Synced            23625                0 8.414 GB (9,034,298,415 bytes)                  95
    Mailbox3  Synced           219617                0 9.275 GB (9,958,462,334 bytes)                  95
    Mailbox5  Synced             1597                0 2.025 GB (2,173,804,244 bytes)                  95
    Mailbox8  Synced             6472                0 7.276 GB (7,812,204,276 bytes)                  95
    Mailbox6  Synced            57514                0 3.712 GB (3,985,266,258 bytes)                  95

    Seems well enough.

  14. At one point, one of the migration requests failed.  I ran a Get-MigrationUser <Identity> | Select -ExpandProperty ErrorSummary, and was greeted with this very non-helpful one message:
    Error: There are 30 Public Folders that could not be mail-enabled. Please, check the migration report starting at 4/9/2017 8:13:15 PM for additional details. This may indicate that mail public folder objects in Exchange Online are out of sync with your Exchange deployment. You may need to rerun the script Sync-MailPublicFolders.ps1 on your source Exchange server to update mail-enabled public folder objects in Exchange Online Active Directory.

    No further details available (or so I thought).  I exported the report and looked in the Failures node.  After much poking around and asking my peers, one of them sent me a script to try and suss it out.

    $resultsarray = @()
    $mailpub = Get-MailPublicFolder -ResultSize unlimited
    foreach ($folder in $mailpub) {
      $email      = $folder.primarysmtpaddress.local + "@" + $folder.primarysmtpaddress.domain
      $pubfolder  = Get-PublicFolder -Identity $folder.identity
      $folderpath = $pubfolder.parentpath + "\" + $pubfolder.name
      # Create a new object for the purpose of exporting as a CSV
      $pubObject = new-object PSObject
      $pubObject | add-member -membertype NoteProperty -name "Email" -Value $email
      $pubObject | add-member -membertype NoteProperty -name "FolderPath" -Value $folderpath
      # Append this iteration of our for loop to our results array.
      $resultsarray += $pubObject
    # Finally we want export our results to a CSV:
    $resultsarray | export-csv -Path C:\Temp\mail-enabled-public-folders.csv -NoType

    I then took the output:

    $NoPublicFolderPath = Import-Csv C:\Temp\mail-enabled-public-folders.csv | ? { $_.Parentpath -eq "" } | Export-Csv C:\Temp\NoFolderPath.csv -NoType
  15. From here, I was able to see what Mail-enabled public folders had addresses but no path to send to.  Launch ADUC, view Advanced Features, and then browse to the Microsoft Exchange System Objects container.
  16. Right-Click and Add columns for E-mail Address and Exchange Alias–that way, if your organization is like my customer’s and you have multiple objects with similar display names, you’ll easily be able to figure out which ones you need to remove.
  17. Remove the objects from the Microsoft Exchange System Container.
  18. Re-run Sync-MailPublicFolders.ps1.
  19. Re-start the migration batch.
  20. At this point, there the error count went down, but there were still a number of problems (again, reported as super helpful “There are 10 Exchange Public Folders that could not be mail-enabled.”).  I’m sorry, but…
  21. Another peer of mine pointed me towards this incredible script by Bill Long, and that was part of the secret sauce.  To use it, I had to get an export of the public folder tree from PFDAVAdmin (if you are using Exchange 2010, you’ll want to check out ExFolders) and I included the following fields:
    PR_ENTRYIDBut in order to use PFDAVAdmin, I had to unlock the Public Folders.  So:
    Set-OrganizationConfig -PublicFoldersLockedForMigration $false

    And wait about 15 minutes.

    Then, I launched PFDAVAdmin, connected to the public folder tree, and exported the fields necessary.

  22. I ran Bill’s script against that output file, re-ran Sync-MailPublicFolders.ps1 AGAIN, and now had synced set of public folders.  Thank you, tiny baby Jesus.  And then, just before cutting over, I decided to check the Get-MigrationUserStatistics.  Much to my dismay, I found the “There are 26 Exchange Public Folders that could not be mail enabled.” error.  Again.  But I knew it was all good?
  23. Back to Sync-MailPublicFolders.  I noticed an error when running:
    4/12/2017 6:11:16 PM,1a3a8d9e-0eb6-4b8a-bf00-a305f3229b9e,Update,"Active Directory operation failed on CY1PR14A001DC06.NAMPR14A001.PROD.OUTLOOK.COM. The object 'CN=Staff Folder,OU=tenant.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=NAMPR14A001,DC=PROD,DC=OUTLOOK,DC=COM' already exists.","Set-EXOMailPublicFolder -OnPremisesObjectId:""ae9563f4-1056-44c1-846a-c948c771720b"" -HiddenFromAddressListsEnabled:""False"" -ExternalEmailAddress:""StaffFolder@domain.edu"" -Alias:""StaffFolder"" -EmailAddresses:@(""X400:C=US;A= ;P=ORG;O=Exchange;S=StaffFolder;"",""SMTP:StaffFolder@domain.edu"",""x500:/O=ORG/OU=EXCHANGE/CN=RECIPIENTS/CN=STAFFC89080BC4725C2AEEDFB74A5292C16AE6F7CEE"",""smtp:StaffFolder@tenant.onmicrosoft.com"") -Name:""Staff Folder"" -Identity:""CN=Staff Folder,OU=tenant.onmicrosoft.com,OU=Microsoft Exchange Hosted Organizations,DC=NAMPR14A001,DC=PROD,DC=OUTLOOK,DC=COM"" -ErrorAction:""Stop"" -WindowsEmailAddress:""StaffFolder@domain.edu"" -DisplayName:""Staff Folder"""

    What do you mean “The object already exists?”  I know it does–I’m trying to update it.

  24. The output file also contains the command it was trying to run.  I copy / paste it into a PowerShell session and after tinkering with the quotes  am able to replicate the error.  I start removing the parameters one at a time until I found the one that was causing it to fail–name.  Turns out, the customer had multiple mail-enabled objects with the same name field.  I confirmed this on-premises by running:
    Get-Recipient -anr "Staff Folder"

    And was presented with both a distribution group and a public folder bearing the same name attribute.

  25. I ran Set-MailPublicFolder -Identity <email> -Name “Old Name + PF” and re-ran Sync-MailPublicFolders.ps1.  Completed successfully.
  26. Restarted the migration batch.  Again.  And now, the world is at peace.

Completing the Migration

  1. At this point, it’s time to finish off the beast.  From the On-Premises Exchange Management Shell:
    Set-OrganizationConfig -PublicFoldersLockedForMigration $True

    And wait.  Like about 10 minutes.  YMMV.  If you don’t wait long enough, you’ll receive this message when you go to run Complete-MigrationBatch in the cloud:

    Complete-MigrationBatch -Identity PublicFolderMigration
    Before finalizing the migration, it is necessary to lock down public folders on the legacy Exchange server (downtime required). Make sure public folder access is locked on the legacy Exchange server and then try to complete the batch again.
  2. I wait some more, try it again, and this time, get the prompt I’m looking for:
    Hells to the yeah.But alas, it was not to be.  Once more I received the “There are Exchange Public Folders that could not be mail enabled” error message, but to my utter dismay, the number had grown to 59.  It’s like fixing one kind of problem revealed yet another.  This is literally the opposite of completing! The next step was digging yet further into the public folder migration logs, using a tip from another of my way-smarter-than-me coworkers.  You see, while the “Failures” log file entry showed me *that* there was a failure, the *Messages* entries were what I actually needed to find the problem folders.
    Get-MigrationUserStatistics mailbox1 -IncludeReport | Export-Clixml .\mailbox1.xml
    $Mailbox1Report = Import-Clixml mailbox1.xml

    Now, where I had looked at the Failures node before ($Report.Report.Failures), I looked in $Report.Entries.Messages this time:

    $Entries = $Mailbox1Report.Report.Entries | Select Message -Last 300 | ? { $_.Message -like "*could not be mail-enabled*" }

    The result? Lines like this:

    Public folder "/Schools/School1 HS/School1 Guidance" could not be mail-enabled.
    Public folder "/Schools/School2 ES/Staff News" could not be mail-enabled.
    Public folder "/Schools/School1 HS/Yearbook" could not be mail-enabled.
    Public folder "/Schools/School3 HS/Staff News" could not be mail-enabled.
    Public folder "/Schools/School4 MS/Media" could not be mail-enabled.

    Indeed, the error log did log the errors. I just didn’t know where to look for it.

    These ended up being objects that had *some* attributes set for mail-enabled Public Folders on-premises, but not enough to actually be correct.  I quick run through the PF tree allowed me to “mail-disable” and “mail-re-enable” these public folders, which created the appropriate objects in the Microsoft Exchange System Objects container.

  3. And then again with the .\Sync-MailPublicFolders.ps1 from my customer’s on-premises Exchange 2007 Public Folder server.
  4. And then again with the Start-MigrationBatch in Exchange Online PowerShell, which brought the mailbox objects to “Synced” state.
  5. And then again with the Set-OrganizationConfig -PublicFoldersLockedForMigration $true on the on-premises Exchange 2007 server.
  6. And again with the waiting.
  7. From Exchange Online PowerShell, I then ran:
    Complete-MigrationBatch -Identity PublicFolderMigration -SyncAndComplete

    And waited.

    For 7 hours.

    And then, I saw this:

    At long last.

And on the 7th Day

After the migration completes, there are a few things left to do to make sure everything functioned as designed.  Now, just the tidying up:

Set the cloud public folders to local, and test public folders with a user (by default, they’ll select a PF, but for a test user, we’re going to set a default PF mailbox). From Office 365 / Exchange Online PowerShell:

Set-OrganizationConfig -PublicFoldersEnabled Local -RemotePublicFolderMailboxes $null
Get-Mailbox -PublicFolder | Set-Mailbox -PublicFolder -IsExcludedFromServingHierarchy $false
Set-Mailbox -Identity <test user> -DefaultPublicFolderMailbox mailbox1

Adding SendAs

You should probably put the permissions back the way you found them.

$SendAs = Import-Csv .\SendAs.csv
foreach ($obj in $SendAs) 
    write-host "$($i)/$($SendAs.Count) adding $($obj.User) to $($obj.Identity)"
    Add-RecipientPermission -Identity $obj.Identity.Split("/")[2] -Trustee $obj.User.Split("\")[1] -AccessRights SendAs -confirm:$false; $i++

Adding GrantSendOnBehalfTo

And give people their rights to send back.

$GrantSendOnBehalfTo = Import-Csv .\GrantSendOnBehalfTo-Resolved.csv
Foreach ($obj in $GrantSendOnBehalfTo)
    Write-host "$($i)/$($grantsendonbehalfto.count) Granting $($obj.GrantSendOnBehalfTo) Send-On-Behalf to folder $($obj.PrimarySmtpAddress)"
    Set-MailPublicFolder -Identity $obj.PrimarySmtpAddress -GrantSendOnBehalfTo $obj.GrantSendOnBehalfTo

Mark as complete

You probably want to tell the whole world (or at least your on-premises Exchange org) that the public folder migration is complete.

From Exchange On-Premises Management Shell

Set-OrganizationConfig -PublicFolderMigrationComplete $true

Configure for anonymous relay to mail-enabled Public Folders

Depending on how your Office 365 tenant is already configured, you may need to disable Directory-Based Edge Blocking (DBEB) in order to anonymous delivery to mail-enabled public folders to work.  DBEB doesn’t see MEPF’s as valid recipients, and will reject messages with a 550 5.4.1 “Recipient address rejected: Access denied.” message.  You can disable DBEB by setting the Exchange Online domain to InternalRelay:

Set-AcceptedDomain -Identity <domain> -DomainType InternalRelay

I also ran into this issue: During the Batch Migration, you’re instructed to create an internal relay domain for a well-known namespace:

New-AcceptedDomain -Name "PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99" -DomainName tenant.onmicrosoft.com -DomainType InternalRelay

However, you also need to add this to the Outbound to Office 365 Send Connector address space, or external mail destined for cloud MEPFs will go out the default send connector back to the internet and get rejected (if the customer has the MX pointed on-premises).  I’m putting a request in to have clarification made in our documentation to reflect that.  The

Much rejoicing, indeed.

Published by Aaron Guilmette

Helping companies conquer inferior technology since 1997. I spend my time developing and implementing technology solutions so people can spend less time with technology. Specialties: Active Directory and Exchange consulting and deployment, Virtualization, Disaster Recovery, Office 365, datacenter migration/consolidation, cheese. View all posts by Aaron Guilmette

Reader Comments

  1. MY PF migration batch has 9 PF mailboxes, on which 5 PF maiilbox content has been synced up without any issues. For the first 4 PF mailboxes it is at 10% “creating folder hierarchy stage”, not moving further. Ran the sourcesidevalidation script, based on the output removed orphaned permissions from the account. On all four mailboxes it is failing with this error “ErrorSummary : Error: The job encountered too many transient failures (61) and is quitting. The most common failure is MapiExceptionLogonFailed with the hit count 37. –>MapiExceptionLogonFailed: Unable to open message store. (hr=0x80040111, ec=-2147221231)” …Migration account has public folder management role, even provided owner permission on all PF folders, validate the system mailbox permissions migration account has permissions on it. Could you help in finding out the cause of it?

  2. If you have any customers complaint about the error: couldn‎’t be mail-enabled. The error is as follows: “No MailPublicFolder entry was found in Active Directory with OnPremisesObjectId

    this kept failing one of my Mailbox1 in the batch but I was able to trust this article below:

    posting the info. in case the Documentation goes away:


    This problem occurs if the NON_IPM_SUBTREE system folder contains mail-enabled public folders. Public folders in the NON_IPM_SUBTREE folder don’t have to be mail-enabled. They may have been previously mail-enabled because they were in a Microsoft Exchange 2000 Server environment that was running in mixed mode. In this situation, Exchange tries to mail-enable all public folders.

    Mail-disable all public folders that are mail-enabled in the NON_IPM_SUBTREE folder. For more information about how to mail-disable a public folder, see Mail-enable or mail-disable a public folder.

    To locate the system public folders, open the Exchange Management Console, go to Toolbox, open the Public Folder Management Console, and then expand System Public Folders. Mail-disable any public folder that has a mail icon next to it.


  3. Another update on the error: TooManyLargeItemsPermanentException has occurred.

    this would keep my mailbox9 in failed status even after I went and removed those larger than 20MB files and Synced-Folders.

    my boss had the brilliant idea that we should Set-MigrationBatch -LargeItemLimit 100

    since I was already resuming that mailbox syncing after the largeitemlimit changed it was able to resume and the batch now it’s moving data from on prem to the cloud 😉 when running:

    Get-MigrationUser -BatchId PublicFolderMigration | Get-MigrationUserStatistics | Select Identity,Status,SyncedItemCount,SkippedItemCount,BytesTransferred,PercentageComplete
    | Sort -prop Status,PercentageComplete | Ft -auto

  4. Hi Aaron, I thought I’d post a powerful script I found to get rid of the ghost NT users like S-1-* found when you run the Get-PublicFolder \ -Recurse | Get-PublicFolderClientPermission | Export-Csv PFPerms.csv -NoType

    posting the script in case the below article goes away:
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010

    $ghostuser = get-publicfolder “\” -recurse -resultsize unlimited | get-publicfolderclientpermission | where {$_.user -like “NT User:*”}

    $ghostuser |
    ForEach-Object{ remove-publicfolderclientpermission -identity $_.identity -user $_.user -access $_.accessrights -confirm:$false
    Copy to a textfile and save it as ps1.

    the script is found here: https://social.technet.microsoft.com/forums/exchange/en-US/29bf8aeb-3c7a-4db2-ad03-a15c5baf696a/public-folder-how-to-remove-a-deleted-user-via-powershell

  5. Hi Aaron,

    thank again for your time on putting this together. Just some comments before I forget. 1) it would be nice to have the O365 scripts (Authetication) set from an MFA stand point since nowadays everyone has that enable. the line when setting up the Internal relay was also confusing but was able to figure out the onmicrosoft.com part should really be your own tenant name not the tenant ID 234kh-h34k3oh4-3lk43jllk. another one that is also confusing is the PFMap file but after looking at the PFstatistics I understood that anything that’s not labeled Mailbox2,3,4,5 etc pretty much is just gonna get dumped into the Mailbox1 root folder.

  6. Aaron,

    Thanks so much for clarifying those questions we had about Orphan Objects and the backup snippet for MESO before deleting anything a very handy helpful method.

    posting it here so people finds it when they find your tutorial at least in the comments:

    $SearchBase = “CN=Microsoft Exchange System Objects,” + (Get-ADRootDSE).defaultNamingContext
    Get-ADObject -Prop * -Filter {objectClass -eq “PublicFolder} -SearchBase $SearchBase | Export-Clixml .\PublicFolderMESO.xml

  7. The content in this post helped really understand what is going on during the migration, and gain an understanding of why something failed. Something always missing from Microsofts documentation. Some really useful commands that help identify and fix issues too. Thanks so much for taking the time to write it all up and share the experience. 🙂

  8. I noticed that the Microsoft Docs mention that Exchange 2010 is required (and oldest version supported) for PF migrations. Does your article still apply and work correct for migrating Public Folders from Exchange 2007? If not, what’s the recommended method?

    Thanks for the detailed write-up!

  9. Hi Aaron,

    Just wanted to leave a note that I recently stumbled across your guide and found it far easier to follow than the Microsoft one. I’ve recently completed a PF migration from Exchange to 365 and it helped massively.


  10. Nice article Aaron and I like your humor to.
    I’m struggling with 4th attempt of migratin Exchange 2010 Hybrid public folder to Exchange Online.
    I got stucked at last steps.
    When I run Complete-MigrationBatch it always failes, and when I run Set-OrganizationConfig -PublicFoldersEnabled Local I recieve an error:
    PublicFoldersEnabled state must remain set to “remote” while Public folder mailboxes are on hold for migration. Before you can change your Public Folder deployment to “local”, you must first complete Public folder migration

    Now, for the 5th attempt I have added an argument which I saw on your article and that’s (SyncAndComplete)
    Complete-MigrationBatch -Identity PublicFolderMigration5 -SyncAndComplete

    I hope it will help :/

      1. OMG migration succeded. 😛 %) 😛
        The only issue I see that you can not add non-mail enabled public folders as favorites thru exchange online owa, but in outlook client they work as expected. Cheers!

  11. Very good article. I like your humor also.
    I am in the middle of migratin Hybrid Exchange 2010 public folders to Exchange online. Unfortunatelly this is my 5th attempt and now I’m trying with your approach, but my last 4 attempts got stucked at very last steps, and those were.
    The batch got to synced state, and after that I run Complete Migration Batch and failes all the time.

    The other thing I currently have problem is:
    When I run Set-OrganizationConfig -PublicFoldersEnabled Local I recieve an error:
    PublicFoldersEnabled state must remain set to “remote” while Public folder mailboxes are on hold for migration. Before you can change your Public Folder deployment to “local”, you must first complete Public folder migration.

  12. Hi Aaron,
    There aren’t so many articles about this, but yours is by far the most helpful. Many thanks!
    I have 95 “mailboxes worth” of public folders synchronising now. All looks ok, apart from one that failed before it copied any data.
    The error from Get-MigrationUserStatistics is “MigrationPermanentException: Couldn’t find a unique request with the provided information. –> Couldn’t find a unique request with the provided information.”
    Any idea what this relates to?
    My plan B is to use PowerShell to create the public folders in that mailbox and then just import the data via Outlook. It’s only 1% of our 2TB of public folders, so hopefully not too painful!

  13. Step 14, that’s a mod 10,000 , so it’s trying to give you a progress update every 10,000 items. You shouldn’t need to edit it out to 100,000, if my reading is correct?

    We’re midway through our journey and bad folder ACL’s are a real problem out there for people who have a lot of public folder history. We’re going to have to bite the bullet somewhere here and give a really high baditemlimit to our sync batch because with 20 years of history, there’s a hell of a lot of bad ACL’s out there 🙂

    1. That’s how it’s supposed to work. However, in my experience (after dozens of PF migrations), it truncated and the output CSV would only ever have 10,000 lines. I hadn’t ever paid attention to it until I got to a customer that had more than 10,000 folders. I haven’t been back to revisit it in a few years.

  14. Hi Aaron,

    First, thanks for this great blog post. We are currently reviewing the migration process from PF on Exch 2010 to Office 365.

    I’m following your steps in our Development environment. I run steps 14/15 of the preparation guide and I receive a list of 191 folders that appear to be prepared properly in “PFStatistics.csv”. When I run the mapgenerator script the output only shows:


    I’ve run the script on the mailbox server with the PF database (local on-premises). I’ve tried editing the PFstatistics.csv to see if there were any spurious records that were stopping the map script running. No joy at all. There are no errors when I run any of the scripts, I just get the same output mapping file every time.

    Have you seen this before or can you shed any light on what might be causing this?



    1. So, how big is your public folder database? And how many users? The folder sizes listed in the PFStatistics output file are used to decide which folders get divided into which mailboxes. If your data / content amount is smaller than the value you specified per public folder mailbox, then all of the public folders will only go to one mailbox. For example, if you set the cutoff at 30GB, but you only have 10GB of content, then you’ll only have one line in the map generator output.

  15. Well I got down to step 4 and ran the sync again and all my on-premise public folders disappeared and the only thing left it the Internet Newsgroups. Seems I am lost in running them. The only thing I can think pf is that I synched them the wrong way. In step 16 earlier, when I ran the PFmap script the only thing I got was MAILBOX1 and no other folders. At this time the public folders were still visible.
    I am restoring from backup now and will give it anther shot later.
    Also not sure what “Run the SyncMailPublicFolders.ps1 script one last time to make sure everything is working. If you already have hybrid public folders enabled, you know how to do this. If you don’t:”
    This is a small organization with only about 10 public folders to Migrate.
    Can you point me in the right direction?


    1. Fortunately, you can’t sync the wrong way.

      Which step 4 (since it’s broken into sections? I’m guessing under “Completing the migration batch?”

      Running Sync-MailPublicFolders.ps1 by itself will NEVER remove data (there are no cmdlets in there that specify removal), so I’m thinking something else happened. During the migration process, the public folders are UNAVAILABLE for local LAN users (the result of Set-OrganizationConfig -PublicFoldersLockedForMigration:$true), so if you do a Get-PublicFolder or Get-MailPublicFolder, you won’t see them. Once you complete the migration batch, the only public folders will be available in-cloud. If you need to re-enable public folders on-premises for some reason (like a long-running migration that keeps failing and you need to return public folders to service), you can run Set-OrganizationConfig -PublicFoldersLocakedForMigration:$false to make the PF store available again.

      1. Step 4 under Starting the Migration. This seemed to be the point where they disappeard, but I’m not sure. They disappeard from the ESM on the on premise 2010 Exchange server. When I run
        Get-OrganizationConfig | FL Public*Migration* from Office 365 I get:
        PublicFoldersLockedForMigration : False
        PublicFolderMigrationComplete : False
        PublicFolderMailboxesMigrationComplete : False

        From On-Prem:
        PublicFoldersLockedForMigration : False
        PublicFolderMigrationComplete : False

        Yes when I run:
        PS C:\WINDOWS\system32> (Get-OrganizationConfig).RootPublicFolderMailbox
        IsValid CanUpdate HierarchyMailboxGuid LockedForMigration
        ——- ——— ——————– ——————
        True True dd7ee2d4-77c5-46aa-a1da-55479976e014 True

        So am unable to even create public folders.
        At this point since I only have 4 public folders to deal with I just want to create the New folders with a different name in Office 365 amd import to data from a pst that I exported. I have already restoredthe on prem folder from backup.

        1. Step 4 is just running the Sync-MailPublicFolders.ps1 script. All that happens during that stage is an objects is created in Office 365 to allow Office 365 users to see mail-enabled public folders in the GAL.

          In Step 11, I ran the cmdlet Set-OrganizationConfig -PublicFoldersLockedForMigration:$True (because my customer insisted that PFs be static for users). As soon as you run that cmd, the Public Folder tree will be *unavailable* for end users until you finish the migration. You don’t have to lock them until the complete stage. This is more than likely why you can’t see your public folders. You can run Set-OrganizationConfig -PublicFoldersLockedForMigration:$false to unlock them.

  16. Is it possible to get a confirmation to what I think would be ‘simple’ to find, but no luck. Referencing your document here (I bought the book the other day BTW) and
    Between Step 5 and Step 6 of the technet url above:
    Assuming sync went well, based on reporting etc., should I, or should I not, see the public folders that were migrated under ECP Public Folders > Public Folders? When I run a PS for
    Get-PublicFolder -recurse I am informed that nothing to show, and states one option, no PF mailboxes (not true)or $holdForMigration – true (which is yes). So does that mean there is no way to review the PF in any way before Locking Down and Finalizing Migration?

    I just like to see with my eyes, as there are some customer expectations that are not pleased, and I don’t want to cause greater issues.

    It seems odd (to me) not to be able to see anything. Thanks!

    1. Hey, thanks! I’ve got 5 kids to put through college, so feel free to buy as many copies as you want. 😉

      No, you won’t see them until you complete the migration. Just like when you migrate mailboxes, you won’t see a mailbox until the migration is complete. It’s hidden until the migration is complete.

      1. Thank you for the confirmation. I received the book the other day and I look forward to using it. I do have a question:
        Please note: This is a 2013/2016 on-prem to 365 hybrid. All dbs are on 2013 servers. The customer does not appreciate the complexities re PF (since they are now ‘mailboxes’ does not understand the extra challenge – and I am a little soft on the tech reasons to explain).

        Question 1:
        This is not reflective in your article above, but on page 666 (the way PF has been treating me that seems appropriate) of book and also
        Step 9: Finalize the migration on-premises
        Both references the command:

        .\SetMailPublicFolderExternalAddress.ps1 -ExecutionSummaryFile:mepf_summary.csv
        Yet I can not find any explanation as to where/how the mepf_summary.csv comes from. That is what I am looking for. How is the csv generated? (full disclosure – this comment is specific to the article. It may be in your book, but I have not found.
        I have the following partner portal question re the same:

        1. Resolved – I think. I ran the
          New-AcceptedDomain -Name “PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99” -DomainName tenant.onmicrosoft.com -DomainType InternalRelay
          then I was able to run the
          .\sycnh….P.s1 command

        2. The mepf_summary.csv file is created as an output from the SetMailPublicFolderExternalAddress.ps1 script. It’s just got some information on activities performed.

      2. Hi, above and in book I see the following:
        Set-OrganizationConfig -PublicFoldersLockedForMigration $True -whatif
        The parameter was not found, however this is a
        Set-OrganizationConfig -PublicFoldersMailboxesLockedForMigration $True
        I have not had access to schema, and this is a 2013/2016 onprem environment (hybrid 365) – ran on a 2016…is one older than the other? Interchangeable, or do I need to address schema? Thank you.

        1. This version is for Exchange 2007. The parameter is different in Exchange 2013/2016 (PublicFolderMailboxesLockedForMigration is the updated parameter name for 2013/2016).

  17. Hello Aaron, you have been great help so far. I am planning to start the public folder migration, however I already have mail public folders synchronized to Office 365 (which I had done for the co-existence). Now in the Microsoft article it says in the prerequisites part to clear all the public folders- see here: https://technet.microsoft.com/en-us/library/dn874017%28v=exchg.150%29.aspx?f=255&MSPPError=-2147217396 and in step 5 it asks to sync folders again. DO I really have to remove and sync mail public folders again or I can skip that step or removing the folder? Please assist.

    1. I believe it says, “clear any existing public folder requests.” There’s two things the target Office 365 environment can’t have already when you start a public folder migration: 1. Existing public folders, 2. Existing public folder migration requests (from either failed or successful public folder migrations). The mail-enabled public folder sync is *separate* from that–it’s just put a mail recipient in the cloud so it’s a valid mail destination (it’s neither a folder nor a migration request), and it’s not being cleared/removed.

  18. Just completed my first Public Folder migration thanks to this article. I paid particular attention the the Diet Coke and Bourbon step to get through it. Excellent write up!

  19. Thank you for a great article. Good technical detail, and enjoyable to read too! We’re doing a similar migration from Exchange 2010 to Office 365, and having problems during the completion step. (We’re trying this in our lab environment with less than 10 public folders before we try migrating our production environment with over 5000!)

    Once we run Complete-MigrationBatch, the job spins up, but we see “Post-move cleanup failed. The operation will try again in 30 seconds (xx/480).” in the migration report log. This takes 30 seconds x 480 retries to timeout, then says “Request is complete.”

    After doing the final steps to finish up, we have some mail flow issues from on-prem. (There are a few “stragglers” left, like things sending mail through on-prem SMTP relay.)

    It appears the on-prem AD objects representing the mail-enabled public folders aren’t getting updated/cleaned up. Is that what’s failing in the 480 retries? What should happen to these on-prem objects: do they get deleted, should the target address be updated to the Exchange online mail delivery domain?

    Thanks for any additional info you can offer!

    1. Usually, the “480 retries” steps comes when your Active Directory isn’t reachable/responding or writable during the migration (you also might see it during mailbox moves when you can’t communicate back to AD to update the mailbox’s targetAddress property). I’d potentially give your environment a restart, make sure that the account performing the migration has both rights delegated over the MESO container (or is just a domain admin) and is an Exchange Org Admin, and that your Exchange servers can reach the internet.

  20. Thanks for the great article! When finalizing PF migration (to Online) in EXCH 2013 CU18 I had to lock on-prem with basic powershell window (EMS wouldn’t work, did not recognize PublicFolderMailboxesLockedForNewConnections parameter). And PublicFoldersLockedForMigration didn’t do anything. As soon as I ran the following, I was able to start SyncAndComplete:

    Get-PSSnapin -Registered|Add-PSSnapin
    Set-OrganizationConfig -PublicFolderMailboxesLockedForNewConnections $true

    1. The public folder mapping file only shows the top level folder branches. For example, say your public folder tree looks like this:


      Your PFMap file might just look like this:


      That would indicate that everything from \Folder1 on down went to PFMailbox1, and everything from Folder2 down (as well as Folder3 down) went to PFMailbox2. You more than likely will have far fewer lines in your map file than you do when you view the output of Get-PublicFolder.

  21. I have problem removing the single existing public folder mailbox on Exchange online. It was created back in 2014 by someone monkeying around Office 365 portal. We are just migrating from Exchange 2013 to Office 365 recently. When I tried to delete the public folder mailbox on Office 365 using the portal or power shell, it gives me an error and wont delete it. “WARNING: An unexpected error has occurred and a Watson dump is being generated: Object reference not set to an instance of an object.” The same error applied when using “Get-Mailbox -PublicFolder |Remove-Mailbox -PublicFolder -Force”

    1. Make sure you have backed up the public folders inside the public folder mailbox. WARNING: THIS WILL DELETE DATA IF ANY DATA IS PRESENT IN THE OFFICE 365 PUBLIC FOLDERS:

      Get-Mailbox -PublicFolder
      Get-MailPublicFolder | where {$_.EntryId -ne $null}| Disable-MailPublicFolder -Confirm:$false
      Get-PublicFolder -GetChildren \ | Remove-PublicFolder -Recurse -Confirm:$false
      $hierarchyMailboxGuid = $(Get-OrganizationConfig).RootPublicFolderMailbox.HierarchyMailboxGuid
      Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -ne $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false
      Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -eq $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false

      1. No, it does not work. After spending 5 days with Microsoft Support, they finally remove the mailbox from the backend.
        Why 5 days? Every day, they asked me to run a bunch of commands which I ran for many times. At the end of the day they will tell me either the end user and their team can not remove it. It will forward to Exchange Online team for removal which will take 24 to 48 hours.
        But after 24 hours, they will call me to do the same thing again.
        Finally I need to file a complaint than they removed it and apologized.

  22. I am stuck removing the root public folder mailbox in Exchange online. Someone back in 2014 created this public folder before the migration to the Exchange Online. When I tried to remove it from Exchange Admin Portal and Exchange Shell. I will get: WARNING: An unexpected error has occurred and a Watson dump is being generated: Object reference not set to an instance of an object.
    The public folder mailbox is called “test”. It is not mail enabled. The mailbox is empty. But there is a IPM_Subtree Public Folder in the mailbox which can not be remove. Anyone can help me?

  23. Thanks Aaron for sharing this well explained document. Much more didactic than TechNet.
    I will test this procedure next month in a PF migration. However, I have a doubt.
    Can we simplify the PF structure in the Office 365 target? I mean, from the original PF structure, we only want to migrate half of the folders, and place them in a subfolder of the target PF structure. How can we achieve that, if possible?

    Thank you very much for your great blog.

    1. No, you don’t get to pick-and-choose with the hybrid public folder move. You take everything. If you want to perform cleanup, you have to do it either pre-migration (preferable) or post-migration.

  24. Thanks Aaron for sharing this well explained document. Much more didactic than TechNet.
    I will test this procedure next month in a PF migration. However, I have a doubt.
    Can we simplify the PF structure in the Office 365 target? I mean, from the original PF structure, we only want to migrate half of the folders, and place them in a subfolder of the target PF structure. How can we achieve that, if possible?

    Thank you very much for your great blog.

    1. No, you don’t get to pick-and-choose with the hybrid public folder move. You take everything. If you want to perform cleanup, you have to do it either pre-migration (preferable) or post-migration.

  25. Hello Aaron,

    A confusion while going through step 10 where we run this command to create an accepted domain: New-AcceptedDomain -Name “PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99” -DomainName tenant.onmicrosoft.com -DomainType InternalRelay

    Wondering if in a hybrid setup, we´d have this accepted domain already created on-premises or not. And the main confusion is, when is the proxy address tenant.onmicrosoft.com is stamped on a MEPF on on-premises, is it when we setup hybrid and email address policy takes care of this, or is there a script that does this?

    1. In the Exchange Hybrid configuration, the EAPs that contain domains included in the hybrid environment are updated with the new template %m@tenant.mail.onmicrosoft.com. In much older versions of HCW, we used to include tenant.onmicrosoft.com and tenant.mail.onmicrosoft.com, but that long has since been removed.

      You shouldn’t have to worry about stamping additional proxies on MEPFs. The migration process is complete. As long as your MEPF objects have valid addresses and the domains are set to internal relay, they should make it to the cloud without issue. If you have changed your MX to Office 365 prior to cutting over public folders, you’ll need to turn off DBEB in Office 365 (by setting the cloud domain to internal relay), since the MEPF objects aren’t synced through to EOP.

      1. Aaron, in a hybrid environment, with MX pointed on-prem, how are new mail-enabled public folders created on-prem to reflect new MEPFs created in the cloud? Do we need to manually create matching on-prem objects?

        1. So, that’s going to be an “it depends.” If you change your on-premises accepted domain(s) to InternalRelay, you shouldn’t need to do anything. However, if you keep them authoritative, you’ll need to create a contact.

          1. Thanks for your quick reply! In this case, if the domain(s) can be converted to InternalRelay, we could delete all the MESO/MEPF objects entirely, right? Am I missing a good reason not to do this?

            1. Since they are technically mail-enabled objects that were copied during the Sync-MailPublicFolders.ps1 script, I think you potentially could delete them. I’d be more inclined to leave them, since it ain’t broke. 😉

          2. But when you use the Sync-MailPublicFolders.ps1 script to create the mail public folder objects in EXO you must delete objects before finalizing the migration. I’ve encountered this situation in some legacy PF to EXO migrations in the past. The objects created by Sync-MailPublicFolders.ps1 prevent the migrated folders to be mail enabled.

            1. Interesting. I’ve not run into that before in my travels unless the the MX has been pointed to Office 365 prior to the migration being completed.

              In earlier instances of EOP, MEPF’s synced to Office 365 did not get forward synced into EOP, meaning that if you had MEPFs on-premises and your MX pointed to cloud, DBEB would bounce messages in-bound for the on-premises MEPF’s because there wasn’t a record for them in EOP. To get around this, some folks created contacts in Office 365 directly so MEPFs that received mail from the internet could continue to receive mail.

              I’d be interested in hearing about your cases.

  26. Thank you so much for sharing this! I tried to follow the official guide, but ran into the exact problems that you did. I would not have got though this with out your well documented experience. Thank you, thank you, thank you.

  27. Hi Aaron! It’s a great great great post! Thanks!
    I have a question about “Completing the Migration” section. When you execute the Complete-MigrationBatch -Identity PublicFolderMigration command, you received the “There are Exchange Public Folders that could not be mail enabled” error message. You fix it and then you run again with the .\Sync-MailPublicFolders.ps1, and again with the Start-MigrationBatch… and finally, you run Complete-MigrationBatch -Identity PublicFolderMigration -SyncAndComplete command. My question is: Is it necessary to execute the Start-MigrationBatch command again? Or I should run the Complete-MigrationBatch command again since the initial synchronization of the batch was already in the Synced state. In other words, why should I execute the Start-MigrationBatch command again? Thank you!!

    1. In my case, because Sync-MailPublicFolders.ps1 wasn’t able to sync some MEU’s, I had new objects, plus I had objects in a failed state. When you run the Complete-MigrationBatch cmdlet and you still have a sync in an inconsistent state, you don’t know if it’s going to complete successfully. I believe you also receive additional warnings where Complete-MigrationBatch will inform you of an incomplete sync. Chances are you’ll have to work through several Start-MigrationBatch sequences to find and eliminate all of the errors before Complete-MigrationBatch will run successfully.

  28. Hi Aaron,
    It would be greatly appreciated if you could also post the “clean-up” steps that need to be done post migration. That seems to be missing from every KB / work through that i can find on the internet…
    For instance, after migration is successfully completed, how do we clear up the old PF Mailboxes, etc, without causing any impact to the mailflow to those (now in Office 365) PF’s when mail is still flowing in a hybrid environment (on-prem inbound/outbound mail flow)?
    Will just doing a remove-mailbox for the public folder mailbox cause any changes (mail disable?) to the AD objects (which in a hybrid AD Connect environment) would then cause delivery issues up to o365 or worse, AD attribute sync’s that would cause the PF’s in 365 to no longer be mail enabled or anything strange like that?
    What about that last PF Mailbox / Heirarchy Mailbox – any special considerations for that (like in the past legacy environments when trying to remove the last PF database)?
    Would be great if this could be detailed somewhere by someone such as yourself who has the experience of large scale hybrid environments with multiple 10’s of thousand seats… that scale does bring a different perspective on things 😉

    1. That sounds like a perfect follow-up article. I’ll get to work on it and post the steps and scripts necessary to decommission the public folder infrastructure.

  29. In case anyone is looking for more detail while move is completing, you can run:
    Get-PublicFolderMailboxMigrationRequest. This will return “PublicFolderMailboxMigrationXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX” name for each mailbox you are migrating to.

    To get more detail, you can save the above to variable and iterate through:
    $pfmigs = Get-PublicFolderMailboxMigrationRequest
    $pfmigs | % {Get-PublicFolderMailboxMigrationRequestStatistics -Identity $_.Identity}

  30. This has been quite helpful! I have two questions, 1) I have 36 PF Mailboxes. I have 34 that are currently in “Synced” state. I have one that says “Syncing” and it has been syncing for days. It is stuck at 20%. I am pretty sure it’s finished but there aren’t any errors. It just says “syncing.” Any idea why this might be happening? I have restarted the Information Store on the PF server several times to no avail. The other issue I have is on the first mailbox it fails saying that there are 100 folders it couldn’t mail enable. I don’t have 100 folders that are mail enabled. I used to have 9000 or so but they should have never been mail enabled in the first place so I mail disabled all of them (AD object has been removed as well). Now I only have 18 mail enabled folders. Any idea why it keeps telling me that there are 100 it couldn’t mail enable? The batch eventually gets to the point where it says “SyncedWithErrors.” If it gets to this point will it complete successfully if I try to finalize it or will it fail because there are errors?

    1. On the second mailbox that I am having problems with when it fails I get the error, “Cannot save changes to an item to store. MAPIExceptionMaxObjsExceeded: Unable to save changes. (hr=80004005, ec=1252).” Currently 1.8 million items have been migrated into the mailbox that equal about 17gb.

    2. I would look at the move request report for errors as I indicated about under “Completing the migration.”

      Get-MoveRequest | Get-MoveRequestStatistics -IncludeReport | Export-Clixml pfmailbox.xml

      And then review the Failures and Messages nodes:
      $pfmailboxreport = Import-Clixml pfmailbox.xml
      $Entries = $pfmailboxreport.Report.Entries | Select Message -Last 300 | ? { $_.Message -like “*could not be mail-enabled*” }

      More than likely, you have some folders that “think” they’re mail-enabled that are causing problems or some Active Directory objects in the Microsoft Exchange System Objects container. Or, potentially, you have an overlap between an alias of a mail-enabled public folder and a mailbox/distribution list.

      If you cleaned up objects during your migration, you’re going to want to run the Sync-MailPublicFolders.ps1 script again. Watch it carefully both on-screen and in the log file for errors.

      You won’t be able to complete the migration if any of those are the case.

      1. Thanks! I found all the pf’s that “thought” they were mail enabled and cleaned them up. Now that mailbox says there is 1 mail public folder in active directory that is not linked to a public folder. It tells me the address of the PF and I can find it in O365 but not on-perm. When I do the sync it tries to delete it from O365 but says it doesn’t exist. Any ideas? What about the other mailbox that won’t go above 20%?

        1. I fixed the one with the 1 Public folder in AD not linked to a public folder by running get-mailpublicfolder PublicFolder |Set-MailPublicFolder -IgnoreMissingFolderLink:$true in an Office 365 Powershell. The other PF Mailbox that won’t go past 20% is giving me an error that says “Fatal error StoragePermanentException has occured.” It says it is going to keep retrying but it seems to be stuck. Any suggestions?

          1. I found the problem. There was a corrupt Public Folder with 1.2 Million items in it. I couldn’t even open the folder. The folder was old so we removed it and the mailbox that was stuck is now moving.

        2. You may have a recipient that also matches that PF alias or SMTP address. You can use my Duplicate SMTP address finder (which may or may not help). I had a customer that had a similar error. The output of the Sync-MailPublicFolders.ps1 is helpful in determining what object(s) may be in conflict.

  31. On step 7 of starting the migration, I keep getting the error –

    PS C:\PFScripts> $PFEndpoint = New-MigrationEndpoint -PublicFolder -Name PublicF
    olderEndPoint -RpcProxyServer $OAEndPoint -Credentials $Credential -SourceMailbo
    xLegacyDN $MailboxLegacyExchangeDN -PublicFolderDatabaseServerLegacyDN $ServerEx
    changeLegacyDN -Authentication Basic

    Cannot process argument transformation on parameter ‘RpcProxyServer’. Cannot
    convert the “System.Collections.ArrayList” value of type
    “System.Collections.ArrayList” to type “Microsoft.Exchange.Data.Fqdn”.
    + CategoryInfo : InvalidData: (:) [New-MigrationEndpoint], Parame
    + FullyQualifiedErrorId : ParameterArgumentTransformationError,New-Migrati
    + PSComputerName : ps.outlook.com

    Here is the output of that variable –

    PS C:\PFScripts> Write-Host $OAEndpoint


    Trying to migrate from Exchange 2010 to Office 365. Any suggestions?

    1. Sounds like you have more than one item listed. If it returned more than one OA Endpoint, you can just try specifying the name (like -RpcProxyServer externalOWAsite.server.com) or Try $OAEndpoint[0] to select the first item in the array index.

      1. Thanks for the input. Turns out that I had to change the authentication at the end of the command to NTLM, I now have my migration point!

        Now I just can’t seem to get the migration to finish. It syncs to 95%, but won’t ever finish. Varying messages (not able to mail enable, unexpected objects, etc.) each time I try a suggested fix. Seem like out of sync, but each time I try to run the Sync-MailPublicFolder.ps1 I get the following error.

        Get-MailPublicFolder : Cannot execute a document in the middle of a pipeline: C:\Windows\system32\Get-MailPublicFolder.
        At C:\pfscripts\Sync-MailPublicFolders.ps1:522 char:131
        + $allSystemFoldersInAD = @($allSystemFolderParents | Get-PublicFolder -Recurse -ResultSize:Unlimited | Get-Mai
        lPublicFolder <<<< -ErrorAction:SilentlyContinue);
        + CategoryInfo : InvalidOperation: (C:\Windows\system32\Get-MailPublicFolder:String) [], RuntimeException
        + FullyQualifiedErrorId : CantActivateDocumentInPipeline

    1. IIRC, Get-PublicFolderMigrationRequest was used with serial migrations. This is a batch migration process, so you’ll want to use Get-MigrationBatch | ? { $_.MigrationType.ToString() -eq “Public Folder” } to see what’s going on.

      It’s essential a mailbox migration (as the targets are Public Folder mailboxes).

  32. Aaron, Great migration post. I am currently using it to migrate Exchange 2007 to O365. I do have a question about downtime. In the Start Migration Section #11 – Why do you lock the public folders while they are pre-migrating? I was under the impression that user downtime only occurs during the final migration (95% synced to 100%final). Please confirm. Thank you

    1. Yes. That’s the only place it needs to be (prior to cutover), but if you want to prevent people from changing the structure (such as adding new public folders or changing the email address for one), you can lock them ahead of time.

      1. Ideally incremental sync should pick if anyone is creating a new folder… so I guess we should not be worried about it. Correct me if I am wrong?

    2. Ideally incremental sync should pick if anyone is creating a new folder… so I guess we should not be worried about it. Correct me if I am wrong?

      1. Yes, you are correct. If someone changes the email address of a PF, though, you’ll need to re-run sync-mailpublicfolders.ps1. As we discussed via Skype, you really only need to lock the public folders before you run Complete-MigrationBatch. Complete-MigrationBatch will capture all of the delta content and folder changes (aside from email address changes to MEPFs).

  33. Hi Aaron Guilmette,

    I was following your steps in migrating Public folder but I got this error code message in step 13.

    cd \PFScripts
    Mkdir C:\PFScripts\PFMigrate
    .\Export-PublicFolderStatistics.ps1 C:\PfScripts\PFMigrate\PFStatistics.csv publicfolderserver.domain.com

    Exisiting code:
    cd c: \pfmigration
    [PS] C: \pfmigration>.\Export-PublicfoldersStatistics.ps1 PFStatistics.csv FCIEXCH

    Error: A Parameter cannot be found that matches Parameter name server

      1. I unblocked the script (I don’t think it was blocked in the first place) and got the same error. Tab-completing the command didn’t make a difference. Putting FCIEXCH.federico.local for the server name also gave me the same error.

        1. It sounds like you may have a bad download/truncated version of the script. I used the publicly available scripts from the download site; I’d try re-downloading.

  34. Appreciating the commitment you put into your blog and in depth information you offer. It’s good to come across a blog every once in a while that isn’t the same unwanted rehashed material. Excellent read! I’ve bookmarked your site and I’m including your RSS feeds to my Google account.

  35. Excellent guide, which came to me from a Exchange migration expert. I noticed you increased the Public Folders quota setting to 9.5 GB in the guide. I’d like to adjust settings to accommodate two large public folders – 14 GB and 7 GB. Should I change this quota setting to allow these PFs to migrate? Or should I break up the two PFs?


  36. Can you tell me what In need to replace in this part of the script to get it to run
    (Get-Mailbox ).legacyExchangeDN | Out-File .\MailboxLegacyExchangeDN.txt
    (Get-ExchangeServer ).ExchangeLegacyDN | Out-File .\ServerExchangeLegacyDN.txt
    $OAEndpoint = (Get-ExchangeServer).[0].ExternalHostNameHostnameString
    $OAEndpoint | Out-File .\OAEndpoint.txt

      1. Looks like I had a typo. I’ve updated it. It should be:

        $OAEndpoint = (Get-ExchangeServer)[0] instead of (Get-ExchangeServer).[0]

        The end goal is you’re looking to save your external autodiscover hostname to a file, since you’re going to import it later.

  37. Have you ever considered publishing an e-book or guest authoring on other websites? I have a blog based upon on the same topics you discuss and would love to have you share some stories/information. I know my visitors would enjoy your work. If you are even remotely interested, feel free to send me an e-mail.

  38. Thank you Aaron for posting with such great details, screen shots, logic for troubleshooting…Everything!
    Your Post helps understanding and walking thru the MS article like having a personal instructor!
    (Psst… Typo in Step 16 of the first section. “PFStatisticsNew.csv” doesn’t match Step 15’s output file “PFStatistics.csv”)

    1. Thanks! 🙂 Between steps 15 and 16, I had to re-run it, and I just forgot to note that the output for the second time running the command was “PFStatisticsNew.csv.” I’ll fix it. Thanks!

  39. With Azure AD Connect 1.1.524.0 now supports synchronizing Mail-Enabled Public Folder objects , is it still required to use Sync-MailPublicFolder.ps1 in a hybrid environment (Ex2013)?

    1. There’s actually a different set of steps for doing this with Modern Public Folders, including the script “Sync-ModernMailPublicFolders.ps1.” I’m writing up a similar set of steps/procedures that you’ll be able to find in the new Office 365 Administration Inside Out book (http://aka.ms/o365adminio), but I’ll post a pretty comprehensive set here as well when I get it fully mapped out.

  40. You really make it seem so easy with your presentation but I find this topic to be actually something that I think I would never understand. It seems too complicated and very broad for me. I’m looking forward for your next post, I’ll try to get the hang of it!

  41. This 52-step process is well documented, Aaron (and good humor throughout).
    Unfortunately, I think that most people will not survive it, nor have the time or patience to work through it.

    I’d love to get your take on our 4-step process. http://www.exchangesavvy.com/public-folder-migration-to-office-365/

    We have managed to make PF migration so simple that anybody can do it, as well as make it easy to cut over over and roll back the settings (especially around mail-enabled folders).

    1. I’m always interested to see what solutions may exist. In the last year, I’ve moved around 3 million public folders from Exchange 2007, 2010, and 2013 to other Exchange systems or Office 365 using the batch migration method described here, which is mostly online except for the actual cutover (just like hybrid mailbox moves). This method now supports incremental syncing to keep content virtually up-to-date for months or years, and uses the standard move request reporting to report underlying errors.

      The biggest challenge we face in consulting services is around multi-organization public folder trees for mergers and divestitures. As long as you understand the science around how many users will be accessing public folders and how you should be breaking apart the branches in your public folder tree to accommodate the necessary growth, I haven’t really run into too many problems. That being said, I’m always interested in learning about additional methods to solve complex tasks that we don’t necessarily have guidance around.

      1. Full Disclosure: I work with Geoff at ExchangeSavvy.

        Great blog post!

        The whole multi-org public folder problem is an interesting one, and one that PowerShell scripts are ill-suited to solve, IMO. Having an application drive the process allows a lot of logic to be easily introduced into the picture. The ExchangeSavvy solution is able to migrate and merge public folder hierarchies from multiple on-premise Exchange organizations into a single O365 public folder hierarchy. Part of this process is to allow the user to easily specify where in the O365 tree a given on-prem PF hierarchy should be rooted. This lets users organize the new O365 hierarchy however they see fit.

        A lot of the value of solutions like ExchangeSavvy is around handling so many of the complex details that you’ve documented. I think the average Exchange admin is really going to struggle to pull off a migration without either a tool like ExchangeSavvy or a seasoned pro such as yourself. Even for experienced guys like you, I think ExchangeSavvy offers a lot of value is certain situations where the MSFT scripts just don’t cut it, multi-org migrations being a perfect example. Other examples would be situations where only a subset of the on-premise folders are to be migrated (that’s super-easy to do with ExchangeSavvy) or you want to limit migration based on other criteria like item age (something else we can do).

        Aaron, if you ever wanted to take a look at ExchangeSavvy drop us a request at http://www.exchangesavvy.com. I think you’d find what we have interesting and I’m sure we’d learn a few things by getting to have you grill us on our solution.

        1. In the event that you want to migrate just a subset of folders, you can technically do that as well by editing the PF Mapping file output. 😉 No matter what solution you go through (native or on-premises), there are still a lot of steps to do prior to migration that need a human touch because they’re analytical (for example, if you have mismatched MEPFs, EntryIDs, missing MESOs or corrupt items in the PF Tree), as well as understanding how PF hierarchy works so you don’t end up with too few mailboxes to support your number of users (which can be disastrous).

          At some point, I’d be interested in looking at it, but since we have a working methodology that’s developed and supported by PG, it’s pretty hard to get a consulting services person to recommend a third-party product without voiding our own support. 🙂

  42. Great write-up! Thanks for documenting this stuff, some of which I do myself in migrations, like the sendas and grantsendonbehalfto. Learned some things, and thought I knew everything when it comes to Public Folders and migrating them… Nice one!

    1. Thanks for the comments! I always learn something new every time I do this, too. I have put together a toolkit around planning and deployment for on-premises modern public folders as well as transition to Office 365, based on some of my larger projects. Most of my tools come out of particular tasks for customers that I adapt to be more general.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Exit mobile version