Exchange 2007 to Office 365 Staged Migration – convert Mailbox to MEU

Overview

In an Exchange 2007 to Office 365 Staged Migration, when a mailbox has been successfully migrated to Exchange Online, it is necessary to convert the Mailbox to a Mail Enabled User (MEU).

MEUs have email addresses and accounts in the Exchange Organisation.  They do not have Exchange mailboxes.  Messages sent to MEUs are delivered to the specified external email address (targetAddress – e.g. steve.bush@x500.onmicrosoft.com).

Conversion to an MEU needs to happen for the following two reasons.

Double Mailboxes: when a mailbox has been successfully migrated to Exchange Online in a Staged Migration scenario, the user ends up with two mailboxes: one on Exchange 2007, the other on Exchange Online.

Although targetAddress is set on the source Exchange 2007 mailbox, meaning no mail will be delivered to the mailbox, there is nothing to stop the user opening their mailbox in Outlook (Autodiscover will direct them to it, as Autodiscover records cannot be changed to resolve to Exchange Online until all mailboxes have been migrated).

Exchange 2007 Decommissioning: at some point in the near future, when all mailboxes have been migrated to Exchange Online, it will be necessary to decommission Exchange 2007.  If mailboxes are deleted, all mail related attributes will also be removed, causing synchronisation issues between local AD and Azure AD.  Mail related attributes will not be removed from MEUs when Exchange 2007 is decommissioned.

MEU Attributes

When a Mailbox is converted to an MEU, the following attributes are removed from the local AD object:

  • homeMDB
  • homeMTA
  • mDBUseDefaults
  • msExchHomeServerName
  • msExchMailboxSecurityDescriptor
  • msExchPoliciesIncluded
  • msExchRecipientTypeDetails
  • msExchUserAccountControl
  • msExchUserCulture

Proxy addresses from the Exchange Online mailbox are added to proxyAddresses on the local AD object.  Addresses in red have been added:

proxyAddresses (3): X500:/o=x500 Org/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=SteveTest1; X500:/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4b260b5dbfb645df8981aaebd9a3e3b0-Steve Test1; SMTP:Steve.Test1@x500.co.uk

The following attributes are also added to the local AD object:

  • msExchRecipientDisplayType: 6 (6 is a MailContact).
  • msExchPoliciesExcluded: {26491cfc-9e50-4857-861b-0cb8df22b5d7} (This is the value applied when Exchange Recipients are excluded from email address policies – EmailAddressPolicyEnabled $false).

Post-conversion

The properties of the MEU enable DirSync (here Azure AD Connect) to match the MEU with the corresponding Exchange Online mailbox.

The Autodiscover service uses the MEU to connect Outlook to the cloud mailbox after the user creates a new Outlook profile (based on Autodiscovery against the targetAddress).

The mailbox will be disconnected from Exchange, and visible in ‘Disconnected Mailboxes’:

Disconnected Mailboxes

PowerShell Scripts

Microsoft provide two scripts to collect proxy addresses from the Exchange Online mailbox, and convert the mailbox to an MEU.

Save both of these scripts to the same folder.

First Script: ExportO365UserInfo.ps1

Param($migrationCSVFileName = "migration.csv")

function O365Logon
{
	#Check for current open O365 sessions and allow the admin to either use the existing session or create a new one
	$session = Get-PSSession | ?{$_.ConfigurationName -eq 'Microsoft.Exchange'}
	if($session -ne $null)
	{
		$a = Read-Host "An open session to Office 365 already exists.  Do you want to use this session?  Enter y to use the open session, anything else to close and open a fresh session."
		if($a.ToLower() -eq 'y')
		{
			Write-Host "Using existing Office 365 Powershell Session." -ForeGroundColor Green
			return	
		}
		$session | Remove-PSSession
	}
	Write-Host "Please enter your Office 365 credentials" -ForeGroundColor Green
	$cred = Get-Credential
	$s = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $cred -Authentication Basic -AllowRedirection
	$importresults = Import-PSSession -Prefix "Cloud" $s
}

