The problem with Import-MailPublicFoldersForMigration.ps1 and Mail Enabled Public Folders

Did you just run Import-MailPublicFoldersForMigration.ps1 and receive a ton of red errors saying that
 "The proxy address "" is already being used by "foldername"."
And also a summary at the end letting you know that "The following mail enabled public folders could not be imported:"?  Not to worry!  You are not alone, and I can show you why you got the error and more importantly how to fix it.  If you haven't run into this yet, this post will help you avoid it.

In order to migrate public folders to Exchange Online or enabled legacy public folder access from Exchange Online to an on-premises server, Microsoft has a set of scripts to export the mail-enabled public folder recipient objects from on-premises and import them to the Exchange Online directory.  Unfortunately, the import script makes a rather dubious decision that might be causing some admins a serious headache.

The export script runs Get-MailPublicFolder against your public folder structure and extracts the following properties: Name, Alias, EmailAddresses, EntryId, HiddenFromAddressListsEnabled, LegacyExchangeDN, WindowsEmailAddresses.

The Name property is unique within a given organization.  The Alias field is not.  EmailAddresses is an array of email addresses which are associated with the mail-enabled public folder.  The import script runs a specialized cmdlet called New-SyncMailPublicFolder which was specifically written for this script.  It essentially takes the data collected from the export script for each public folder and creates a PublicFolder recipient type object in the Office 365 directory.  This is only important if an Exchange Online user is sending mail to a public folder and they actually want it to be delivered.

You would expect that the import script would simply import the xml data and run the New-SyncMailPublicFolder cmdlet for each item, and for the most part that's what it does.  There is one block of code that makes very little sense and here it is:
$emailAddressesArray = @($folder.EmailAddresses);
$acceptedDomainCount = $script:AcceptedDomains.Count;
if ($folder.WindowsEmailAddress.ToString() -ne "")
     for ($index = 0; $index -lt $acceptedDomainCount; $index++)
          $emailAddressesArray += $folder.WindowsEmailAddress.Local + "@" + $script:AcceptedDomains[$index].DomainName.ToString();
For those not accustomed to reading code, here's what the script is doing.

At an earlier point the script runs a Get-AcceptedDomains command to populate the variable $script:AcceptedDomains array with all domains which are accepted by this Exchange Online tenant.  The first line loads all the EmailAddresses stored in the xml file.  That is where the process should probably stop.  However, the next line gets a count of how many accepted domains there are and then uses that in a for loop a few lines down.

This for loop adds an email address for each accepted domain it found in your Exchange Online tenant using the name stored in the WindowsEmailAddress.Local variable.  So if you have the accepted domains of,, and and a WindowsEmailAddress of, the script will add the email addresses:

It only runs the for loop if the WindowsEmailAddress field has a value, which is good error checking since there is a dependency in the loop, but that doesn't really explain what the loop is doing there.  If you had wanted all those email addresses associated with your public folder, wouldn't you have added them yourself?  You might be tempted to write this off as a little over-ambitious programming and move on to your next task.  But there is a major issue here.

Let's say this I have two public folders.  The first is \fabrikam contracts\info and the other is \contoso contracts\info.  I mail enable the first one with and the second with  That makes sense right?  Those email addresses will become the WindowsMailAddress property as well as being added to the EmailAddresses array object.  When the import script runs it will find the first info folder and create the object, but wait!  It will add and as email addresses in addition to  Now when it gets to the second info folder, it will try and do the same thing and the red error will appear on your screen saying:  The proxy address "" is already being used by "info".  Now users in your Exchange Online tenant that send mail to the will find their mail in the \fabrikam contracts\info folder instead!  

Unfortunately the damage is done, and now you have to clean it up.  The first step is to remove all those PublicFolder recipient objects from the Office 365 directory.  Connect a PowerShell session to Exchange Online and run Get-MailPublicFolder -resultsize unlimited.  This will show you all the mail enabled public folder objects that have been created in Exchange Online.  You can also run Get-Recipient -resultsize unlimited | where{$_.RecipientType -eq "PublicFolders"} to get the recipient objects that were created as well.  I recommend that you wipe out all of these entries since they all have the additional email addresses, unless of course you want them there.  The easiest way to do this is run Get-MailPublicFolder -resultsize unlimited | Disable-MailPublicFolder.  That is going to kick out a bunch of yellow warnings because Exchange is looking for the public folder objects to disable, which don't exist in Exchange Online.  Those warnings are fine and expected.  The end result is that the recipient object is purged, and that is what we really want.

Now you need to edit the script (here there be dragons!).  Simply add a # to the beginning of the line for the if statement and you are done.  It should look like this:

#    if ($folder.WindowsEmailAddress.ToString() -ne "")
#    {
#        for ($index = 0; $index -lt $acceptedDomainCount; $index++)
#        {
#            $emailAddressesArray += $folder.WindowsEmailAddress.Local + "@" + $script:AcceptedDomains[$index].DomainName.ToString();
#        }
#    }
Now you can save and run the script.  The objects will be created again, this time with only the email addresses found in the xml file.  If you were lucky enough to find this article before you ran the script, you can just alter the script as detailed above and skip all the extra parts.  You will need to change your execution policy to Unrestricted instead of RemotelySigned since altering the script invalidates the embedded signature.

You might notice that the new objects have an incremented alias name.  This is okay.  The old entries seem to be living on somewhere in the directory, but the important thing is having the correct LegacyDN, EntryID, and EmailAddresses.

I've notified Microsoft of the code issue and suggested a change.  Hopefully they will post a new version of the scripts.  

Labels: , , , , , , , ,