Exchange Online Cross-tenant mailbox migration (preview)
Hi All,
There is a Preview for M365 Tenant to Tenant Migration of Exchange Mailboxes. I've tested this in my Lab. Took me several Days to complete the Migration or until i understand everything correctly and had everything fixed.
Cross-tenant mailbox migration (preview)
I've created this Overview to explain it a bit more. Here are all prerequisits to create a Tenant to Tenant (aka Cross-tenant) Mailbox Migration.
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_00.jpg)
Target Tenant
Azure AD Application
Create a new Azure AD App registration
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_01.jpg)
Give it a Name, select Multitenant and Redirect URL is "Web" "https://office.com"
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_02.jpg)
Add a ClientSecret
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_03.jpg)
Add Permissions from "APIs my organization uses" and search for "Office 365 Exchange"
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_04.jpg)
Select Application Permission and select "Mailbox.Migration"
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_05.jpg)
The default Permission "User.read" can be removed. Afterwards cklick on "Grant Admin Consent"
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_06.jpg)
That's how it should look at the end
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_07.jpg)
Migration Endpoint
Check the OrganizationCustomization
# Enable customization if tenant is dehydrated
$dehydrated = Get-OrganizationConfig | select isdehydrated
if ($dehydrated.isdehydrated -eq $true)
{
Enable-OrganizationCustomization
}
$dehydrated = Get-OrganizationConfig | select isdehydrated
if ($dehydrated.isdehydrated -eq $true)
{
Enable-OrganizationCustomization
}
Now you can create a new Migration Endpoint with the AzureAD AppId and the ClientSecret as Password.
#New MigrationEndpoint
$AppId = "6aa3cf87-4754-43e2-8ec6-606f6b938ae8"
$ClientSecret = "YourClientSecret"
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, (ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force)
New-MigrationEndpoint -RemoteServer outlook.office.com -RemoteTenant "serveralive.onmicrosoft.com" -Credentials $Credential -ExchangeRemoteMove:$true -Name "ServerAliveMigrationEndpoint" -ApplicationId $AppId
$AppId = "6aa3cf87-4754-43e2-8ec6-606f6b938ae8"
$ClientSecret = "YourClientSecret"
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AppId, (ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force)
New-MigrationEndpoint -RemoteServer outlook.office.com -RemoteTenant "serveralive.onmicrosoft.com" -Credentials $Credential -ExchangeRemoteMove:$true -Name "ServerAliveMigrationEndpoint" -ApplicationId $AppId
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_09.jpg)
Organization Relationship
Create a new Organization Relationship with MailboxMoveCapability Inbound
#OrganizationRelationship
$SourceTenantName="serveralive.onmicrosoft.com"
$SourceTenantId="ebc81bf5-2cef-414c-a948-2bbb116fae48"
$OrgRels = Get-OrganizationRelationship
$ExistingOrgRel = $OrgRels | ?{$_.DomainNames -like $sourceTenantId}
If ($null -ne $ExistingOrgRel)
{
Set-OrganizationRelationship $existingOrgRel.Name -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability Inbound
}
If ($null -eq $existingOrgRel)
{
New-OrganizationRelationship $SourceTenantName -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability Inbound -DomainNames $sourceTenantId
}
Get-OrganizationRelationship -Identity serveralive.onmicrosoft.com | fl
$SourceTenantName="serveralive.onmicrosoft.com"
$SourceTenantId="ebc81bf5-2cef-414c-a948-2bbb116fae48"
$OrgRels = Get-OrganizationRelationship
$ExistingOrgRel = $OrgRels | ?{$_.DomainNames -like $sourceTenantId}
If ($null -ne $ExistingOrgRel)
{
Set-OrganizationRelationship $existingOrgRel.Name -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability Inbound
}
If ($null -eq $existingOrgRel)
{
New-OrganizationRelationship $SourceTenantName -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability Inbound -DomainNames $sourceTenantId
}
Get-OrganizationRelationship -Identity serveralive.onmicrosoft.com | fl
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_10.jpg)
Source Tenant
Grant the Permissions to the Azure AD App in the Source Tenant
$AppID="6aa3cf87-4754-43e2-8ec6-606f6b938ae8"
$SourceTenant = "serveralive.onmicrosoft.com"
$URL = "https://login.microsoftonline.com/$SourceTenant/adminconsent?client_id=$AppID&redirect_uri=https://office.com"
$URL
$SourceTenant = "serveralive.onmicrosoft.com"
$URL = "https://login.microsoftonline.com/$SourceTenant/adminconsent?client_id=$AppID&redirect_uri=https://office.com"
$URL
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_10b.jpg)
Use the URL in the Browser
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_11.jpg)
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_12.jpg)
Accept the Permissions
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_14.jpg)
Create a Mail enabled Security Group
Create the Mail enabled Security Group. Only Members of that Group can be migrated later on.
#Create MailEnabled Security Group
New-DistributionGroup -Name "T2TMigrationEXO" -Alias T2TMigrationEXO -Type security
New-DistributionGroup -Name "T2TMigrationEXO" -Alias T2TMigrationEXO -Type security
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_15.jpg)
Create Organization Relationship
Now we need to create an Organization Relationship with the MailboxMoveCapability RemoteOutbound
#Organization Relationship in Source
$TargetTenantName="icewolfch.onmicrosoft.com"
$TargetTenantId="46bbad84-29f0-4e03-8d34-f6841a5071ad"
$AppID="6aa3cf87-4754-43e2-8ec6-606f6b938ae8"
$scope="T2TMigrationEXO"
$OrgRels = Get-OrganizationRelationship
$ExistingOrgRel = $OrgRels | ?{$_.DomainNames -like $targetTenantId}
If ($null -ne $existingOrgRel)
{
Set-OrganizationRelationship $existingOrgRel.Name -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability RemoteOutbound -OAuthApplicationId $appId -MailboxMovePublishedScopes $scope
}
If ($null -eq $existingOrgRel)
{
New-OrganizationRelationship $TargetTenantName -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability RemoteOutbound -DomainNames $targetTenantId -OAuthApplicationId $appId -MailboxMovePublishedScopes $scope
}
Get-OrganizationRelationship -Identity icewolfch.onmicrosoft.com | fl
$TargetTenantId="46bbad84-29f0-4e03-8d34-f6841a5071ad"
$AppID="6aa3cf87-4754-43e2-8ec6-606f6b938ae8"
$scope="T2TMigrationEXO"
$OrgRels = Get-OrganizationRelationship
$ExistingOrgRel = $OrgRels | ?{$_.DomainNames -like $targetTenantId}
If ($null -ne $existingOrgRel)
{
Set-OrganizationRelationship $existingOrgRel.Name -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability RemoteOutbound -OAuthApplicationId $appId -MailboxMovePublishedScopes $scope
}
If ($null -eq $existingOrgRel)
{
New-OrganizationRelationship $TargetTenantName -Enabled:$true -MailboxMoveEnabled:$true -MailboxMoveCapability RemoteOutbound -DomainNames $targetTenantId -OAuthApplicationId $appId -MailboxMovePublishedScopes $scope
}
Get-OrganizationRelationship -Identity icewolfch.onmicrosoft.com | fl
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_16.jpg)
Source Mailbox
Now it's time to Gather Infos from the Source Mailbox
We need:
- PrimarySMTPAddress
- MailboxGUID
- LegacyExchangeDN
- All X500 EmailAdresses
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_17.jpg)
Target Mail User
The Target needs to be a Mail user.
In my Environement i create the Mail User on my Exchange Server OnPrem. The ExternalEmailAddress is the PrimarySMTPAddress from the Source.
$Password = ConvertTo-SecureString "MySecretPassword" -AsPlainText -Force
New-Mailuser -Name "Hans Muster" -FirstName "Hans" -LastName "Muster" -PrimarySmtpAddress "h.muster@icewolf.ch" -ExternalEmailAddress "hans.muster@serveralive.ch" -Alias "h.muster" -OrganizationalUnit "corp.icewolf.ch/Icewolf Users" -UserPrincipalName "h.muster@icewolf.ch" -SamAccountName "h.muster" -Password $Password
Now we ned to set the MailboxGUID und the LegacyExchangeDN and all X500 from the Soure Mailbox as X500 Addresses on the Target Mail User
$ExchangeGuid = "9e82fc17-be93-4aac-8214-0c1118700728"
$Mailuser = Get-MailUser h.muster
$EmailArray = $Mailuser.EmailAddresses
$EmailArray.Add("X500:/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4b58e230c8a64b16a4fc919c818172a0-Hans Muster")
$EmailArray.Add("x500:/o=First Organization/ou=External (FYDIBOHF25SPDLT)/cn=Recipients/cn=817c735da1b043cd9f44725d6bf06fc3")
$EmailArray.Add("X500:/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=e7ff61a4af774705ad0872b051070535-Hans")
Set-MailUser h.muster -ExchangeGuid $ExchangeGuid -EmailAddresses $EmailArray
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_18.jpg)
If there was a Mailbox before make sure it is deleted in Exchange Online completely
#Clear old Mailboxes
Set-User k.klammer -PermanentlyClearPreviousMailboxInfo
Set-User k.klammer -PermanentlyClearPreviousMailboxInfo
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_19.jpg)
Let's wait for AAD Connect until the Mail User has been syncronized to Exchange Online
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_20.jpg)
Test Migration
Connect to Exchange Online in the Target Tenant.
Now we can test if everything works. You need to use the MigrationEndpoint we created earlier with the Primary Emailadress of the Target Mail User
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_21.jpg)
If you receive an Error it will look like this.
Check again if you have added all X500 from the Source Mailbox
And also added the LegacyExchangeDN from The Source Mailbox as X500 on the Target Mailuser
Don't forget to set the MailboxGUID on the Target Mailuser
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_xx.jpg)
Migration Batch
Now we can create a Migration Batch. The CSV Format for the Batchfiles are documented in the Link below
CSV files for mailbox migration
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_22.jpg)
Now we can create the Migration Batch with the CSV File
New-MigrationBatch -Name T2TMigrationBatch -SourceEndpoint ServerAliveMigrationEndpoint -CSVData ([System.IO.File]::ReadAllBytes('C:\Temp\users.csv')) -Autostart -TargetDeliveryDomain icewolfch.onmicrosoft.com
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_23.jpg)
Check the Migration Batch
Get-MigrationBatch
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_24.jpg)
After a while you can see also the Moverequests
Get-MoveRequests
Get-MoveRequests | Get-MoveRequestStatistics
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_25.jpg)
You can see the Migration Batch also in the Echange Online Admin Center
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_26.jpg)
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_27.jpg)
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_28.jpg)
Every 24 Hours i receive a Status Report per Email
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_29.jpg)
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_30.jpg)
Complete Migration
The Sync of the Mailbox is up to 95%. Now we can complete the Migration Batch.
Get-MigrationBatch
Get-MigrationBatch -Identity T2TMigrationBatch
Complete-MigrationBatch - Identity T2TMigrationBatch
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_31.jpg)
Once the Migration Batch has been completed, i receive a Completion Mail
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_32.jpg)
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_33.jpg)
The Moverequest has been Completed
Get-MigrationBatch
Get-MoveRequest
Get-MoveRequest -Identity <Identity> | Get-MoveRequestStatistics
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_34.jpg)
Mail User in Source after Migration
After the Migration the Mailbox in the Source has been convertet to a Mail User. The ExternalEmailAddress has been set to the M365 Emailaddress. Emails to Hans.Muster@serveralive.ch will now be forwarded to h.muster@icewolfch.onmicrosoft.com.
Get-Mailuser -Identity hans.muster | fl Name, *Email*
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_35.jpg)
Target Mailbox after Migration
And this is how the Mailbox in the Target Tenant looks after the Migration.
Get-Mailbox h.muster | fl name, *email*, *smtp*
![](https://icewolffile.blob.core.windows.net/$web/202207/EXO_CrossTenantMailboxMigration_36.jpg)
Final Words
This "built-In" Method of Tenant to Tenant (or Cross-tenant) Mailbox Migration works fine.
But for a Migration for multiple Mailboxes, there is some serious Scripting needed.
Get all the Attributes from the Soure Mailboxes and create the Mailusers in the Target Tenant.
But remember, with that only the Mailboxes are moved. But there exist many more Mail enabled Objects:
- Mail Contacts
- Mail Users
- Distribution Groups
- M365 Groups / Teams
These Objects are not Covered and might have to be created also in the Target Tenant.
Regards
Andres Bohren
![](https://icewolffile.blob.core.windows.net/$web/logos/Exchange_logo.png)