Tag Archives: Exchange Online

Special characters in Active Directory and Exchange Online

During migrations to Exchange Online I get the question regarding special characters in the User Principal Name (UPN) and e-mail address. Every time I have to check this again and again, so it’s time to do a write-up.

The UserPrincipalName (UPN)

The UserPrincipalName (UPN)

The UPN is the user’s identifier in Active Directory, and it is formatted like j.wesselius@exchangelabs.nl. It is a Microsoft recommendation to keep the user’s email address and UPN identical, but that’s not a hard requirement.

The UserPrincipalName attribute has the following characteristics and/or requirements:

  • The @ character is required.
  • The @ character cannot be the first or the last character of a UPN.
  • The total length cannot exceed the 113 characters limit. 64 characters in front of the @ character (i.e. username) and 48 characters after the @ character (i.e. domain name).
  • Allowed characters are A – Z, a – z, 0 – 9, ‘ . – _ ! # ^ ~
  • Invalid characters are \ % & * + / = ? { } | < > ( ) ; : , [ ] “
  • An Umlaut, tilde and accents are also invalid characters.
  • The UPN cannot end with a dot.
  • The UPN cannot contain spaces.
  • With directory synchronization in mind:
    • a routable domain must be used for the UPN (for a stand-alone AD this is not the case).
    • UPN must be unique and cannot contain any duplicated value in the directory (like UPN of user A is the same as e-mail address of user B).

The last bullet is something I see a lot in hybrid scenarios. In Exchange 2019 it is possible to have a user with a UPN like J.Doe@exchangelabs.nl, and another user with an identical email address J.Doe@exchangelabs.nl. Although it is confusing, it is possible on-premises.

In Exchange Online this is not possible, and when you have Entra ID in place, it will generate error messages, and strip the email address from the second user. Needless to say, you must fix this inconsistency (which can be problematic since you must remove an email address from a mailbox).

A little bit related is the samAccountName attribute of a user. This has the following limitations:

  • The maximum length is 20 characters.
  • It must be unique in the entire organization.
  • The following characters are invalid: [ ] \ | / , : < > + = ; ? * ‘ and the double quoute character “

Email Addresses

In Exchange and Exchange Online there are four e-mail address related attributes:

  • The mail attribute. The mail attribute of a recipient must be unique in the entire organization.
  • mailNickName (or alias). Must be unique in the entire organization and it cannot start with a period.
  • ProxyAddresses. This is a multi-valued attribute and has the following restrictions:
    • The maximum length of an entry is 256 characters.
    • It cannot contain a space character.
    • It must be unique in the entire organization.
    • It cannot contain any of the following characters: < > ( ) ; , [ ] and a double quote character “. The colon character : is allowed, but only after an identifier like SMTP: or X500:
    • Special characters with an umlaut, accent or tilde are invalid.
    • TargetAddress. This is used for forwarding email messages, in a hybrid environment this is the remote routing address. It is a singe value attribute, and has the same limitations as the proxyAddresses attribute.

Most likely there are more related attributes that need attention, but these are the most interesting I see when working with customers.

MTA-STS Support in Exchange 2019 and Exchange Online

My previous blogpost was about DANE and discussed how DANE can be used to make TLS negotiations between mailservers more secure. Another topic in this area is MTA-STS. MTA-STS stands for Message Transfer Server – Strict Transport Security and MTA-STS is a mechanism to enforce the use of TLS and the use of a valid 3rd party server certificate.

MTA-STS, DNS and Policies

When an MTA-STS capable servers wants to send an email, it first retrieves the MX record for the recipient domain. The second step is that the sending server check for an MTA-STS record in DNS. This record looks like:

_mta-sts.exchangelabs.nl. IN TXT "v=STSv1; id=202306242147;"

The id is an identifier and defines the version of the MTA-STS record when changes are made to the MTA-STS record. A good practice is to create an identifier based on the date and time of the last change. In this example, it is June 24, 2023 at 9:47pm.

The next step is that the sending servers looks for a policy. This policy is not stored in DNS, but on a website. The URL for this policy looks like: https://mta-sts.exchangelabs.nl/.well-known/mta-sts.txt. The subdomain mta-sts, the filename mta-sts.txt and the directory .well-known (including the dot) directory are mandatory for the MTA-STS policy. It must also be secured using a valid 3rd party server certificate.

The MTA-STS policy will look something like:

version: STSv1
mode: enforce
mx: smtphost.exchangelabs.nl
mx: *.mail.protection.outlook.com
max_age: 604800

The MTA-STS policy is structured as follows:

  • Version identifies the version of MTA-STS but must always be STSv1 (for now at least).
  • Mode defines how the policy must be applied:
    • Enforce: Sending MTAs MUST NOT deliver the message to hosts that fail MX matching or certificate validation or that do not support STARTTLS.
    • Testing: Sending MTAs that also implement the TLSRPT (TLS Reporting) specification [RFC8460] send a report indicating policy application failures (as long as TLSRPT is also implemented by the recipient domain); in any case, messages maybe delivered as though there were no MTA-STS validation failure.
    • None: In this mode, Sending MTAs should treat the Policy Domain as though it does not have any active policy.
  • MX defines all MX records in use by the recipient domain. This can be one entry, but it can hold multiple MX records, each on a separate line as shown in the policy above.
  • Max_age defines the time (in seconds) that the MTA-STS policy can be cached by a mail server. In this example, the policy is cached for 604800 seconds, which equals to 1 week. When a sending server must send a new email within a week, the policy is still cached. After checking the MX record the server retrieves the TXT record from DNS (as explained in the second step above) and when the identifier has not changed it uses the policy that is cached. If the identifies has changed within the lifetime of the cached policy, a new policy is downloaded.