function Main
{

	#Verify the migration CSV file exists
	if(!(Test-Path $migrationCSVFileName))
	{
		Write-Host "File $migrationCSVFileName does not exist." -ForegroundColor Red
		Exit
	}
	
	#Import user list from migration.csv file
	$MigrationCSV = Import-Csv $migrationCSVFileName
	
	#Get mailbox list based on email addresses from CSV file
	$MailBoxList = $MigrationCSV | %{$_.EmailAddress} | Get-CloudMailbox
	$Users = @()

	#Get LegacyDN, Tenant, and On-Premise Email addresses for the users
	foreach($user in $MailBoxList)
	{
		$UserInfo = New-Object System.Object
	
		$CloudEmailAddress = $user.EmailAddresses | ?{($_ -match 'onmicrosoft') -and ($_ -cmatch 'smtp:')}	
		if ($CloudEmailAddress.Count -gt 1)
		{
			$CloudEmailAddress = $CloudEmailAddress[0].ToString().ToLower().Replace('smtp:', '')
			Write-Host "$user returned more than one cloud email address.  Using $CloudEmailAddress" -ForegroundColor Yellow
		}
		else
		{
			$CloudEmailAddress = $CloudEmailAddress.ToString().ToLower().Replace('smtp:', '')
		}
			
		$UserInfo | Add-Member -Type NoteProperty -Name LegacyExchangeDN -Value $user.LegacyExchangeDN	
		$UserInfo | Add-Member -Type NoteProperty -Name CloudEmailAddress -Value $CloudEmailAddress
		$UserInfo | Add-Member -Type NoteProperty -Name OnPremiseEmailAddress -Value $user.PrimarySMTPAddress.ToString()
		$UserInfo | Add-Member -Type NoteProperty -Name MailboxGUID -Value $user.ExchangeGUID	

		$Users += $UserInfo
	}

	#Check for existing csv file and overwrite if needed
	if(Test-Path ".\cloud.csv")
	{
		$delete = Read-Host "The file cloud.csv already exists in the current directory.  Do you want to delete it?  Enter y to delete, anything else to exit this script."
		if($delete.ToString().ToLower() -eq 'y')
		{
			Write-Host "Deleting existing cloud.csv file" -ForeGroundColor Red
			Remove-Item ".\cloud.csv"
		}
		else
		{
			Write-Host "Will NOT delete current cloud.csv file.  Exiting script." -ForeGroundColor Green
			Exit
		}
	}
	$Users | Export-CSV -Path ".\cloud.csv" -notype
	(Get-Content ".\cloud.csv") | %{$_ -replace '"', ''} | Set-Content ".\cloud.csv" -Encoding Unicode
	Write-Host "CSV File Successfully Exported to cloud.csv" -ForeGroundColor Green

}

O365Logon
Main

Second Script: Exchange2007MBtoMEU.ps1

param($DomainController = [String]::Empty)

function Main
{
	#Script Logic flow
	#1. Pull User Info from cloud.csv file in the current directory
	#2. Lookup AD Info (DN, mail, proxyAddresses, and legacyExchangeDN) using the SMTP address from the CSV file
	#3. Save existing proxyAddresses
	#4. Add existing legacyExchangeDN's to proxyAddresses
	#5. Delete Mailbox
	#6. Mail-Enable the user using the cloud email address as the targetAddress
	#7. Disable RUS processing
	#8. Add proxyAddresses and mail attribute back to the object
	#9. Add msExchMailboxGUID from cloud.csv to the user object (for offboarding support)
	
	if($DomainController -eq [String]::Empty)
	{
		Write-Host "You must supply a value for the -DomainController switch" -ForegroundColor Red
		Exit
	}
	
	$CSVInfo = Import-Csv ".\cloud.csv"
	foreach($User in $CSVInfo)
	{
		Write-Host "Processing user" $User.OnPremiseEmailAddress -ForegroundColor Green
		Write-Host "Calling LookupADInformationFromSMTPAddress" -ForegroundColor Green
		$UserInfo = LookupADInformationFromSMTPAddress($User)
		
		#Check existing proxies for On-Premise and Cloud Legacy DN's as x500 proxies.  If not present add them.
		$CloudLegacyDNPresent = $false
		$LegacyDNPresent = $false
		foreach($Proxy in $UserInfo.ProxyAddresses)
		{
			if(("x500:$UserInfo.CloudLegacyDN") -ieq $Proxy)
			{
				$CloudLegacyDNPresent = $true
			}
			if(("x500:$UserInfo.LegacyDN") -ieq $Proxy)
			{
				$LegacyDNPresent = $true
			}
		}
		if(-not $CloudLegacyDNPresent)
		{
			$X500Proxy = "x500:" + $UserInfo.CloudLegacyDN
			Write-Host "Adding $X500Proxy to EmailAddresses" -ForegroundColor Green
			$UserInfo.ProxyAddresses += $X500Proxy
		}
		if(-not $LegacyDNPresent)
		{
			$X500Proxy = "x500:" + $UserInfo.LegacyDN
			Write-Host "Adding $X500Proxy to EmailAddresses" -ForegroundColor Green
			$UserInfo.ProxyAddresses += $X500Proxy
		}
		
		#Disable Mailbox
		Write-Host "Disabling Mailbox" -ForegroundColor Green
		Disable-Mailbox -Identity $UserInfo.OnPremiseEmailAddress -DomainController $DomainController -Confirm:$false
		
		#Mail Enable
		Write-Host "Enabling Mailbox" -ForegroundColor Green
		Enable-MailUser  -Identity $UserInfo.Identity -ExternalEmailAddress $UserInfo.CloudEmailAddress -DomainController $DomainController
		
		#Disable RUS
		Write-Host "Disabling RUS" -ForegroundColor Green
		Set-MailUser -Identity $UserInfo.Identity -EmailAddressPolicyEnabled $false -DomainController $DomainController
		
		#Add Proxies and Mail
		Write-Host "Adding EmailAddresses and WindowsEmailAddress" -ForegroundColor Green
		Set-MailUser -Identity $UserInfo.Identity -EmailAddresses $UserInfo.ProxyAddresses -WindowsEmailAddress $UserInfo.Mail -DomainController $DomainController
		
		#Set Mailbox GUID.  Need to do this via S.DS as Set-MailUser doesn't expose this property.
		$ADPath = "LDAP://" + $DomainController + "/" + $UserInfo.DistinguishedName
		$ADUser = New-Object -TypeName System.DirectoryServices.DirectoryEntry -ArgumentList $ADPath
		$MailboxGUID = New-Object -TypeName System.Guid -ArgumentList $UserInfo.MailboxGUID
		[Void]$ADUser.psbase.invokeset('msExchMailboxGUID',$MailboxGUID.ToByteArray())
		Write-Host "Setting Mailbox GUID" $UserInfo.MailboxGUID -ForegroundColor Green
		$ADUser.psbase.CommitChanges()
		
		Write-Host "Migration Complete for" $UserInfo.OnPremiseEmailAddress -ForegroundColor Green
		Write-Host ""
		Write-Host ""
	}
}

