Move Multiple Mailboxes to Exchange Online via Powershell

Issue:

We have Office 365 hybrid mode for Exchange Online.  I quickly grew tired of creating a single new account, migrating the mailbox with the online wizard, and then clicking through the user licensing.  I wanted a single powershell script that would move an existing mailbox, move a group of existing users, move a new user, or migrate a group of new users.  This script will need to move the mailbox to Exchange Online, license the user, provision the mailbox, and provision OneDrive.  We have two different domains; one domain for our local on prem and another domain for our Office 365 Exchange Online. Once the email is migrated our email address policy automatically changes the primary SMTP address in Active Directory. I want to run the script and not worry about waiting for migration jobs to finish or extra steps after migration is complete.

Prerequisites:

 

Solution:

#Migrate Mailboxes to Office 365 for new or existing users
#This script also has an option to license the user and not move the mailbox
#Install-Module -Name AzureAD #install of AzureAD 2.0
Import-Module AzureAD
Import-Module ADSync
Import-Module Microsoft.Online.SharePoint.PowerShell -DisableNameChecking

#Fix Powershell ISE and powershell to be able to run .net 2.0 and .net 4.0 code and clr version for powershell 
# or http://stackoverflow.com/questions/2094694/how-can-i-run-powershell-with-the-net-4-runtime
#Azure AD Module PowerShell Command "Install-Module -Name AzureAD"
#.NET 4.5.2 or newer
#SharePoint Online SDK? install https://www.microsoft.com/en-us/download/details.aspx?id=42038
#SharePoint Online Management Shell https://www.microsoft.com/en-us/download/details.aspx?id=35588
#Microsoft Online Services Sign-In Assistant https://www.microsoft.com/en-us/download/details.aspx?id=28177
#Windows Management Framework 5.0 https://www.microsoft.com/en-us/download/details.aspx?id=50395
#Make sure one drive for admin account already has onedrive provisioned and licensed for onedrive
#Tile -> Admin -> Admin -> SharePoint -> Settings ->
#SharePoint Online Management Shell https://technet.microsoft.com/en-us/library/fp161372.aspx
#Turn Scripting capabilities on in Office365 https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
 
#License Information: https://technet.microsoft.com/en-us/library/dn771773.aspx
 

#Variables

#Mailboxes to Move
$usernames = @('user1','user2','user3')

#Hybrid Mode Setup with old alias going to on prem exchange server, and new alias going to Office 365
$oldAliasDomain = '@localOnPremDomain'
$newAliasDomain = '@ExchangeOnlineDomain'

#Default alias sometimes given automatically to new accounts in Office 365 ****.onmicrosoft.com
$badAliasDomain = '@****.onmicrosoft.com'

#Education User
$addClassroomLic = $True

#New users just created and sync has not occurred for the new users yet
$newUsers = $False
#Move Mailbox if not it has already been moved then only licensing and provisioning is needed
$moveMailbox = $True

$targetDeliveryDomain = "****.mail.onmicrosoft.com"
$externalHybridServerName = 'ExternalFQDNofHybridServer'



$logfile = 'c:\temp\MoveMultipleStaffToExchangeOnline.txt'

Set-Content $logfile ""

#Sharepoint Site URL
$webUrl = "https://tenantId-admin.sharepoint.com";

$office365Cred = Get-Credential -Message "Office 365 Credential" -UserName "Office365Admin@contoso.com"

$hybridServerCred = Get-Credential -Message "Hybrid Server Local Credential" -UserName "LocalOnPremAdministrator@domain.com"

#End Variables



#Initialize Variables for larger scope (error catching)
$newaliases = @()
$userstatus = @{}
$oldalias = ''
$newalias = ''
$username1 = ''
$batchName = ''
$status = ''

#Start Differential Sync before attempting to move to Exchange Online so that the new users are synched
if ($newUsers)
{
    Start-ADSyncSyncCycle -PolicyType Delta
    if ($usernames.Count -gt 20)
    {
        Start-Sleep -Seconds 1200
    }
    elseif ($usernames.Count -gt 10 -and $usernames.Count -lt 20)
    {
        Start-Sleep -Seconds 600
    }
    else
    {
        Start-Sleep -Seconds 300
    }
}


