Tag Archives: hybrid migration

Office 365: Monitor and finish remote mailbox moves

A while ago, I posted a script that helps you out informing end users and starting remote mailbox moves in hybrid migration scenarios.
In the script we started our mailbox moves with the -SuspendWhenReadyToComplete switch. That switch means we manually have to go in and resume the mailbox moves with the Resume-MoveRequest cmdlet.

In this follow up script, we automate that process by monitoring active mailbox moves and handle resuming and removing of them. We also have the possibility to send out emails to our end users when the move has been completed (if uncommenting row 78).

Note: The script only works as intended if you have created one moverequest per user, as done in my script that start the migrations.

<#  
.SYNOPSIS 
    Script that monitors remote mailbox moves in Exchange Online and handles resuming, removing of them. 
.NOTES 
    Author: Johan Dahlbom 
    Blog: 365lab.net 
    Email: johan[at]dahlbom.eu 
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.     
#>

#region variables
$CloudUsername = "migration@365lab.net"
$CloudPassword = ConvertTo-SecureString "password" -AsPlainText -Force
$CloudCred = New-Object System.Management.Automation.PSCredential $CloudUsername, $CloudPassword
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
$Logfile = ($PSScriptRoot + "\HybridMigrations.log")
$enduserinfo = @"
Hi $($MoveRequest.batchname),<p>

You've just been moved to Office 365, if you have questions regarding the move, or if anything is not working as intended, let us know!
<p>
<strong>Best Regards
IT Department
</strong>
"@
#endregion variables

#region functions
#Function to send out email with defined parameters
function Send-MigrationMail {
param(
    [Parameter(Mandatory=$true)]
    [string]$Message,
    [Parameter(Mandatory=$true)]
    [string]$Recipient
)
$emailFrom = "Office 365 <migration@365lab.net>"
$smtpserver = "mailserver.365lab.net"
$subject = "Migration batch Finished"
$cc = "migrationadmin@365lab.net"
 
Send-MailMessage -From $emailfrom -To $Recipient -Cc $cc -SmtpServer $smtpserver -Subject $subject -Body $Message -BodyAsHtml
 
}
#Function to write information to a log file and to console output
function Write-Log {
Param (
    [Parameter(Mandatory=$true)]
    [string]$Logstring
)
Add-Content $Logfile -value $logstring -ErrorAction Stop
Write-Output $logstring
}
#endregion functions

#Connect to Exchange Online
if (!(Get-Command Get-CloudMoveRequest -ErrorAction SilentlyContinue)) {
    $session = New-PSSession -ConfigurationName Microsoft.Exchange -Authentication Basic -ConnectionUri https://ps.outlook.com/powershell -AllowRedirection:$true -Credential $CloudCred
    Import-PSSession $session -Prefix Cloud -CommandName @("Get-MoveRequest","Resume-MoveRequest","Remove-MoveRequest","Get-MoveRequestStatistics") -AllowClobber
}

#Run the following as long as there are active move requests
while (Get-CloudMoveRequest)
{
	$MoveRequests = Get-CloudMoveRequest
	foreach ($MoveRequest in $MoveRequests) {
		switch ($MoveRequest.status) { 
			"InProgress" 	{ 
				Write-Output "INFO: Move request for $($MoveRequest.Batchname) at $((Get-CloudMoveRequestStatistics -Identity $MoveRequest.BatchName).PercentComplete)% $(Get-date -format u)" 
			}
			"AutoSuspended" {
				Write-Log "INFO: Completing mailbox move for $($MoveRequest.BatchName) at $(get-date -Format u)"
				Resume-CloudMoveRequest -identity $moverequest.batchname
			}
			"Completed" 	{
				Write-Log "INFO: Removing completed MoveRequest $($MoveRequest.Batchname) at $(get-date -Format u)"
                #Uncomment row below to send information to end users when migration has been completed.
                #Send-MigrationMail -Message $enduserinfo -Recipient $moverequest.batchname 
				Remove-CloudMoveRequest -identity $moverequest.batchname -confirm:$false 
			}
		}
	}
	
	Write-Host "------------------------------------------------------------------------------"
	Start-Sleep 60
}

 Write-Log "INFO: All migration batches finished at $(Get-Date -format u)"
 #Send information to admin then the batches are complete.
 Send-MigrationMail -Message "Migration batch finished at $(Get-Date -format u)<br>Logfile attached." -Recipient migrator@365lab.net
 

Hope this is at any help, just let me know if you have suggestions that can improve the script.

/Johan

Office 365: Start hybrid migrations and inform users

Lately I’ve been doing quite a few Hybrid setups.
One of the things that cause quite a lot of pain when doing migrations in general is getting information out to your users.
Even if you have a good communication plan, only 20% of your users read the information and only 5% of them reflect and understand it. 🙂

To make this a bit easier, I’ve created a small script that starts Hybrid migration batches and sends out information to the end user.
This is of course just an example, but gives you a good idea of what you are able to do with quite simple means.

HybridMigrationEmail

Getting started:

  • Review all relevant variables in the script
  • Change the text in BodyHybrid.txt to fit in to your needs, the html in that file will be the information sent in the email. The words ActiveSyncDevices,OWAAddress,EmailAddress and Firstname will automatically be replaced by its corresponding variables. (and no the css is not the best 🙂 )
  • Create the PDF Office365Info.pdf and put it in the same folder as the script, that file will be attached to the email.
  • Create input file Prepare-Mailboxes.csv with samAccountNames of the users that you want to migrate. (Note that I am assuming that the users Email Address and UserPrincipalName are the same in the script)
    Also note that the MoveRequest settings in the script might not fit your needs (Baditemlimit, Largeitemlimit).