function LookupADInformationFromSMTPAddress($CSV)
{
	$Mailbox = Get-Mailbox $CSV.OnPremiseEmailAddress -ErrorAction SilentlyContinue
	
	if($Mailbox -eq $null)
	{
		Write-Host "Get-Mailbox failed for" $CSV.OnPremiseEmailAddress -ForegroundColor Red
		continue
	}
	
	$UserInfo = New-Object System.Object
	
	$UserInfo | Add-Member -Type NoteProperty -Name OnPremiseEmailAddress -Value $CSV.OnPremiseEmailAddress
	$UserInfo | Add-Member -Type NoteProperty -Name CloudEmailAddress -Value $CSV.CloudEmailAddress
	$UserInfo | Add-Member -Type NoteProperty -Name CloudLegacyDN -Value $CSV.LegacyExchangeDN
	$UserInfo | Add-Member -Type NoteProperty -Name LegacyDN -Value $Mailbox.LegacyExchangeDN
	$ProxyAddresses = @()
	foreach($Address in $Mailbox.EmailAddresses)
	{
		$ProxyAddresses += $Address
	}
	$UserInfo | Add-Member -Type NoteProperty -Name ProxyAddresses -Value $ProxyAddresses
	$UserInfo | Add-Member -Type NoteProperty -Name Mail -Value $Mailbox.WindowsEmailAddress
	$UserInfo | Add-Member -Type NoteProperty -Name MailboxGUID -Value $CSV.MailboxGUID
	$UserInfo | Add-Member -Type NoteProperty -Name Identity -Value $Mailbox.Identity
	$UserInfo | Add-Member -Type NoteProperty -Name DistinguishedName -Value (Get-User $Mailbox.Identity).DistinguishedName
	
	$UserInfo
}

Main

Running the PowerShell Scripts

An input CSV file is required, typically you’d use the same file as that used to create a Migration Batch.  It’s named migration.csv, and needs to be saved in the same folder.

Example migration.csv file – it must contain the EmailAddress header.

EmailAddress
Steve.Test1@x500.co.uk

First Script

Run the script in the Exchange Management Shell:

.\ExportO365UserInfo.ps1

You’ll be prompted to enter Exchange Online credentials (or re-use an existing session if one is already established).

The script will create an output file named cloud.csv, to be used with the next script.

Example of cloud.csv:

LegacyExchangeDN,CloudEmailAddress,OnPremiseEmailAddress,MailboxGUID
/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=4b260b5dbfb645df8981aaebd9a3e3b0-Steve Test1,Steve.Test1@x500.onmicrosoft.com,Steve.Test1@x500.co.uk,
310b4e92-e437-4453-8f86-fd9dd23f5873

Second Script

Run the script in the Exchange Management Shell:

.\Exchange2007MBtoMEU.ps1 <FQDN of Domain Controller>, for example:
.\Exchange2007MBtoMEU.ps1 x500dc01v.x500.local

Output:

Processing user Steve.Test1@x500.co.uk
Calling LookupADInformationFromSMTPAddress
Adding x500:/o=ExchangeLabs/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/c n=Recipients/cn=4b260b5dbfb645df8981aaebd9a3e3b0-Steve Test1 to EmailAddresses Adding x500:/o=x500 Org:/ou=Exchange Administrative Group (FYDIBOHF23SPDLT )/cn=Recipients/cn=SteveTest1 to EmailAddresses
Disabling Mailbox
Enabling Mailbox

Name RecipientType
—- ————-
Steve Test1 MailUser
Disabling RUS
Adding EmailAddresses and WindowsEmailAddress
Setting Mailbox GUID 310b4e92-e437-4453-8f86-fd9dd23f5873
Migration Complete for Steve.Test1@x500.co.uk

Verification

Verify MEUs have been created successfully.  It is critical that the following values remain and are correct after MEU conversion:

  • legacyExchangeDN

  • mail
  • msExchMailboxGuid
  • proxyAddresses
  • targetAddress
Advertisements

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s