try
{
    #Move Each Mailbox

    #Connect to Office 365 Exchange
    $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri  -Credential $office365Cred -Authentication Basic -AllowRedirection
    Import-PSSession $Session

    if ($moveMailbox)
    {
        For ($i=0; $i -lt $usernames.Count; $i++)
        {
            #Get the mailbox moves started
            try
            {
                $username1 = $usernames[$i]
                $oldalias = "$username1$oldAliasDomain"
                $newalias = "$username1$newAliasDomain"
                $batchName = $usernames[$i]
            

                Write-Host $oldalias
                Write-Host $newalias

                #Migrate the Mailbox from on prem to Office 365 cloud
                $request = New-MoveRequest -Identity $oldalias -BatchName $batchName -Remote -RemoteHostName $externalHybridServerName -TargetDeliveryDomain $targetDeliveryDomain -RemoteCredential $hybridServerCred -BadItemLimit 15 -SuspendWhenReadyToComplete:$false
                #$request | Get-MoveRequestStatistics
            }
            catch
            {
                $ErrorMessage = $_.Exception.Message
                $FailedItem = $_.Exception.ItemName
                WRITE-HOST "Single Mailbox Move Request Failure! $username1 $status $FailedItem $ErrorMessage"
                Add-Content $logfile "Single Mailbox Move Request Failure! $username1 $status $FailedItem $ErrorMessage" 
            }
        }
        #Give the migrations a moment to get started
        Start-Sleep -Seconds 210
    }
    

    For ($i=0; $i -lt $usernames.Count; $i++)
    {
        #Wait each mailbox move to finish and then initialize the language, TimeZone, and Provision the mailbox.
        try
        {
            $username1 = $usernames[$i]
            $oldalias = "$username1$oldAliasDomain"
            $newalias = "$username1$newAliasDomain"
            $batchName = $usernames[$i]
            

            Write-Host $oldalias
            Write-Host $newalias
            
            if ($moveMailbox)
            { 
                $mreq = $null
                #Wait for the move to complete and get the status of the mailbox move
                $iterationcount = 0
                Do
                {
                    Start-Sleep -Seconds 90
                    $mreq = Get-MoveRequest -BatchName $batchName
                    $status = $($mreq.status)
                    $iterationcount++

                }While (($status -eq 'InProgress' -or $status -eq 'Queued' -or $status -eq 'CompletionInProgress') -and $iterationcount -lt 1000)

                #Remove the Move Request
                if ($status -eq 'Completed' -or $status -eq 'CompletedWithWarning')
                {
                    $mreq | Remove-MoveRequest
                }

            }
            else
            {
                $status = 'Completed'
            }
            
            
            #Save the Mailbox Status for later user (to be licensed if successful)

            $userstatus.Add($newalias,$status)
            #Set the Language and TimeZone of the Mailbox
            if ($status -eq 'Completed' -or $status -eq 'CompletedWithWarning')
            {
                #Set language and timezone
                Set-MailboxRegionalConfiguration -identity $newalias –Language en-US -confirm:$False
                Set-MailboxRegionalConfiguration -identity $newalias -TimeZone "Pacific Standard Time" -confirm:$False
                #Provision the mailbox
                Test-MapiConnectivity $newalias
                Write-Host "User Mailbox Migrated to Exchange Online and Provisioned: $newalias"
                Add-Content $logfile "User Mailbox Migrated to Exchange Online and Provisioned: $newalias"
            }
            else
            {
                WRITE-HOST "Mailbox Move Failure! $username1 $status"
                Add-Content $logfile "Mailbox Move Failure! $username1 $status" 
            }
        }
        catch
        {
            $ErrorMessage = $_.Exception.Message
            $FailedItem = $_.Exception.ItemName
            WRITE-HOST "Single Mailbox Move Failure! $username1 $status $FailedItem $ErrorMessage"
            Add-Content $logfile "Single Mailbox Move Failure! $username1 $status $FailedItem $ErrorMessage" 
        }

    }
    Remove-PSSession $Session;
}
catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    WRITE-HOST "Mailbox Move Failure! $username1 $status $FailedItem $ErrorMessage"
    Add-Content $logfile "Mailbox Move Failure! $username1 $status $FailedItem $ErrorMessage" 
}

#Mailboxes are moved Synch to set the new aliases after the move
#Our Email Address Policy with our local on prem exchange automatically changes the primary SMTP email address after it is moved 
if ($moveMailbox)
{
    Start-ADSyncSyncCycle -PolicyType Initial
    Start-Sleep -Seconds 300
}