So, in my example an MTA-STS capable mail server will check the MTA-STS policy and only connects to my mail server using TLS 1.2 (this is enforced with MTA-STS when mode is set to ‘enforce’) and only when a certificate that matches the FQDN is used. When authentication fails for an entry, the sending server continues with the next line in the policy, in my example with the MX record pointing to Exchange Online.

An interesting option in MTA-STS is reporting. DMARC has a reporting function as well, but reports are only sent by receiving domains. Reporting in MTA-STS is performed daily by sending mail servers that supports MTA-STS and TLS-RPT.

To configure the reporting functionality, create a mailbox in Exchange 2019 or Exchange Online and assign it an email address like TLSReports@Exchangelabs.nl. The next step is to configure the following DNS TXT record:

_smtp._tls.exchangelabs.nl. 3600 IN  TXT v=TLSRPTv1;rua=mailto:TLSReports@exchangelabs.nl

There are several online tools available for checking the MTS-STS record. Just like in my other blogs, I often use MXToolbox to check for DNS records as shown in the following screenshot:

As with the DANE checks, the mailhardener site (https://www.mailhardener.com/tools/mta-sts-validator) can also be used to check MTA-STS records as shown in the following screenshot:

When you check the IIS logs of the webserver where the mta-sts.txt file is stored, you can see which servers are using MTA-STS:

2023-07-11 08:29:59 172.16.1.4 GET /.well-known/mta-sts.txt - 443 - 74.125.217.68 Google-SMTP-STS - 200 0 0 119
2023-07-11 13:54:51 172.16.1.4 GET /.well-known/mta-sts.txt - 443 - 104.47.11.254 - - 200 0 0 62
2023-07-11 14:13:37 172.16.1.4 GET /.well-known/mta-sts.txt - 443 - 77.238.178.114 AHC/2.1 - 200 0 0 20

The first one is obvious, the second line is a Microsoft IP address, the third line is a Yahoo IP address. So, Google, Microsoft and Yahoo are using MTA-STS when sending email.

MTA-STS versus DANE

MTA-STS and DANE share a common concept, that is to secure the (initial) communication between mail servers. The ‘problem’ with DANE is that is relies on DNSSEC and the global roll-out of DNSSEC is very slow (to put it mildly).

MTA-STS was developed to overcome the slow roll-out of DNSSEC (since it does not use DNSSEC of course). MTA-STS can be seen as a ‘light-weight’ version of DANE and it will be sufficient for most customers.

And how about Exchange?

Just like with DANE, the ugly part is that Exchange 2019 does not support MTA-STS. You can configure the MTA-STS record in DNS and the policy on a website so that MTA-STS capable servers use your Exchange 2019 server safely. But for sent messages by Exchange 2019, MTA-STS is a no-go, it does not support it and most likely will never do.

Exchange Online on the other hand does support MTA-STS (since the beginning of 2022) for both inbound and outbound messages. The only thing you must do to enable it for inbound messages is create the TXT record in DNS and create and publish the MTA-STS policy.

Edited on July 11, 2023

DNSSEC and DANE support in Exchange server and Exchange Online

In the past I have written about SPF, DKIM and DMARC for email authentication in Exchange server to improve e-mail security. Additional topics to improve security are secure DNS (DNSSEC) and DNS-based Authentication of Named Entities (DANE). Where SPF, DKIM and DMARC are focusing more on the email messages and the sending hosts they are coming from, is DANE more focusing on setting up the TLS connection between mail servers. DANE has a dependency on DNSSEC but since my focus is on Exchange and Exchange Online, I don’t discuss DNSSEC and leave that to my provider (DANE only works when DNSSEC is enabled).

When two mailservers setup a connection, they negotiate a common TLS protocol and this handshake as it is called is unencrypted. As such, this handshake is vulnerable for man-in-the-middle or downgrade attacks.

DANE is using a special DNS record (a TLSA record, Transport Layer Security Authentication) where an organization can publish information regarding the certificate on the mail server and thus the TLS options available.

The TLSA record for my own domain is shown in the following example:

_25._tcp.smtphost.exchangelabs.nl IN TLSA 3 1 1 50167c478a2f536a88ee9ec232b14b0d223c2d5bdc837451eee104c153376cbe

When a mailserver wants to send email to my Exchange environment it retrieves the MX record first which is smtphost.exchangelabs.nl (this is an Exchange 2019 Edge transport server, with a Digicert certificate that matches the servername). When DNSSEC is not used at the recipient domain, opportunistic TLS is used. When DNSSEC is used at the recipient domain, a DANE capable mail server will check for a TLSA record.

When a TLSA record is found and retrieved, the sending server sets up a TLS connection with the receiving server, which in turn returns the fingerprint of the certificate. The sending server compares the fingerprint with the information found in DNS and when it matches, the connection is established and communication between the two servers continue, and the email is sent.

So, how do you create the TLSA record? There are publicly available webservers that can create TLSA records. Shumon Huque is a software engineer and technologist based in Washington DC who has a site that can generate TLSA records. As a bonus his site can also check TLSA records.

To generate a new TLSA record, navigate to his website on https://www.huque.com/bin/gen_tlsa and fill out the necessary information as shown in the following screenshot:

The easiest way to create a PEM format X.509 certificate is using the MMC certificate snap-in. Select your certificate and export it. Select the ‘No, do not export the private key’ option, select the ‘Base-64 encoded X.509 (CER)’ option and export the certificate. You can use Notepad to open the certificate export, and copy-and-past this into the PEM format X.509 textbox.

Select the port number (25), the protocol (TCP, not SMTP) and enter the domain name. This is the FQDN of the receiving server, so in my example it is smtphost.exchangelabs.nl and click Generate.

It will show the generated TLSA record, including the certificate information as shown in the following screenshot:

For my domain the following TLSA record is generated:

_25._tcp.smtphost.exchangelabs.nl. IN TLSA 3 1 1 a63d74fc7ec1acad702017d13479a1b60b36f234ccb96b90f97c9619ba2c91ab

How is this TLSA record structured?

TCP port 25 and the FQDN of the server make up the first part of this TLSA record.

The first number after the TLSA text is the certificate verification or the certificate usage on the server and this number can have 4 values:

  • 0 – CA Constraint. The certificate provided when establishing the TLS connection must be issued by the listed root-CA or one of its intermediate Certificate Authorities.
  • 1 – Service Certificate constraint. – The certificate used must match the TLSA record, and it must also pass the certification path validation to a trusted root-CA.
  • 2 – Trust Anchor Assertion. The TLSA record must match the certificate of the root CA, or one of the intermediate CAs of the certificate in use by the mailserver.
  • 3 – Domain Issued Certificate. The TLSA record matches the used certificate itself. The certificate does not need to be signed by other parties and as such a self-signed certificate can be used (this is interesting on an Exchange server).

In my TLSA record a value of 3 is used for the certificate usage, so the TLSA record must match the certificate on the Exchange 2019 server.

The second number that is used is the selector. The selector can have two values:

  • 0 – The entire certificate is used for matching.
  • 1 – Only the public key of the certificate is used for matching.

In my TLSA record the value is 1 for the selector, so only the public key is used.

The third number that is used is the matching type. The matching type can have three values:

  • 0 – the entire information select is present in the certificate associated data (the last text string in the TLSA record)
  • 1 – The SHA-256 hash of the public key of the certificate must match the certificate associated data.
  • 2 – The SHA-512 hash of the public key of the certificate must match the certificate associated data.

In my TLSA record a value of 1 is used for matching, to a SHA-256 has of the public key of the Exchange server certificate must match the data in the TLSA record.

The next step is to add the generated TLSA record in DNS and check the TLSA record once added.

You can use the same site (https://www.huque.com/bin/danecheck-smtp) to check the TLSA record. The site queries the MX record for your domain and checks the accompanying TLSA record. The result is shown in the following screenshot:

But there are more sites that can check your TLSA records. Mailhardener (https://www.mailhardener.com/tools/dane-validator) for example can do the same as shown in the following screenshot:

In this blog I am discussing DANE for mail server, but it can also be used for other services. For a website a TLSA record can be:

_443._tcp.www.exchangelabs.nl. IN TLSA 3 1 1 a63d74fc7ec1acad702017d13479a1b60b36f234ccb96b90f97c9619ba2c91ab

Or for IMAP it can be:

_995._tcp.mail.exchangelabs.nl. IN TLSA 3 1 1 a63d74fc7ec1acad702017d13479a1b60b36f234ccb96b90f97c9619ba2c91ab

The ugly part is that Exchange 2019 does not support DANE out of the box. For incoming mail, you can configure the TLSA record based on the certificate that is installed on the Exchange (Edge Transport) server. A DANE capable mail server automatically checks for a TLSA record and when available, use this to setup the TLS connection.

Exchange Online on the other hand only supports DANE for outbound connections at the time of writing. As an Exchange Online customer, there’s nothing you must configure, it’s available for everyone. DANE for inbound mail will become available in the future, but when is unclear. According to Microsoft it is ‘near future’ but unfortunately this is already the case for quite some time.

One final (and very important) closing note: When you have configured a TLSA record for your (inbound) Exchange server you MUST update your TLSA record when renewing the Exchange certificate. You must also do this when the Exchange server is used in a hybrid configuration, since Exchange Online will check for a TLSA record. Mail flow from Exchange Online to Exchange 2019 will halt and the following error message is shown in Exchange Online message trace:

Reason: [{LED=450 4.7.323 tlsa-invalid: The domain failed DANE validation [Message=450 4.7.323 tlsa-invalid: The domain failed DANE validation] [LastAttemptedServerName=smtphost.exchangelabs.nl] [LastAttemptedIP=185.116.41.45:25] [SmtpSecurity=11;-1] [HE1EUR01FT069.eop-EUR01.prod.protection.outlook.com 2023-06-06T10:23:06.0. OutboundProxyTargetIP: 185.116.41.45. OutboundProxyTargetHostName: smtphost.exchangelabs.nl

This is solved when a new TLSA record is generated and published in DNS.

You are currently using an older version of the Exchange Online PowerShell module

When connection to Exchange Online PowerShell using the command Connect-ExchangeOnline nothing special happens. But when executing a PowerShell command like Get-Mailbox you get the following warning:

PS C:\WINDOWS\system32> $mailboxes = Get-mailbox -ResultSize unlimited
Attention!
You are currently using an older version of the Exchange Online PowerShell module which uses RPS. RPS deprecation has been announced and you will need to move to the latest V3 module by June 2023. Read more here: https://aka.ms/RPSDeprecation 
Please install our new REST-based PS V3 module downloadable from https://www.powershellgallery.com/packages/ExchangeOnlineManagement/, which is more secure and reliable.
Please note that you will no longer be able to use -UseRPSSession after June 2023.

Microsoft has introduced a new Exchange Online PowerShell module (v3) and the previous modules will be deprecated in the (near) future.

To update the PowerShell module, the old module needs to be uninstalled first before you can install the latest version. To uninstall the old v2 module, execute the following command:

Uninstall-Module ExchangeOnlineManagement

And to install the new v3 module, execute the following command:

Install-Module -Name ExchangeOnlineManagement -RequiredVersion 3.0.0

To check which PowerShell modules and versions you have installed, execute the Get-InstalledModule command.

Migrating Exchange 2016 Public Folders to Office 365

Many customers are running in an Exchange hybrid environment where they have mailboxes in Exchange Online and in Exchange on-premises and a lot of my customers have Exchange 2016 running on-premises. Not a lot of customers still have Public Folders in Exchange on-premises, but they are still there. This blog explains steps to migrate Modern Public Folders from Exchange 2016 to Exchange Online, but this blog is also valid for modern Public Folders in Exchange 2013 and Exchange 2019. If you are still running Exchange 2010 and you want to move your (legacy) Public Folders to Exchange Online, follow the steps in this Microsoft article: Use batch migration to migrate legacy public folders to Microsoft 365 or Office 365.

Note. This is a long read. It has also been a long project, preparations took a couple of weeks, the synchronization a couple of days, roll-back after the first attempt to finalize the migration and start over again with fixing the unexpected issues. When migration Public Folder from Exchange 2016 to Exchange Online, take your time and do it right!

When to migrate your Public Folders to Exchange Online

Public Folder access cross-premises is one-way only. Mailboxes in Exchange Online can access Public Folders in Exchange on-premises, but not the other way around. So, mailboxes in Exchange on-premises cannot access Public Folders in Exchange Online. You should only migrate Public Folder to Exchange Online, after you have migrated all user mailboxes to Exchange Online.

Note. I deliberately say “user mailboxes” in this context, you can still have mailboxes in Exchange on-premises for applications, service accounts, devices etc. that do not need to access Public Folders.

Requirements

The following requirements on-premises need to be met before the migration of Public Folders to Exchange Online can be started:

  • Exchange 2016 CU4 or higher (which is a no brainer in my opinion)
  • The Exchange administrator needs to be a member of the Organization Management role group (in Exchange Online and in Exchange 2016)
  • Public Folders need to be less than 25GB in size
  • User mailbox migration need to be finished before Public Folder migration starts
  • Migration needs to be executed using Exchange PowerShell. The Public Folder Migration option is not available in the Exchange Admin Center
  • All Public Folder data must be migrated in one single migration batch
  • Verify if the DefaultPublicFolderAgeLimit is configured on the organization level or if you have any AgeLimit configured for the individual Public Folders, so that automatic deletions of the content is prevented

e domains, the Exchange system objects (like mail-enabled Public Folder objects) can reside in multiple locations since these locations have changed over the years (with different versions of Exchange). Sometimes these object can be found in the root domain, but also in a child domain. If you are using an Active Directory environment with multiple domains, make sure you extend the scope of Powershell using the following command:

[PS] C:\> Set-ADServerSetting -ViewEntireForest $True

Migration Steps

Migration of Public Folders from Exchange 2016 to Exchange Online consists of the following steps:

  1. Download the migration scripts
  2. Prepare for the migration
  3. Generate the CSV files
  4. Create Public Folder mailboxes in Exchange Online
  5. Start Migration Request
  6. Lockdown Public Folders in Exchange 2016
  7. Finalize Public Folder migration
  8. Test and unlock Public Folders in Exchange Online
  9. Finalize the migration in Exchange 2016

These steps will be discussed in the following sections.

1. Download the migration scripts

Download the premigration or source side validation script from the Microsoft website https://aka.ms/ssv2 (SourceSideValidations.ps1) and the Public Folder migration scripts (https://www.microsoft.com/en-us/download/details.aspx?id=54855). Store the scripts on the Exchange server in the C:\PFScripts directory.

The SourceSideValidations.ps1 script does an inventory of all Public Folders in the Exchange organization and reports any issues that are found and can cause problems during the Public Folders migration.

The results of this script are written in the SourceSideValidations.csv in the PFScripts directory and contains something like this:

ResultTypeSeverityCountAction
TotalItemSizeError2Items should be deleted from these folders until the folder size is less than 25 GB.
EmptyFolderInformation138Folders contain no items and have only empty subfolders. These will not cause a migration issue, but they may be pruned if desired.
SpecialCharactersError12Folders have characters @, /, or \ in the folder name. These folders should be renamed prior to migrating. The following command can be used:

Import-Csv .\ValidationResults.csv |
? ResultType -eq SpecialCharacters |
% {
$newName = ($_.ResultData -replace “@|/|\”, ” “).Trim()
Set-PublicFolder $_.FolderEntryId -Name $newName
}
OrphanedMPFError37Mail public folders are orphaned. They exist in Active Directory but are not linked to any public folder. Therefore, they should be deleted. After confirming the accuracy of the results, you can delete them manually, or use a command like this to delete them all:
Import-Csv .\ValidationResults.csv |
? ResultType -eq OrphanedMPF |
% {
$folder = <see below>
$parent = ([ADSI]”$($folder.Parent)”)
$parent.Children.Remove($folder)
}
OrphanedMPFDuplicateError3Mail public folders point to public folders that point to a different directory object. These should be deleted. Their email addresses may be merged onto the linked object. After confirming the accuracy of the results, you can delete them manually, or use a command like this:
Import-Csv .\ValidationResults.csv |
? ResultType -eq OrphanedMPFDuplicate |
% {
$folder = <see below>
$parent = ([ADSI]”$($folder.Parent)”)
$parent.Children.Remove($folder)
}
OrphanedMPFDisconnectedError1Mail public folders point to public folders that are mail-disabled.
These require manual intervention. Either the directory object should be deleted, or the folder should be mail-enabled, or both.
Open the ValidationResults.csv and filter for ResultType of OrphanedMPFDisconnected to identify these folders. The
FolderIdentity provides the DN of the mail object. The FolderEntryId provides the EntryId of the folder.
BadpermissionError89Invalid permissions were found. These can be removed using the RemoveInvalidPermissions switch as follows:
.\SourceSideValidations.ps1 -RemoveInvalidPermissions

Note. In this table in row 4 and row 5 there’s the $folder variable. I was able to add the PowerShell command in there, so here’s the command. Please substitute as needed:

$Folder = ([ADSI]("LDAP://$($_.FolderIdentity)"))

Besides reporting the issues, the last column also reports the solutions about how to solve the issues. These are (also) discussed in the next section.

2. Prepare for the migration

The SourceSideValidations.ps1 script is a good starting point as it returns a number of potential issues for the migration and these need to be fixed before you can start the migration:

  • The TotalItemSize is easy to fix. Remember that the size is per folder, so if you have a large folder containing for example 40GB of data, you can create 3 subfolders and move 10GB of data in each subfolder. Problem solved 🙂
  • The EmptyFolder is informational. You can migrate these to Exchange Online, or decide to remove them before the migration.
  • Public Folder names that contain a backslash or a forward slash are not supported in Exchange Online. These are also reported by the SourceSideValidations.ps1 script. To check for Public Folders containing these characters, you can use the following commands in Exchange PowerShell on-premises:
[PS] C:\> Get-PublicFolder -Recurse -ResultSize Unlimited | Where {$_.Name -like "*\*" -or $_.Name -like "*/*"} | Format-List Name, Identity, EntryId

To remove these illegal characters, you can use the following commands in Exchange PowerShell on-premises:

Import-Csv .\ValidationResults.csv | ? ResultType -eq SpecialCharacters |
% {
  $newName = ($_.ResultData -replace "@|/|\\", " ").Trim()
  Set-PublicFolder $_.FolderEntryId -Name $newName
}
  • Check for orphaned folders, duplicate orphaned folders and disconnected orphaned folders. You can try to mail-disabled the corresponding Public Folder and re-enable the Public Folder again. I have also seen situations where the Exchange object in Active Directory (in the Microsoft Exchange System Objects container) was deleted using ADSI Edit.
  • Confirm SMTP Email addresses and Accepted Domains in Exchange on-premises and Exchange Online match. Most likely this is the case since all (user) mailboxes are already migrated, but it can happen that an email address is set on a Public Folder in Exchange 2016 with a domain that’s unknown in Exchange Online (contoso.local for example).
  • Also, make sure your public folders are stamped with a Microsoft online email address, like folder@contoso.mail.onmicrosoft.com. This makes life much easier after the migration, when you still have local application trying to send email to public folders.
  • Create an Accepted Domain with a well-known name to prevent messages getting lost in the DNS transition period. To do this, execute the following command in Exchange PowerShell on-premises:
[PS] C:\> New-AcceptedDomain -Name PublicFolderDestination_78c0b207_5ad2_4fee_8cb9_f373175b3f99 -DomainName "contoso.mail.onmicrosoft.com" -DomainType InternalRelay
  • Create a snapshot of the existing Public Folder environment in Exchange 2016. This can be useful when checking if the Public Folder migration was successful. The following four commands will export the Public Folders, the Public Folder Statistics, the Public Folder permissions and the mail-enabled Public Folders. Be aware that this can take a considerable amount of time, depending of the number of Public Folders in your organization.
[PS] C:\> Get-PublicFolder -Recurse -ResultSize Unlimited | Export-CliXML OnPrem_PFStructure.xml
[PS] C:\> Get-PublicFolderStatistics -ResultSize Unlimited | Export-CliXML OnPrem_PFStatistics.xml
[PS] C:\> Get-PublicFolder -Recurse -ResultSize Unlimited | Get-PublicFolderClientPermission | Select-Object Identity,User,AccessRights -ExpandProperty AccessRights | Export-CliXML OnPrem_PFPerms.xml
[PS] C:\> Set-ADServerSettings -ViewEntireForest $True
[PS] C:\> Get-MailPublicFolder -ResultSize Unlimited | Export-CliXML OnPrem_MEPF.xml

  • In Azure AD Connect there’s the option to synchronize Exchange Mail Public Folders to Azure AD, as shown in the following screenshot:

This is used for Directory Based Edge Blocking (DBEB) only so that external mail for Public Folders is not blocked by DBEB. DBEB is automatically available when recipients are in Exchange Online, so there’s no need to synchronize this using Azure AD Connect. Uncheck the Exchange Mail Public Folders in the Optional Features in Azure AD Connect.

Note. When the issues are solved and all prerequisites are met, run the SourceSideValidations.ps1 script again to see if no more issues are returned.

3. Generate .CSV Files

If you have done all the prerequisite step, it’s time to generate the CSV files in preparation of the actual Public Folder migration to Exchange Online.

The first CSV file is the Name-to-Folder size mapping file. This file contains three columns: FolderSize, DeletedItemSize and Foldername. To create this file, execute the Export-ModernPublicFolderStatistics.ps1 script with a filename option, like this:

[PS] C:\PFScripts\> .\Export-ModernPublicFolderStatistics.ps1 On_Prem_Stats.csv

The second CSV is created using the ModernPublicFolderToMailboxMapGenerator.ps1 script. This script creates a mapping between the Public Folders from the previous step to mailboxes in Exchange Online. For input, this script takes the CSV file from the previous step, together with the maximum Public Folder mailbox size and the maximum mailbox recoverable items quota.

The maximum mailbox size by default is 100GB, but it recommended to use 50GB here to anticipate for future growth of the Public Folders in this mailbox. The recommended size for recoverable items quota is 15GB.

The command to execute this script is something like this:

[PS] C:\> PFScripts\ .\ModernPublicFolderToMailboxMapGenerator.ps1 -MailboxSize 50GB -MailboxRecoverableItemSize 15GB -ImportFile On_Prem_Stats.csv -ExportFile PFMapping.csv

When you look at the output file you will see only two columns: TargetMailbox and FolderPath. The TargetMailbox shows the Public Folder mailboxes that will be created. These Public Folder mailboxes have generic names, like Mailbox1, Mailbox2, Mailbox3 etc. You can change the Public Folder mailbox names in the CSV file into something that’s more suitable for your environment, for example PFMailbox1, PFMailbox2, PFmailbox3 etc.

4. Create Public Folder mailboxes in Exchange Online

When all information has been gathered the Public Folder mailboxes can be created. One Public Folder mailbox will be used for the Public Folder hierarchy (this will be primary mailbox, and the first mailbox in the CSV file that was created earlier) and the others are used for storing the Public Folders contents.

To create the Public Folder mailboxes, use the following commands in Exchange Online (!) PowerShell:

$PFMappings = Import-Csv C:\PFScripts\PFMapping.csv
$PrimaryMailboxName = ($PFMappings | Where-Object FolderPath -eq "\").TargetMailbox
New-Mailbox -HoldForMigration:$True -PublicFolder -IsExcludedFromServingHierarchy:$False $PrimaryMailboxName
($PFMappings | Where-Object TargetMailbox -ne $PrimaryMailboxName).TargetMailbox | Sort-Object -Unique | ForEach-Object { New-Mailbox -PublicFolder -IsExcludedFromServingHierarchy:$False $_}

It is possible that some warning messages appear because of AD replication within Exchange Online as can be seen in the following screenshot. Just wait some time and these Public Folder mailboxes will become automatically.

5. Start Migration Request

Before starting the migration request, start the synchronization of mail-enabled public folders to Exchange Online. In my current project I don’t have to do this since this script was already running as part of the hybrid configuration (mailboxes in Exchange Online, Public Folders in Exchange 2016).

But to start this, run the following command:

[PS] C:\PFScripts> .\Sync-ModernMailPublicFolders.ps1 -CsvSummaryFile:sync_summary.csv

Note. Be aware that you use the latest version of this script. Older version (prior to June 2022) do not support Modern Authentication and will fail with an Access Denied error. I have blogged about this a couple of months ago: Sync-ModernMailPublicFOlders.ps1 fails with access denied.

It is possible that error messages are shown on the console, and if you have a lot of Public Folders these can also be quite a lot (been there, done that unfortunately), but you can import the sync_summary.csv file into Microsoft Excel for detailed analysis.

To create the migration request, we need the source credential of the PF Administrator, the endpoint where the MRS is running, the GUID of the hierarchy mailbox in Exchange 2016 and the Public Folder mapping file (PFMapping.csv) that was created in the previous step.

Execute the following command in Exchange 2016 PowerShell. Copy the value of $HierarchyGUID to (for example) Notepad since it will be used in the last command where the actual migration batch is created.

[PS] C:\PFScripts> $HierarchyGUID = (Get-OrganizationConfig).RootPublicFolderMailbox.HierarchyMailboxGuid.GUID

Execute the following command in Exchange Online PowerShell:

[PS] C:\PFScripts> $Source_Credential = Get-Credential Contoso\Administrator
[PS] C:\PFScripts> $Source_RemoteServer = "webmail.contoso.com"
[PS] C:\PFScripts> $Mapping = [System.IO.File]::ReadAllBytes('C:\PFScripts\PFMapping.csv')
[PS] C:\PFScripts> $PfEndpoint = New-MigrationEndpoint -PublicFolder -Name PublicFolderEndpoint -RemoteServer $Source_RemoteServer -Credentials $Source_Credential
[PS] C:\PFScripts> New-MigrationBatch -Name PublicFolderMigration -CSVData $Mapping -SourceEndpoint $PfEndpoint.Identity -SourcePfPrimaryMailboxGuid <HierarchyGUID> -NotificationEmails Administrator@contoso.com

As shown in the following screenshot:

Note. Please take some time between creating the Public Folder mailboxes (especially the primary hierarchy mailbox), the New-MigrationEndpoint command will fail if there’s not enough time for internal Exchange Online replication, causing error messages like “mailbox <GUID> cannot be found”.

When created you can start the migration request using the following command in Exchange Online Powershell:

[PS] C:\PFScripts> Start-MigrationBatch PublicFolderMigration

When the migration batch is started, it will create a number of Public Folder Mailbox Migration Requests, depending on the number of Public Folder Mailboxes. In this example, there are 18 Public Folder mailboxes and 18 individual migration requests will be created as part of the migration batch. It took up to 4 hours before all migration requests were created. Be aware of this because at first you will be thinking that something is wrong 🙂

Use the following commands in Exchange Online PowerShell to monitor the migration batch and the individual migration requests:

[PS] C:\PFScripts> Get-PublicFolderMailboxMigrationRequest
[PS] C:\PFScripts> Get-PublicFolderMailboxMigrationRequestStatistics
[PS] C:\PFScripts> Get-MigrationBatch -Identity PublicFolderMigration

What I personally do for some more details is adding some more options and using the format-table feature, like this:

[PS] C:\PFScripts\> Get-PublicFolderMailboxMigrationRequest | Get-PublicFolderMailboxMigrationRequestStatistics | Select TargetMailbox,Status,StatusDetail,ItemsTransferred,BytesTransferred,BytesTransferredPerMinute,PercentComplete | ft -a

If for some reason you have failed migration requests in your migration batch, you can always run the Start-MigrationBatch -Identity PublicFolderMigration command to resume the batch.

Another interesting issue I had was that the PFMailbox1 that holds the hierarchy, failed synchronization with StatusDetail ‘FailedOther’. When requesting the PublicFolderMailboxMigrationStatistics for this PFMailbox, the following error is returned:

olderMappingFlags: InheritedInclude" could not be mail-enabled. The error is as follows: "No mail public folder was found in Active Directory with OnPremisesObjectId='dd887445-0b0a-447f-a6fc-889cc49ab16c' or LegacyExchangeDN='/CN=Mail Public Folder/CN=Version_1_0/CN=e71f13d1-0178-42a7-8c47-24206de84a77/CN=000000006F0ABC0AC0DF544387022DEA38DAE5840100F33760E70CFA4C489E930054C1EC880900038611485B0000'". This may indicate that mail public folderobjects 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.

Synchronization takes a couple of days (I started on Tuesday and plan to finalize the next weekend) and during that time new mail-enabled Public Folders are created and synchronized.

To bring this back in sync, run the Sync-ModernMailPublicFolders.ps1 script again, wait some time (for replication in Exchange Online) and start the migration batch again.

Remember that up to this point, users can continue to work just like they can when their mailbox is migrated to Exchange Online. Only when the migration request is finalized the Public Folders are not available.

6. Lockdown Public Folders in Exchange 2016

Finalizing the Public Folder migration is not different than when migrating Mailboxes from Exchange 2016 to Exchange Online. When the finalization takes places, users are logged off of the Public Folders, the last content is migrated to Exchange Online and the Public Folder mailboxes in Exchange Online become active. However, they are not automatically available to users, some additional steps (including testing) are needed.

Important to note is that a Public Folder migration finalization can take a lot of time, depending on the number of folders, the number of items (equals data) and if there are corrupt ACLs in the source Public Folders. Microsoft recommends to plan at least 48 hours of downtime during the Public Folder migration finalization.

To check if the Public Folder migration batch is successfully synced (and thus ready to finalize) use the following commands in Exchange Online PowerShell:

[PS] C:\PFScripts> Get-MigrationBatch -Identity PublicFolderMigration | ft *last*sync*
[PS] C:\PFScripts> Get-PublicFolderMailboxMigrationRequest | Get-PublicFolderMailboxMigrationRequestStatistics |ft targetmailbox,*last*sync*

Preferably, the LastSyncedDate on the migration batch and the LastSuccessfulSyncTimestamp on the individual jobs) should be within the last 7 days as can be seen in the following screenshot. If it’s not, check the Public Folder migration requests to see why it is not in sync.

If all is ok, you can lock down the Public Folders in Exchange 2016 by executing the following command in Exchange PowerShell:

[PS] C:\PFScripts> Set-OrganizationConfig -PublicFolderMailboxesLockedForNewConnections $true

After (Active Directory) replication you can check if Public Folders do not accept new connections anymore by typing the following command in Exchange PowerShell:

[PS] C:\PFScripts> Get-PublicFolder \

It should generate an error message saying “Could not find the public folder mailbox” as shown in the following screenshot:

7. Finalize Public Folder migration

Before finalizing the Public Folder migration you should run the SyncModernPublicFolders.sp1 script again to make sure that newly created Mail-Enabled Public Folder (or better, their email addresses) are synchronized with Exchange Online.

[PS] C:\PFScripts> .\Sync-ModernPublicFolders.ps1 -CsvSummaryFile:Sync_Summary.csv

And to complete the migration batch:

[PS] C:\PFScripts> Complete-MigrationBatch PublicFolderMigration

Migrationbatch status will change from Synced to Completing. This can take a tremendous amount of time; Microsoft recommendation is to take 48 hours into account for this. In my scenario, there are 18 Public Folder mailboxes that are sync. The on-premises Public Folders were closed around 10PM on Friday night, but after twelve hours all Public Folder Mailbox Requests still had a status of ‘synced’. But when requesting the details of the migration batch (Get-MigrationBatch | fl) the TriggeredAction property of the migration batch was set to SyncAndComplete. Microsoft also says that it can take up to 24 hours before the status of the migration batch and the corresponding migration requests change from ‘synced’ to ‘Completing’. In the meantime, you can only wait, and check back every few hours. Eventually the migrationbatch status will change to Completed.

When the migration batch is completed you can test the Public Folders in Exchange. To do this, configure a user account with the default Public Folder mailbox in Exchange Online, using the following command in Exchange Online PowerShell:

[PS] C:\PFScripts> Set-Mailbox -Identity <user> -DefaultPublicFolderMailbox PFMailbox1

The default Public Folder mailbox is the first PF mailbox (holding the hierarchy) that was created in a previous step using the ModernPublicFolderToMailboxMapGenerator.ps1 script.

Make sure you can see the PF hierarchy, check the permissions, create some Public Folders (and delete them) and post some content into Public Folders (both direct as via email). In our first attempt, permissions failed and we had to roll-back the migration. It took over 6 weeks before we could do a second attempt (ok, I have to admit, it was holiday time, but still….).

When tested successfully, change the Public Folders for all users. This is an organizational setting and can be changed using the following command in Exchange Online PowerShell:

[PS] C:\PFScripts> Set-OrganizationConfig -RemotePublicFolderMailboxes $Null -PublicFoldersEnabled Local

It is possible that SMTP message are stuck in SMTP Queues on the Exchange 2016 servers during the migration. To redirect these stuck messages run the following command on your Exchange 2016 server:

[PS] C:\PFScripts> $Server=Get-TransportService;ForEach ($t in $server) {Get-Message -Server $t -ResultSize Unlimited| ?{$_.Recipients -like "*PF.InTransit*"} | ForEach-Object {Suspend-Message $_.Identity -Confirm:$False; $Temp="C:\ExportFolder\"+$_.InternetMessageID+".eml"; $Temp=$Temp.Replace("<","_"); $Temp=$Temp.Replace(">","_"); Export-Message $_.Identity | AssembleMessage -Path $Temp;Resume-message $_.Identity -Confirm:$false}}

To stamp Mail-Enabled Public Folder objects in Active Directory with an external email address in Exchange Online (i.e. @contoso.mail.onmicrosoft.com) execute the following PowerShell script in Exchange 2016:

[PS] C:\PFScripts> .\SetMailPublicFolderExternalAddress.ps1 -ExecutionSummaryFile:mepf_summary.csv

And the ultimate last step, set the Public Folders in Exchange 2016 to ‘remote’. This is an organizational settings and can be configured by executing the following command in Exchange 2016:

[PS] C:\PFScripts> Set-OrganizationConfig -PublicFolderMailboxesMigrationComplete:$true -PublicFoldersEnabled Remote

8. Remove Public Folder Mailboxes

After some time, if you are 100% sure you are not going to roll-back your Public Folder migration, the Public Folder Mailboxes in Exchange 2016 can be removed. Remember, this step is irreversible!

Roll-back Public Folder migration

If for some reason you must roll-back the migration you must execute the PowerShell commands in a reverse order. Be aware that if you roll back the migration after you finalized the migration, you will lose all mail delivered to the Public Folders in Exchange Online (unless you manually copy all new items to a location in a mailbox, which is practically impossible of course).

The first step is to unlock the Public Folder migration in Exchange 2016 by using the following command:

[PS] C:\> Set-OrganizationConfig -PublicFoldersLockedForMigration:$False

Delete all Public Folder mailboxes in Exchange Online using the following Powershell commands:

PS] C:\> $hierarchyMailboxGuid = $(Get-OrganizationConfig).RootPublicFolderMailbox.HierarchyMailboxGuid
[PS] C:\> Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -ne $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false -Force
[PS] C:\> Get-Mailbox -PublicFolder:$true | Where-Object {$_.ExchangeGuid -eq $hierarchyMailboxGuid} | Remove-Mailbox -PublicFolder -Confirm:$false -Force

If you run into issues with this, check one of my previous blogs Multiple Mailbox users match identity “Mailbox1” (which I ran into after a roll-back and a 2nd migration attempt).

The last step is to undo the migration completion in the organization config of Exchange 2016 using the following command:

[PS] C:\> Set-OrganizationConfig -PublicFolderMigrationComplete:$False

You should now be able to continue to work with the Public Folders in Exchange 2016.

Send-As and Send-on-Behalf permissions

A common pitfall is that Send-As and Send-on-Behalf permissions are not migrated to Exchange Online. If you are using these permissions you have to identify the Public Folders that have these permissions applied using the following commands:

[PS] C:\> Get-MailPublicFolder | Get-ADPermission | ?{$_.ExtendedRights -like "*Send-As*"}
[PS] C:\> Get-MailPublicFolder | ?{$_.GrantSendOnBehalfTo -ne "$null"} | Format-Table name,GrantSendOnBehalfTo

To grant these permissions in Exchange Online, use the following example commands in Exchange Online PowerShell:

[PS] C:\> Add-RecipientPermission -Identity <Public Folder> -Trustee <User> -AccessRights SendAs
[PS] C:\> Set-MailPublicFolder -Identity <Public Folder> -GrantSendOnBehalfTo <User>

Most likely you will have tons of Public Folders with these permissions, to you must first export to a CSV file, following with an import of the CSV file and assigning permissions in Exchange Online.

Allow anonymous users to send email to a mail-enabled Public Folder

I did not run into this after the migration to Exchange Online, but it might be possible that you lose the anonymous user permissions to send mail to a public folder after the migration. To make sure anonymous users can send email to a Public Folder, use the following command in Exchange Online PowerShell:

[PS] C:\> Add-PublicFolderClientPermission -Identity "\publicfoldername" -User "Anonymous" -AccessRights CreateItems

Summary

In this blog I tried to write down my experiences with a recent Public Folder migration from Exchange 2016 to Exchange Online. Depending on your Public Folder infrastructure this can take a considerable amount of time. And even if you have everything right, there still is the possibility that something goes wrong and you have to start all over again.

Although after the migration the Public Folder infrastructure is Microsoft’s problem and you only have the service available, I still recommend not migrate your Public Folders to Exchange Online and look for a different solution like Microsoft 365 Groups, Shared Mailboxes or Microsoft Teams. That’s the better solution, and at least it’s more future proof than Public Folders.