Use AAD Connect to transform your UPN for tenant-to-tenant migrations

5/5 - (2 votes)

Background

Howdy!

Here’s a great tip that I recently put into practice again for a Microsoft 365 tenant-to-tenant migration. As you (should) know, a custom domain can only be verified in a single Microsoft 365 tenant. When performing tenant-to-tenant migrations, at some point, you need to remove the domain from the source tenant and add to the target tenant–but you can’t do that when the domain is already attached to mailboxes that you’re actively migrating.

And, for large customers with tens of thousands of users being synchronized from on-premises Active Directory, this can be a daunting task.

The options can appear bleak:

  1. Disable directory synchronization to the source tenant and then run scripts to reset the UPN and remove proxy addresses referencing the domains being transferred on the newly-disconnected cloud-only objects.
  2. Update the on-premises UPN and remove the proxy addresses referencing the domain(s) being transferred.
  3. Cry, and then proceed with option 1 or 2.

Couple that with trying to populate objects in a target tenant at the same time for your migration, and your problems multiply.

What if there was another way?

Fortunately, there is.

Remove Proxy

It starts with this little script I wrote years ago for a very similar purpose (https://www.powershellgallery.com/packages/New-AADConnectRuleRemoveProxy/). With it, you can specify one or more patterns to remove from the proxyAddresses attribute.

Now, for the new stuff!

UPN Rewrite

The next part is where a new bit of magic happens–by creating another AAD Connect rule, you can configure AAD Connect to rewrite user object UPNs on the way out to Azure AD–freeing up another attribute that could contain the referenced domain you’re trying to remove.

While I’m waiting for my PowerShell Gallery account to be re-associated with a new ID, I’ll post this companion script here:

<#PSScriptInfo

.VERSION 1.0.0

.GUID 4516e116-b74a-4efa-9331-03fbd7d1215f

.AUTHOR Aaron Guilmette

.COMPANYNAME Planet Technologies

.COPYRIGHT 2023

.TAGS

.LICENSEURI

.PROJECTURI https://undocumented-features.com/2023/08/21/use-aad-connect-to-transform-your-upn-for-tenant-to-tenant-migrations

.ICONURI

.EXTERNALMODULEDEPENDENCIES

.REQUIREDSCRIPTS

.EXTERNALSCRIPTDEPENDENCIES

.RELEASENOTES

.DESCRIPTION 
Use this script to configure an AAD connect rule transform the UPN on the way out to Azure AD.

.PRIVATEDATA

#>
<# 
.SYNOPSIS  
Create a new AADConnect rule to rewrite a UPN suffix pattern. 

.PARAMETER LowestPrecedence 
Automatically create new rule as the lowest precedence rule (highest priority).

.PARAMETER Name
Specify the name for the new rule

.PARAMETER OldUPN 
Specify the existing UPN suffix pattern to match, e.g. contoso.com.

.PARAMETER NewUPN
Specify the new UPN suffix to rewrite objects with, e.g. contoso.onmicrosoft.com.

.PARAMETER Precedence 
Choose a precedence value. 

.EXAMPLE 
.\New-AADConnectRuleRewriteUPN.ps1 -OldUPN contoso.com -NewUPN contoso.onmicrosoft.com -LowestPrecedence 

Create a new AAD Connect rule to replace instances of contoso.com UPN with
contoso.onmicrosoft.com in Azure AD using the lowest available precedence rule.

.LINK 
https://aka.ms/aarongallery

.LINK
https://undocumented-features.com/2023/08/21/use-aad-connect-to-transform-your-upn-for-tenant-to-tenant-migrations

.NOTES
2023-08-21
    - Initial release.
#>
[CmdletBinding()]
param (
    [switch]$LowestPrecedence,
    [string]$Name = "Out to AAD - User Rewrite UPN",
    [Parameter(Mandatory=$true)][string]$OldUPN,
    [Parameter(Mandatory=$true)][string]$NewUPN,
    [string]$Precedence = "90"
)

# Update UPN formatting to lowercase
$OldUPN = $OldUPN.ToLower()
$NewUPN = $NewUPN.ToLower()

# Build the UPN transform expression
$Expression = New-Object System.Text.Stringbuilder
$Expression.Append("Replace(LCase([userPrincipalName]),`"$($OldUPN)`",`"$($NewUPN)`")") | Out-Null
$Expression = $Expression.ToString().TrimEnd()

If ($LowestPrecedence)
{
    [array]$AllRulesPrecedence = (Get-ADSyncRule).Precedence
    $Precedence = (($AllRulesPrecedence | Measure-Object -Minimum).Minimum - 1)
}

[string]$global:Identifier = [Guid]::NewGuid().ToString()
[string]$Connector = (Get-ADSyncConnector | ? { $_.Name -like "* - AAD" }).Identifier.ToString()

New-ADSyncRule  `
               -Name $Name `
               -Identifier $Identifier `
               -Description "Rewrite User UPN from $($OldUPN) to $($NewUPN)" `
               -Direction 'Outbound' `
               -Precedence $Precedence `
               -PrecedenceAfter '00000000-0000-0000-0000-000000000000' `
               -PrecedenceBefore '00000000-0000-0000-0000-000000000000' `
               -SourceObjectType 'person' `
               -TargetObjectType 'user' `
               -Connector $Connector `
               -LinkType 'Join' `
               -SoftDeleteExpiryInterval 0 `
               -ImmutableTag '' `
               -OutVariable syncRule

Add-ADSyncAttributeFlowMapping  `
                               -SynchronizationRule $syncRule[0] `
                               -Destination 'userPrincipalName' `
                               -FlowType 'Expression' `
                               -ValueMergeType 'Update' `
                               -Expression $Expression `
                               -OutVariable syncRule

Add-ADSyncRule  `
               -SynchronizationRule $syncRule[0]

Write-Host -NoNewLine "New AD Sync Rule Created: "
Write-Host -ForegroundColor Green "$($Name)"
Write-Host -NoNewline "Object Guid: "
Write-Host -ForegroundColor Green "$($Identifier)"

Use this in conjunction with the Remove Proxy rule and you’ll have made a giant step towards your project goal.

Footnote

Before deploying these rules as part of a migration effort where you’re persisting the domain between tenants, you’ll want to disable hybrid write-back to Active Directory if you have it configured (via AAD Connect setup). After all, the goal was to keep your on-premises environment the same–if you have Exchange Hybrid write-back enabled, you’re going to fail miserably.