As we are using the switch -SuspendWhenReadytoComplete in the script, the batches will autosuspend at 95% completion.
That means you’ll have to complete them manually with for example the following line:

Get-CloudMoveRequest | Where-Object {$_.Status -eq "AutoSuspended"} | Resume-CloudMoveRequest. 

Another good option to get in better control of your remote mailbox moves is to use Michael Halls excellent tool based on PowerShell and Excel that does that for you.

Start-HybridMigrations.ps1

<#
.SYNOPSIS
    Starts hybrid migrations based on input (samAccountName) from a csv file.
.DESCRIPTION
    The script starts hybrid migrations and sends out information emails based on the file "BodyHybrid.txt".
    PowerShell V3.0 required
.NOTES
    File Name: Start-HybridMigrations.ps1
    Author   : Johan Dahlbom, johan[at]dahlbom.eu
    The script are provided “AS IS” with no guarantees, no warranties, and they confer no rights.
#>

#Import Required Modules
Import-Module ActiveDirectory

#Define Mail Information and Logging
$Users = Get-Content Prepare-MailboxMoves.csv
$BodyHybrid = Get-Content BodyHybrid.txt -Raw
$OWAAddress = 'http://outlook.com/365lab.net'
$Attachment = Get-Childitem .\Office365Info.pdf
$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition
$Logfile = ($PSScriptRoot + "\HybridMigrations.log")

#Define Exchange Online Variables
$ExchServ = "exchangecas.corp.365lab.net"
$HybridServer = "webmail.365lab.net"
$DeliveryDomain = "365lab.mail.onmicrosoft.com"
$OnPremiseUsername = "cloud\svc_mailmigration"
$OnPremisePassword = ConvertTo-SecureString "password" -AsPlainText -Force
$CloudUsername = "svc-cloudmigration@365lab.onmicrosoft.com"
$CloudPassword = ConvertTo-SecureString "password" -AsPlainText -Force
$CloudCred = New-Object System.Management.Automation.PSCredential $CloudUsername, $CloudPassword
$OnPremCred = New-Object System.Management.Automation.PSCredential $OnPremiseUsername, $OnPremisePassword

#Connect to OnPremise Exchange
    if (!(Get-Command Get-ActiveSyncDeviceStatistics -ErrorAction SilentlyContinue)) {
    try {
        Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010
    } catch {
            $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "http://$ExchServ/powershell" -Authentication Kerberos
            Import-PSSession -Session $Session -CommandName  Get-ActiveSyncDeviceStatistics -FormatTypeName * | Out-Null
        }
    }
#Connect to Exchange Online
    if (!(Get-Command New-CloudMoveRequest -ErrorAction SilentlyContinue))
    {
	    $session = New-PSSession -ConfigurationName Microsoft.Exchange -Authentication Basic -ConnectionUri https://ps.outlook.com/powershell -AllowRedirection:$true -Credential $CloudCred
	    Import-PSSession $session -Prefix Cloud
    }

function LogWrite {
Param ([string]$Logstring)
Add-Content $Logfile -value $logstring -ErrorAction Stop
Write-Host $logstring
}

function Send-MigrationMail {
param(
[Parameter(Mandatory=$true)]
[string]$Message,
[Parameter(Mandatory=$true)]
[string]$Recipient
)
    $emailFrom = "Office 365 Migration <office365@365lab.net>"
    $smtpserver = "smtpyrelay.365lab.net"
    $subject = "Email Migration Started"
    $cc = "migrator@365lab.net"

    Send-MailMessage -From $emailfrom -To $Recipient -Cc $cc -SmtpServer $smtpserver -Subject $subject -Body $Message -BodyAsHtml -Attachments $Attachment

}
LogWrite "$(get-date -Format u)"
foreach($aduser in $users) {
	$emailAddress = (Get-ADuser $aduser -properties UserPrincipalName).UserPrincipalName
	$firstname = (Get-ADuser $aduser).givenname
        #Find the number of Active ActiveSyncDevices that the user have in the local Exchange solution. If not, set to 0.
		$ActiveSyncDevices = (Get-ActiveSyncDeviceStatistics -Mailbox $aduser | Where-Object {$_.LastSuccessSync -gt (Get-Date).AddDays(-30)}).count
        if (!($ActiveSyncDevices)) {
            $ActiveSyncDevices = "0"
        }
	    #Start Email Migration for user
        try {
            New-CloudMoveRequest -Identity $emailaddress -Remote -RemoteHostName $HybridServer -RemoteCredential $OnPremCred -TargetDeliveryDomain $DeliveryDomain -BatchName $emailAddress -SuspendWhenReadyToComplete -BadItemLimit 50 -LargeItemLimit 50
	} catch {
            $err = $_
            Logwrite "ERROR: Failed to start migration on $($emailAddress)`r`n$($err)"
        }

        #Verify that the move request was successful and send information email
        if ((Get-CloudMoveRequest | Where-Object {$_.BatchName -eq $emailAddress} -ErrorAction SilentlyContinue)) {
            LogWrite "INFO: Started migration of user $($emailAddress) $(get-date -format u)"
            Send-MigrationMail -Recipient $emailAddress -Message $BodyHybrid.Replace('firstname',"$firstname").Replace('ActiveSyncDevices',"$ActiveSyncDevices").Replace("emailaddress","$emailaddress").Replace("OWAAddress","$OWAAddress")
        }else{
            LogWrite "ERROR: Could not start migration for user $($emailAddress), please verify that user exists"
        }
}

The script would of course be possible to extend with lots of things like license assignment, watching the mailboxes as they move and lots and lots of other things, but that’s for another day.
Just let me know if you have suggestions on improvements or other things that should be changed.
Download the script complete with the HTML-template and example csv file from here.

Happy migrations!

/Johan