try
{
    Connect-AzureAD -credential $office365Cred
    #License each user
    #Licensing has to occur after the mailbox move because the exchange license cannot be turned on until it has been moved

    #Setup License Variables
    $sku = Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq "STANDARDWOFFPACK_IW_FACULTY" }
    $license1 = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
    $license1.SkuId = $Sku.SkuId
    #Disable Service Plans
    $license1.DisabledPlans += ($Sku.ServicePlans | Where { $_.ServicePlanName -eq "YAMMER_EDU" }).ServicePlanID
    $license1.DisabledPlans += ($Sku.ServicePlans | Where { $_.ServicePlanName -eq "FLOW_O365_P2" }).ServicePlanID
    #$license1.DisabledPlans += ($Sku.ServicePlans | Where { $_.ServicePlanName -eq "POWERAPPS_O365_P2" }).ServicePlanID
    #$license1.DisabledPlans += ($Sku.ServicePlans | Where { $_.ServicePlanName -eq "MCOSTANDARD" }).ServicePlanID

    $sku2 = Get-AzureADSubscribedSku | Where { $_.SkuPartNumber -eq "CLASSDASH_PREVIEW" }
    $license2 = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicense
    $license2.SkuId = $Sku2.SkuId
    
    For ($i=0; $i -lt $usernames.Count; $i++)
    {
        $username1 = $usernames[$i]
        $oldalias = "$username1$oldAliasDomain"
        $newalias = "$username1$newAliasDomain"
        $potentialalias = "$username1$badAliasDomain"
        
        Write-Host "Licensing: $newalias"
        
        $status = $userstatus[$newalias]
            
        if ($status -eq 'Completed' -or $status -eq 'CompletedWithWarning')
        {
            
            #fix the alias if necessary
            $baduserList = Get-MsolUser -UserPrincipalName $potentialalias
            if ($baduserList)
            {
                Set-AzureADUser -ObjectId $potentialalias -UserPrincipalName $newalias
                Write-Host "Alias fixed $potentialalias to $newalias."
                Add-Content $logfile "Alias fixed $potentialalias to $newalias."
                Start-Sleep -Seconds 90
            }
            
            $gooduser = Get-AzureADUser -ObjectId $newalias
            
            if ($gooduser)
            {
                #License the New User
                Write-Host "Assign License Usage Location: US"
                Set-AzureADUser -ObjectId $newalias -UsageLocation US -ErrorAction Continue
                Start-Sleep -Seconds 30
 
                #License Options to disable 
                #Remove Prexisting Same License if already present
                Write-Host "Remove Licenses if already present"
                #Assign both licenses
 
                Write-Host "Remove Standard Office Pack License"
                $newLicenses = $Null
                $newLicenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
                $newLicenses.RemoveLicenses += $license1;
                $newLicenses.AddLicenses += $Null;
                Set-AzureADUserLicense -ObjectId $newalias -AssignedLicenses $newLicenses -ErrorAction SilentlyContinue
 
                Write-Host "Remove Classroom License"
                $newLicenses = $Null
                $newLicenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
                $newLicenses.RemoveLicenses += $license2;
                $newLicenses.AddLicenses += $Null;
                Set-AzureADUserLicense -ObjectId $newalias -AssignedLicenses $newLicenses -ErrorAction SilentlyContinue

                Write-Host "End Remove of preexisting license"

                Write-Host "Assign the Office 365 Staff License (If error then problem!)"

                #Assign licenses
                $newLicenses = $Null
                $newLicenses = New-Object -TypeName Microsoft.Open.AzureAD.Model.AssignedLicenses
                $newLicenses.RemoveLicenses = $null;
                $newLicenses.AddLicenses += $license1;

                Start-Sleep -Seconds 120
 
                if ($addClassroomLic)
                {
                    #Add Microsoft Classroom License
                    Write-Host "Assign the Microsoft Classroom License"
                    Set-MsolUserLicense -UserPrincipalName $newalias -AddLicenses "hesd:CLASSDASH_PREVIEW" -ErrorAction Continue

                    $newLicenses.AddLicenses += $license2;

                    Add-Content $logfile "Assigned the Microsoft Classroom License: $newalias"
                 }
                 Set-AzureADUserLicense -ObjectId $newalias -AssignedLicenses $newLicenses

                 Write-Host "Finished Assigning License if no errors above"
                 #Mailbox Moved and Licensed then OneDrive can be provisioned
                 $newaliases += $newalias
 
                 Write-Host "User Licensed Successfully if no errors: $newalias"
                 Add-Content $logfile "User Licensed Successfully if no errors: $newalias"
            }
            else
            {
                Write-Host "User UPN for user: $username1 is different than expected.  Review, fix the username or UPN in Office 365 or AD, and License manually in Office 365."
                Add-Content $logfile "User UPN for user: $username1 is different than expected.  Review, fix the username or UPN in Office 365 or AD, and License manually in Office 365."
            }
        
        }
        else
        {
            WRITE-HOST "License Failure! $username1 $status"
            Add-Content $logfile "License Failure! $username1 $status" 
        }
        
    }
}
catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    WRITE-HOST "License Failure! $username1 $FailedItem $ErrorMessage"
    Add-Content $logfile "License Failure! $username1 $FailedItem $ErrorMessage" 
}
finally
{
    Disconnect-AzureAD
}

#Wait for everything to initialize before moving on to OneDrive provisioning
Start-Sleep -Seconds 300
try
{
    #Provision OneDrive for the all of the new Users
    Connect-SPOService -Url $webUrl -Credential $office365Cred
    Request-SPOPersonalSite -UserEmails $newaliases
    Disconnect-SPOService
    Write-Host "If mailboxes licensed and moved successfully then OneDrive Provisioned Successfully as well."
    Add-Content $logfile "If mailboxes licensed and moved successfully then OneDrive Provisioned Successfully as well."
}
catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    WRITE-HOST "OneDrive Provision Failure! $FailedItem $ErrorMessage"
    Add-Content $logfile "OneDrive Provision Failure! $FailedItem $ErrorMessage" 

}

 

Resources:
http://www.questiondriven.com/2016/01/19/office-365-license-user-and-provision-onedrive-via-powershell-scheduled-task/

Leave a Reply

%d bloggers like this: