Hybrid Migration

In most of my migrations of Exchange on-premise to Office 365 (Exchange Online), also know as a “Hybrid Migration,” I use a plethora of scripts for different parts of the process. In any hybrid migration there is usually an Active Directory sync to O365 that is happening. So on-premises Active Directory also needs attention. As smooth as MS has made the process there are always hiccups and problems not foreseen by less experienced migration engineers. I developed this script to deal with most of those situations.

Technical Details

This PowerShell script presents a menu with various migration procedures and performs different actions based on the user’s selection. You will need to be connected to your tenant with PowerShell before you run this. Here’s a detailed breakdown of what the script does:

There are several variables that need to be updated in the beginning of the script. These are clearly defined.

Also please rename the file name extension to .ps1

Based on the user’s input, the script executes different actions using a switch statement:

  1. Connects to Exchange Online using the specified user principal name.
  2. Reads a list of user accounts from a text file, performs various operations (e.g., modifying UPN, adding proxy addresses), and logs the results to files.
  3. Performs same operations as in option ‘2’, but for a single account entered at a prompt.
  4. Reads a list of user account names from a text file and initiates move requests for each user, specifying the completion time and other settings.
  5. Initiates a move request for a single user specified by the user, with the specified completion time and other settings.
  6. Reads a list of user account names from a text file and updates their User Principal Name (UPN).
  7. Updates the UPN of a single user specified by the user.
  8. Retrieves and displays the license information for a single user account entered at a prompt.
  9. Retrieves and displays the license information for each user specified in the text file (bulk action).
  1.    Retrieves and displays the migration status for each user specified in the text file.
  2.    Retrieves and displays the migration status for a single user specified by the user.
  3.  The script exits.

After each action, the script pauses to allow the user to review the output before displaying the menu again.

The loop continues until the user selects ‘q’ to quit.

In summary, this script provides a menu-driven interface for performing various migration procedures related to Exchange Online, including user account management, migration status checks, and license checks. Here is a screenshot of the menu.

Migration Script

The List Format

The script uses tex file list to run bulk operations. The lists should be formatted as such:

The list containing users should be formatted as: (using user ID’s pulled from AD)
Ctest1
Ctest2
User1
Stest3

Save it as a CR/LF text file.

Get the Script

$day = Get-Date -Format "MM/dd/yyyy"
$date = $day + " 5:00 AM"
#The myacct varible is your O365 admin account
$myacct = "[email protected]"
#Your O365 MX
$domn = "@customer.mail.onmicrosoft.com"
#Your Domain
$ydomain = "@customer.com"
#Output file location and name
$outf = "c:\temp\Migration_Output.txt"
#User or account list location and name
$ulfile = "c:\temp\Account_List.txt"
function Show-Menu
{
param ([string]$Title = 'My Menu')
Clear-Host

Write-Host "===================== Migration Procedures ==========================="
Write-Host "ǁ ǁ"
Write-Host "ǁ 1: Press '1' To connect to Exchange Online. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 2: Press '2' for Bulk completion of migrated mailboxes in AD. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 3: Press '3' for Single completion of migrated mailboxes in AD. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 4: Press '4' for Bulk completion synced mailboxes. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 5: Press '5' for Single completion of synced mailboxes. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 6: Press '6' for Bulk account update of UPN. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 7: Press '7' for Single account update of UPN. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 8: Press '8' for Single account license check. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 9: Press '9' for Bulk account license check. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ 0: Press '0' for Bulk migration status check. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ Z: Press 'Z' for Single migration status check. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ Q: Press 'Q' to quit. ǁ"
Write-Host "ǁ ǁ"
Write-Host "ǁ **Bulk update will use the txt file in the TEMP directory ǁ"
Write-Host "ǁ **The TXT file should be named Account_List ǁ"
Write-Host "ǁ **The logging file is Migration_Output.txt ǁ"
Write-Host "======================================================================"
Write-host "`n"
}
DO
{
Show-Menu –Title 'My Menu'

$input = Read-Host "Please make a selection"

switch ($input)
{
'1' {Connect-ExchangeOnline -UserPrincipalName $myacct -ShowProgress $true
Connect-IPPSSession -UserPrincipalName $myacct
Connect-MsolService}

'2' {$User = Get-Content $ulfile
$smtp = "smtp:"
ForEach ($UItem in $User){
$proxy = $smtp + $UItem + $domn
$target = $UItem + $domn
$UPN = $UItem + "@customer.com"
Set-ADUser -UserPrincipalName $UPN -Identity $UItem
Set-aduser -Identity $UItem -add @{proxyaddresses = $proxy}
Set-ADuser -Identity $UItem -replace @{targetAddress= $target}
Get-ADUser -Identity $UItem -properties proxyaddresses | select -expandproperty proxyaddresses | Out-file -FilePath $outf -Append
Get-aduser $UItem -pr targetaddress |fl targetaddress | Out-file -FilePath $outf -Append
Get-aduser $UItem -pr UserPrincipalName |fl UserPrincipalName | Out-file -FilePath $outf -Append
}}

'3' {$user = Read-Host -Prompt 'User Name'
$smtp = "smtp:"
$proxy = $smtp + $user + $domn
$target = $user + $domn
$UPN = $user + $ydomain
#Add-ADGroupMember -Identity O365_E3_License -Member $user
Set-ADUser -UserPrincipalName $UPN -Identity $user
Set-aduser -Identity $user -add @{proxyaddresses = $proxy}
Set-ADuser -Identity $user -replace @{targetAddress=$target}
Get-ADUser -Identity $user -properties proxyaddresses | select -expandproperty proxyaddresses | Out-file -FilePath $outf -Append
Get-aduser $user -pr targetaddress |fl targetaddress | Out-file -FilePath $outf -Append
Get-aduser $user -pr UserPrincipalName |fl UserPrincipalName | Out-file -FilePath $outf -Append}

'4' {$User = Get-Content $ulfile
$day = Get-Date -Format "MM/dd/yyyy"
$date = $day + " 5:00 AM"
ForEach ($UItem in $User){
Set-MoveRequest -Identity $Uitem -CompleteAfter (Get-Date $date).ToUniversalTime() -BadItemLimit unlimited -AcceptLargeDataLoss -Confirm:$False}}

'5' {$user = Read-Host -Prompt 'User Name'
$day = Get-Date -Format "MM/dd/yyyy"
$date = $day + " 5:00 AM"
Set-MoveRequest -Identity $user -CompleteAfter (Get-Date $date).ToUniversalTime() -BadItemLimit unlimited -AcceptLargeDataLoss -Confirm:$False -SkippedItemApprovalTime $(Get-Date).ToUniversalTime()}

'6' {$User = Get-Content $ulfile
ForEach ($UItem in $User){
$UPN = $UItem + $ydomain
Set-ADUser -UserPrincipalName $UPN -Identity $UItem
}}

'7' {$user = Read-Host -Prompt 'User Name'
$UPN = $user + $ydomain
Set-ADUser -UserPrincipalName $UPN -Identity $UItem}

'8' {$user = Read-Host -Prompt 'User Name'
$UPN = $user + $ydomain
Get-MsolUser -UserPrincipalName $UPN | Format-List DisplayName,Licenses}

'9' {$User = Get-Content $ulfile
ForEach ($UItem in $User){
$UPN = $Uitem + $ydomain
Get-MsolUser -UserPrincipalName $UPN | Format-List DisplayName,Licenses
}}

'0' {$User = Get-Content $ulfile
ForEach ($UItem in $User){
Get-MoveRequest $UItem | Format-List Name,status
}}

'Z' {$user = Read-Host -Prompt 'User Name'
Get-MoveRequest -Identity $user | Format-List Name,status}

'q' {EXIT}
}

pause
}
until ($input -eq 'q')