EWS & PowerShell

A bit of stuff using the EWS managed API, which can be downloaded here:
http://www.microsoft.com/en-us/download/details.aspx?id=35371

Note: Make sure the path to the EWS dll matches the install location of the EWS managed API. Change it if necessary.

This script searches folders in mailboxes under the Contacts folder, looking for 'Skype for Business contacts' and then deletes it. It is also doing a disable-ummailbox. As both actions are quite destructive, there's a warning for the person running the script.

The list of mailboxes to run this against was gathered with a get-aduser searchbase of specific OUs, extracting the Primary SMTP address and outputting that to a text file.


$inputfile = "mailboxlist.txt"

$MailboxList = get-content $inputfile
$MailboxListcount = $MailboxList.count

$CurrentDate = (Get-Date).ToString('MMM-dd-yyyy')

[string]$logfilepath = "SkypeForBus-DeletionsLog_" + $CurrentDate + ".csv"

Write-host -backgroundcolor yellow -foregroundcolor red "WARNING! This script will perform the following:"`n
write-host "Using the file $inputfile containing $MailboxListcount users"`n
write-host "Action 1: Disable-UMMailbox -Identity `$MailboxName -KeepProperties:`$True -Confirm:`$false"
write-host "Action 2: Delete the Skype for Business contacts folder (if found) under the Contacts folder"`n

$verify = Read-Host "If you wish to proceed, type Y"

if($verify -eq "Y"){

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
    [void][Reflection.Assembly]::LoadFile($dllpath)
    $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)

$FoundAmount = 0
$log = @()
$T1 = "Processing "
$T2 = " ("
$T3 = " of "
$T4 = ")"
$counter = 0
$MailboxListCount = $MailboxList.count

$StartTime = Get-Date

Foreach ($MailboxName in $MailboxList)
{
$counter++

$Activity = $T1+$MailboxName+$T2+$counter+$T3+$MailboxListCount+$T4

$percent = (($counter/$MailboxListCount) * 100)

$percentstatus = [String]([System.Math]::Round($percent, 0)) + "`% complete"

Write-Progress -id 1 -PercentComplete $percent -Activity $activity -Status $percentstatus

$FoundFolder = $FALSE
$i = New-Object -TypeName PSObject
$i | Add-Member NoteProperty Email $MailboxName
if(Get-UMMailbox $MailboxName){$UMdisabled = "True"
    Disable-UMMailbox -Identity $MailboxName -KeepProperties:$True -Confirm:$false
$i | Add-Member NoteProperty UMdisabled "True"
}
else{
$i | Add-Member NoteProperty UMdisabled "NA"
}

#    $Service.AutodiscoverUrl($MailboxName,{$true})
    $service.Url= new-object Uri("https://outlook.uk2.practice.linklaters.net/EWS/Exchange.asmx")
    $RootFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Contacts,$MailboxName)
    $RootFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service,$RootFolderID)
    $FolderView = New-Object Microsoft.Exchange.WebServices.Data.FolderView(100,0)
    $FolderView.Traversal = [Microsoft.Exchange.WebServices.Data.FolderTraversal]::Shallow
    $Response = $RootFolder.FindFolders($FolderView)
    

    ForEach ($Folder in $Response.Folders){
            if($folder.DisplayName -eq "Skype for Business contacts"){
        ++$FoundAmount
$FoundFolder = $True
#$folder.delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete)
     #$folder.Empty([Microsoft.Exchange.WebServices.Data.DeleteMode]::SoftDelete, $true)
$i | Add-Member NoteProperty Folder $folder.DisplayName
$i | Add-Member NoteProperty Action "Deleted"
}
    }
    if($FoundFolder -eq $false){
$i | Add-Member NoteProperty Folder "NotFound"
$i | Add-Member NoteProperty Action "None"
        }
$log += $i
}

$log | export-csv $logfilepath -notypeinformation -append

$StopTime = Get-Date
$TimeDiff = New-TimeSpan -Start $StartTime -End $StopTime
Write-Host "Deleted"$FoundAmount "folders in"$TimeDiff " and saved log to: "$logfilepath
}

Exchange Database statistics

DB sizes with whitespace:

Get-MailboxDatabase -Status | sort name | select name,@{Name='DB Size (Gb)';Expression={$_.DatabaseSize.ToGb()}},@{Name='Available New Mbx Space Gb)';Expression={$_.AvailableNewMailboxSpace.ToGb()}} | ft -auto


Database usage stats:


$dbs = Get-mailboxdatabase | select Name,Server | sort Name
$collcount = $dbs.count

$T1 = "Processing "
$T2 = " ("
$T3 = " of "
$T4 = ")"

$counter = 0

$results=@()
foreach($db in $databases){
$counter++

$Activity = $T1+$T2+$counter+$T3+$collcount+$T4

$percent = (($counter/$userscount) * 100)

$percentstatus = [String]([System.Math]::Round($percent, 0)) + "`% complete"

Write-Progress -PercentComplete $percent -Activity $activity -Status $percentstatus

$SUS = Get-StoreUsageStatistics -Database $db.name

$i = New-Object -TypeName PSObject
$i | Add-Member NoteProperty Database $db.name
$i | Add-Member NoteProperty Server $db.server
$i | Add-Member NoteProperty DisplayName $SUS.DisplayName
$i | Add-Member NoteProperty DigestCategory $SUS.DigestCategory
$i | Add-Member NoteProperty TimeInServer $SUS.TimeInServer
$i | Add-Member NoteProperty TimeInServer $SUS.TimeInCPU
$i | Add-Member NoteProperty SampleId $SUS.SampleId
$results += $i
}
$results | sort Server, TimeInServer -descending, Database | export-csv database-statistics.csv -notypeinformation

Functions 1

############## function to add append to export-csv #########################

function Export-CSV {
[CmdletBinding(DefaultParameterSetName='Delimiter',
  SupportsShouldProcess=$true, ConfirmImpact='Medium')]
param(
 [Parameter(Mandatory=$true, ValueFromPipeline=$true,
           ValueFromPipelineByPropertyName=$true)]
 [System.Management.Automation.PSObject]
 ${InputObject},

 [Parameter(Mandatory=$true, Position=0)]

 [Alias('PSPath')]
 [System.String]
 ${Path},

 [Switch]

 ${Append},

 [Switch]

 ${Force},

 [Switch]

 ${NoClobber},

 [ValidateSet('Unicode','UTF7','UTF8','ASCII','UTF32',

                  'BigEndianUnicode','Default','OEM')]
 [System.String]
 ${Encoding},

 [Parameter(ParameterSetName='Delimiter', Position=1)]

 [ValidateNotNull()]
 [System.Char]
 ${Delimiter},

 [Parameter(ParameterSetName='UseCulture')]

 [Switch]
 ${UseCulture},

 [Alias('NTI')]

 [Switch]
 ${NoTypeInformation})

begin

{
 $AppendMode = $false

 try {

  $outBuffer = $null
  if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
  {
      $PSBoundParameters['OutBuffer'] = 1
  }
  $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Export-Csv',
    [System.Management.Automation.CommandTypes]::Cmdlet)
        
 $scriptCmdPipeline = ''

if ($Append) {

  
  $PSBoundParameters.Remove('Append') | Out-Null
    
  if ($Path) {
   if (Test-Path $Path) {        
    # Need to construct new command line
    $AppendMode = $true
    
    if ($Encoding.Length -eq 0) {
     $Encoding = 'ASCII'
    }
    
    $scriptCmdPipeline += 'ConvertTo-Csv -NoTypeInformation '

    if ( $UseCulture ) {

     $scriptCmdPipeline += ' -UseCulture '
    }
    if ( $Delimiter ) {
     $scriptCmdPipeline += " -Delimiter '$Delimiter' "
    } 
    
    $scriptCmdPipeline += ' | Foreach-Object {$start=$true}'
    $scriptCmdPipeline += '{if ($start) {$start=$false} else {$_}} '
    
    $scriptCmdPipeline += " | Out-File -FilePath '$Path'"
    $scriptCmdPipeline += " -Encoding '$Encoding' -Append "
    
    if ($Force) {
     $scriptCmdPipeline += ' -Force'
    }

    if ($NoClobber) {

     $scriptCmdPipeline += ' -NoClobber'
    }   
   }
  }
 } 
  
$scriptCmd = {& $wrappedCmd @PSBoundParameters }

 if ( $AppendMode ) {

  $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
      $scriptCmdPipeline
    )
 } else {
  $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
      [string]$scriptCmd
    )
 }

$steppablePipeline = $scriptCmd.GetSteppablePipeline(

        $myInvocation.CommandOrigin)
 $steppablePipeline.Begin($PSCmdlet)

 } catch {

   throw
 }
    
}

process

{
  try {
      $steppablePipeline.Process($_)
  } catch {
      throw
  }
}

end

{
  try {
      $steppablePipeline.End()
  } catch {
      throw
  }
}
}

######################################################################


function folder-statistics ($mailbox){
if(!($mailbox)){write-host -backgroundcolor yellow -foregroundcolor red "You must specify mailbox"}
else{
$INC = read-host "Enter INC reference"
$CurrentDate = (Get-Date).ToString('_MMM-dd-yyyy_HH-mm-ss_')
$outfile = "c:\users\me\documents\Reports\folderstats_" + $mailbox + $CurrentDate + $INC + ".csv"
[INT]$reportvalue = Read-host "Specify folder size value to report above"
$newhash = @()

$mbsize = Get-MailboxStatistics $mailbox | select TotalItemSize
[String]$mbsizestring = $mbsize.TotalItemSize
$mbSizeMB = [math]::Round(((((($mbsizestring.split("(")[1])).replace(" bytes)","")).replace(",","")))/1048576)
$mbSizeGB = [math]::Round(($mbSizeMB/1024),2)

$folderstats = Get-MailboxFolderStatistics $mailbox | select identity,name,FolderPath,FolderType,ItemsInFolder,DeletedItemsInFolder,FolderSize,ItemsInFolderAndSubfolders,DeletedItemsInFolderAndSubfolders,FolderAndSubfolderSize

foreach($fs in $folderstats){
[string]$foldersize = $fs.foldersize
$FolderSizeMB = [math]::Round(((((($foldersize.split("(")[1])).replace(" bytes)","")).replace(",","")))/1048576)
$FolderSizeGB = [String][System.math]::Round(($FolderSizeMB/1024),2)

$percentofmailbox = (($FolderSizeMB/$mbSizeMB) * 100)
$percentrounded = [string]([System.Math]::Round($percentofmailbox,2))

[string]$identity=$fs.identity

$i = New-Object -TypeName PSObject
$i | Add-Member NoteProperty User $mailbox
$i | Add-Member NoteProperty TotalSize $mbSizeGB
$i | Add-Member NoteProperty Identity $identity.replace($mailbox,"")
$i | Add-Member NoteProperty Name $fs.name
$i | Add-Member NoteProperty GBFolderSize $FolderSizeGB
$i | Add-Member NoteProperty MBFolderSize $FolderSizeMB
$i | Add-Member NoteProperty PerCent $percentrounded
$i | Add-Member NoteProperty ItemsInFolder $fs.ItemsInFolder
$i | Add-Member NoteProperty DeletedItemsInFolder $fs.DeletedItemsInFolder
$i | Add-Member NoteProperty FolderPath $fs.FolderPath
if($FolderSizeMB -gt $reportvalue){
$newhash += $i
}
}
$newhash = $newhash | sort MBFolderSize -descending | export-csv $outfile -NoTypeInformation
start-process excel.exe $outfile
}
}

######################################################################

function enumerate-dl ($dl){
$users = Get-DistributionGroupMember $dl | ?{$_.RecipientType -like "Use*"} | select name | sort name
$groups = Get-DistributionGroupMember $dl | ?{$_.RecipientType -like "*Group"} | select name | sort name

if($groups){Write-Host -foregroundcolor yellow "Nested groups:"
$groups | ft -hidetableheaders}
else{Write-Host -foregroundcolor green "No nested groups found"}

if($users){Write-Host -foregroundcolor white "Users:"
$users | ft -hidetableheaders}
else{Write-Host -foregroundcolor yellow "No users found"}
}

######################################################################

function queues {
$Global:hubs = Get-TransportServer
foreach($hub in $hubs){get-queue -Server $hub | ?{$_.MessageCount -gt 1 -and $_.DeliveryType -notlike "shadow*"}}
}

######################################################################

PS profile variables & transcript

$Global:scripts = "C:\Users\me\Documents\scripts"
$Global:reports = "C:\Users\me\Documents\Reports"

[string]$date = get-date -format "_dd-MMM-yy_HH-mm-ss"


$basepath = $env:userprofile + "\documents\pstranscripts\"


if((test-path $basepath) -eq $false){New-item –ItemType directory –force $basepath}


$Global:filepath = $basepath + "pstranscript" + $date + ".txt"


start-transcript -Path $filepath

Snippets for PsObject

This one pulls PrimarySMTP from ProxyAddresses:

ipmo ActiveDirectory

$PAS = Get-ADUser -Filter * -properties ProxyAddresses | select -ExpandProperty ProxyAddresses | ?{$_ -clike "SMTP:*"}


# (can use -SearchBase with DN of OU or foreach through a list of users)


foreach($PA in $PAS){


if($PA){$smtp = $PA.split(":")[1]}


# (get just the e-mail address)


if($PA){

[string]$cn = "CN=" + $PA.samaccountname + ","
[string]$DN = $PA.DistinguishedName
$oudn = $DN.replace($cn,"")
$truncoudn = $oudn.replace(",DC=domain,DC=com","")
if($truncoudn -notlike "*Mail Enabled*"){

$i = New-Object -TypeName PSObject

$i | Add-Member NoteProperty SamAccountName $PA.samaccountname
$i | Add-Member NoteProperty TruncOU $truncoudn

$results += $i


}}}


O365 - resuming move requests

For resuming move requests outside of business hours

Picks up failed move requests too (if PAW is configured) - https://blogs.technet.microsoft.com/exchange/2017/12/01/paw-your-way-into-office-365-migrations/

EMS:

[string]$ExchOnline = "outlook.office365.com"
$exolusername = "#####@#####.onmicrosoft.com"
$exolpassword = Get-Content "C:\TEMP\EncryptedPasswords\#####.txt" | ConvertTo-SecureString  
$Excredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $exolusername, $exolpassword
$EXOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://$ExchOnline/powershell-liveid/ -Credential $Excredential -Authentication Basic -AllowRedirection -ErrorAction Stop

$startresumetimeinput = Read-host "Specify the time to resume move requests (in hh:mm format)"

$now = [System.DateTime]::Now
$today = [System.DateTime]::Today
$tomorrow = ($today).AddDays(1)

$startresumetime = Get-date $startresumetimeinput

if($now -gt $startresumetime){$startresumetime = ($startresumetime).AddDays(1)}

write-host "`$startresumetime is $startresumetime"


while($startresumetime -gt $now){write-progress -Activity "Waiting to resume moverequests at $($startresumetime)" -Status "Checking every 5 minutes. Time last checked at $($now)"; sleep 300; $now = [System.DateTime]::Now}


$warningPreference = "SilentlyContinue"

Import-PSSession $EXOnlineSession -warningaction silentlycontinue  -erroraction silentlycontinue | Out-Null
$warningPreference = "Continue"

[System.DateTime]::Now


Write-Progress -Activity "Getting move requests..." -Status "(where status = Suspended or Failed/failing)"

$mrstr = Get-MoveRequest -ResultSize unlimited | ?{$_.Status -eq "Suspended" -or $_.Status -like "*ail*"} | select exchangeguid
[INT]$mrstrcount = ($mrstr).count
Write-host "MoveRequests to be resumed:"$mrstrcount
[INT]$counter = 0
foreach($mrtr in $mrstr){
$counter ++
Write-Progress -Activity "Resuming move requests..." -Status "$counter of $mrstrcount"
[string]$mrexguid = $mrtr.exchangeguid
Resume-MoveRequest $mrexguid -confirm:$false
}

[System.DateTime]::Now

Get-PsSession | Remove-PsSession

O365 - suspending move requests

For suspending move requests during business hours (only those below 94%)

Bear these in mind:

The script has to produce a move request statistics report which takes an hour per 1500 requests.
Suspending is performed at a rate of 6000 per hour.
Work backwards from the cutoff time (with an emergency window allowed for).
E.g. Move requests = 3000
Business hours start at 09:00
Emergency cutoff  = 08:00
Suspend time = 07:30
Start report time = 05:00 (allow an extra bit of time between report and suspend to be safe)

EMS:

[string]$ExchOnline = "outlook.office365.com"
$exolusername = "#####@#####.onmicrosoft.com"
$exolpassword = Get-Content "C:\TEMP\EncryptedPasswords\#####.txt" | ConvertTo-SecureString  
$Excredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $exolusername, $exolpassword
$EXOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://$ExchOnline/powershell-liveid/ -Credential $Excredential -Authentication Basic -AllowRedirection -ErrorAction Stop

$startreporttimeinput = Read-host "Specify the time to run a report (in hh:mm format)"

$startsuspendtimeinput = Read-host "Also state what time mailbox moves should be suspended (in hh:mm format)"
$now = [System.DateTime]::Now
$today = [System.DateTime]::Today
$tomorrow = ($today).AddDays(1)

$startreporttime = Get-date $startreporttimeinput

$startsuspendtime = Get-date $startsuspendtimeinput
if($now -gt $startreporttime){$startreporttime = ($startreporttime).AddDays(1); $startsuspendtime = ($startsuspendtime).AddDays(1) }

write-host "`$startreporttime is $startreporttime"

write-host "`$startsuspendtime is $startsuspendtime"

while($startreporttime -gt $now){write-progress -Activity "Waiting to start report at $($startreporttime)" -Status "Time last checked at $($now)"; sleep 60; $now = [System.DateTime]::Now}


[string]$DateTime = (Get-Date).Tostring("dd-MM-yyyy_HHmmss")

[string]$FilePath = "C:\Temp\"
[string]$OutputFilename1 = [string]::Format($FilePath + "MoveRequest_Full_Report_{0}.csv",$DateTime)
[System.DateTime]::Now

$warningPreference = "SilentlyContinue"

Import-PSSession $EXOnlineSession -warningaction silentlycontinue  -erroraction silentlycontinue | Out-Null
$warningPreference = "Continue"

Write-host "Gathering all move request information..."

$mrs = Get-MoveRequest -ResultSize unlimited | ?{$_.Status -notlike "Complet*"}
[INT]$mrscount = ($mrs).count
$moveresults = @()
$remainingmoveresults = @()
[INT64]$totaldata = 0
[INT64]$remainingdatavol = 0
$allmovestats = @()
[int64]$alltransferreddata = 0
[INT]$counter = 0
[System.DateTime]::Now
Write-host "The move requests will now be analyzed for more information..."
foreach($mr in $mrs){
$counter ++
Write-Progress -Activity "Gathering move request statistics..." -Status "$counter of $mrscount"
[String]$ExchangeGUID = $mr.ExchangeGUID
$mrstats = Get-MoveRequestStatistics $ExchangeGUID
[int64]$TotalMailboxSizeBytes = $mrstats.TotalMailboxSize -replace "(^.*?\()|\D"
[INT64]$TotalMailboxSizeMB = $TotalMailboxSizeBytes /1048576
[INT64]$TotalMailboxSizeGB = $TotalMailboxSizeMB /1024
[INT64]$TotalMailboxSizeGBrounded =[math]::Round($TotalMailboxSizeGB,2)
[int64]$TransferredBytes = $mrstats.BytesTransferred -replace "(^.*?\()|\D"
[INT64]$TransferredMB = $TransferredBytes /1048576
[INT64]$TransferredGB = $TransferredMB /1024
[INT64]$TransferredGBrounded =[math]::Round($TransferredGB,2)
#$RemainingGBtoTransfer = $TotalMailboxSizeGB - $TransferredGB
#if($RemainingGBtoTransfer -lt 0){$RemainingGBtoTransfer = 0}
#[INT64]$RemainingGBtoTransferrounded =[math]::Round($RemainingGBtoTransfer,2)
[INT]$TotalMailboxItemCount = $mrstats.TotalMailboxItemCount
[INT]$ItemsTransferred = $mrstats.ItemsTransferred
[INT]$ItemsRemaining = (($TotalMailboxItemCount) - ($ItemsTransferred))
if($ItemsRemaining -lt 0){$ItemsRemaining = 0}
$i = New-Object -TypeName PSObject
$i | Add-Member -MemberType NoteProperty -Name Alias -Value $mrstats.Alias
$i | Add-Member -MemberType NoteProperty -Name PrimarySMTPAddress -Value $mrstats.Alias
$i | Add-Member -MemberType NoteProperty -Name BatchName -Value $mrstats.BatchName
$i | Add-Member -MemberType NoteProperty -Name TotalMailboxSizeGB -Value $TotalMailboxSizeGBrounded
$i | Add-Member -MemberType NoteProperty -Name TransferredGB -Value $TransferredGBrounded
$i | Add-Member -MemberType NoteProperty -Name PercentComplete -Value $mrstats.PercentComplete
$i | Add-Member -MemberType NoteProperty -Name TotalMailboxItemCount -Value $TotalMailboxItemCount
$i | Add-Member -MemberType NoteProperty -Name ItemsTransferred -Value $ItemsTransferred
$i | Add-Member -MemberType NoteProperty -Name ItemsRemaining -Value $ItemsRemaining
$i | Add-Member -MemberType NoteProperty -Name OverallDuration -Value $mrstats.OverallDuration
$i | Add-Member -MemberType NoteProperty -Name TotalInProgressDuration -Value $mrstats.TotalInProgressDuration
$i | Add-Member -MemberType NoteProperty -Name TotalSuspendedDuration -Value $mrstats.TotalSuspendedDuration
$i | Add-Member -MemberType NoteProperty -Name ExchangeGuid -Value $mrstats.ExchangeGuid
$moveresults += $i
$totaldata = (($totaldata) + ($TotalMailboxSizeGB))
[INT64]$alltransferreddata = (($alltransferreddata) + ($TransferredGB))
}
Write-host "Analysis complete. Report saved to"$OutputFilename1
[System.DateTime]::Now

Get-PsSession | Remove-PsSession


$moveresults | sort PercentComplete | export-csv $OutputFilename1 -NoTypeInformation

$moverequeststosuspend = $moveresults | ?{$_.PercentComplete -ne 95}
[INT]$moverequeststosuspendCount = ($moverequeststosuspend).Count

$timenow = [System.DateTime]::Now

while($startsuspendtime -gt $now){write-progress -Activity "Waiting to suspend $moverequeststosuspendCount move requests at $($startsuspendtime)" -Status "Checking once per minute. Time last checked at $($now)"; sleep 60; $now = [System.DateTime]::Now}

write-progress -Activity "Waiting to suspend $moverequeststosuspendCount move requests at $($startsuspendtime)" -Status "Completed" -Completed


[System.DateTime]::Now


$warningPreference = "SilentlyContinue"

Import-PSSession $EXOnlineSession -warningaction silentlycontinue  -erroraction silentlycontinue | Out-Null
$warningPreference = "Continue"

[INT]$counter = 0

Write-host "Suspending $moverequeststosuspendCount move requests..."
foreach($moverequesttosuspend in $moverequeststosuspend){
$counter ++
Write-Progress -Activity "Suspending $moverequeststosuspendCount move requests..." -Status "$counter of $moverequeststosuspendCount"
[String]$ExchangeGUID2 = $moverequesttosuspend.ExchangeGUID
Suspend-MoveRequest $ExchangeGUID2 -confirm:$false
}
[System.DateTime]::Now
Get-PsSession | Remove-PsSession

Office 365 move requests - random incremental sync interval

EMS:

There are two random number generation processes here. Firstly, a random number to change the sort order of the move requests (this is because with large numbers of move requests, the remote Exchange online PS connection can get terminated before the script completes - so the list order is changed each time the script is executed). Secondly, the time periods within the ranges specified are randomized, but only to minutes, not seconds).

The reason for the script? Because as move requests tend to be started in batches / migration batches, they generally start at the same time and there is more than a distinct possibility that they will achieve 95% sync status at the same time. As they are configured by default to 1 day, that may result in a large number of requests all wanting to do their incremental sync at the same time (possibly during business hours), which is not a good thing. Setting them to 12-24 hours in the first instance is a suggestion. Changing that to 1-3 hours in the run up to cutover is another suggestion (as the mailboxes are more likely to be synced at cutover time then).

The script checks for an Exchange online connection, although I advise closing any existing session and starting a new one each time.

[string]$ExchOnline = "outlook.office365.com"
If((Get-PsSession).Computername -ne $ExchOnline){Write-host -foregroundcolor yellow "The script requires a PsSession to $ExchOnline"; Exit}

Write-Progress -Activity "Getting the move requests" -Status "Working..."

$mrs = Get-moverequest -resultsize unlimited

[int]$rnd = get-random -Minimum 1 -Maximum 15

switch ($rnd)
    {
1 {$mrs = $mrs | sort RunspaceId}
2 {$mrs = $mrs | sort RunspaceId -descending}
3 {$mrs = $mrs | sort ExchangeGuid}
4 {$mrs = $mrs | sort ExchangeGuid -descending}
5 {$mrs = $mrs | sort TargetDatabase}
6 {$mrs = $mrs | sort TargetDatabase -descending}
7 {$mrs = $mrs | sort Alias}
8 {$mrs = $mrs | sort Alias -descending}
9 {$mrs = $mrs | sort DisplayName}
10 {$mrs = $mrs | sort DisplayName -descending}
11 {$mrs = $mrs | sort ExternalDirectoryObjectId}
12 {$mrs = $mrs | sort ExternalDirectoryObjectId -descending}
13 {$mrs = $mrs | sort Guid}
14 {$mrs = $mrs | sort Guid -descending}   
    }

[INT]$counter = 0

[INT]$mrscount = ($mrs).count
[INT]$mintimein = Read-host "Specify the minimum number of minutes"
[INT]$maxtimein = Read-host "Specify the maximum number of minutes"
$mintime = $mintimein +1
$maxtime = $maxtimein +1
Foreach($mr in $mrs){
$rn = get-random -Minimum $mintime -Maximum $maxtime
$ts = [timespan]::fromminutes($rn)
[String]$randomoffest = "{0:HH:mm:ss}" -f ([datetime]$ts.ticks)
$counter++
Write-Progress -Activity "Randomizing incremental sync" -Status "$counter of $mrscount"
Set-MoveRequest $mr.exchangeguid -IncrementalSyncInterval $randomoffest
Write-host "The following IncrementalSyncInterval has been set`: $mr`: $randomoffest"
Clear-Variable rn,ts,randomoffest
}

MSOL license check

*Requires O365 admin account with Licensing permission*

PS:

[string]$DateTime = (Get-Date).Tostring("dd-MM-yyyy_HHmmss")
[string]$FilePath = "C:\Temp\"
[string]$Filename1 = "MSOL_LicenseCheck"
[string]$OutputFilename1 = [string]::Format($FilePath + $Filename1 + "_{0}.csv",$DateTime)
[string]$AccountSkuID = "#######"

$msolUsername = "########@########.onmicrosoft.com"

$msolpassword = Get-Content "C:\TEMP\EncryptedPasswords\########.txt" | ConvertTo-SecureString
$msolcredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $msolUsername, $msolpassword

Write-Progress -Activity "Connecting to MSOL" -Status "Working..."

Connect-MsolService -Credential $msolcredential -WarningAction SilentlyContinue | Out-Null
Write-Progress -Activity "Connecting to MSOL" -Status "Working..." -Completed

Write-Progress -Activity "Getting MSOL users" -Status "Working..."

$msolusers = Get-MSOLUser -ALL
Write-Progress -Activity "Getting MSOL users" -Status "Working..." -Completed

[INT]$c = 0

[INT]$msoluserscount = ($msolusers).count

foreach($msoluser in $msolusers){

$c++

Write-Progress -Activity "Examining license status" -Status "$c of $msoluserscount" -PercentComplete ($c/$msoluserscount * 100)

$i = New-Object -TypeName PSObject

$i | Add-Member -MemberType NoteProperty -Name UserPrincipalName -Value $msoluser.UserPrincipalName


if ($msoluser.isLicensed -eq $false){

$i | Add-Member -MemberType NoteProperty -Name isLicensed -Value "False"
$i | Add-Member -MemberType NoteProperty -Name HasPlan -Value "False"
$i | Add-Member -MemberType NoteProperty -Name EXCHANGE_S_ENTERPRISE -Value "NA"
$i | Add-Member -MemberType NoteProperty -Name MCOSTANDARD -Value "NA"
}
if ($msoluser.isLicensed -eq $true -and $msoluser.licenses.AccountSkuId -notcontains $AccountSkuID){
$i | Add-Member -MemberType NoteProperty -Name isLicensed -Value "True"
$i | Add-Member -MemberType NoteProperty -Name HasPlan -Value "False"
$i | Add-Member -MemberType NoteProperty -Name EXCHANGE_S_ENTERPRISE -Value "NA"
$i | Add-Member -MemberType NoteProperty -Name MCOSTANDARD -Value "NA"
}
    if ($msoluser.isLicensed -eq $true -and $msoluser.licenses.AccountSkuId -contains $AccountSkuID){
$i | Add-Member -MemberType NoteProperty -Name isLicensed -Value "True"
$i | Add-Member -MemberType NoteProperty -Name HasPlan -Value "True"

foreach($msoluserlicense in ($msoluser.licenses | ?{$_.accountskuid -eq $AccountSkuID})){

[INT]$servicestatuscount = ($msoluserlicense.servicestatus).count
0..($servicestatuscount - 1)| ForEach-Object{
if(($msoluserlicense.servicestatus[$_]).ServicePlan.ServiceName -eq "EXCHANGE_S_ENTERPRISE"){
[string]$status = ($msoluserlicense.servicestatus[$_]).provisioningstatus
$i | Add-Member -MemberType NoteProperty -Name EXCHANGE_S_ENTERPRISE -Value $status
}
if(($msoluserlicense.servicestatus[$_]).ServicePlan.ServiceName -eq "MCOSTANDARD"){
[string]$status = ($msoluserlicense.servicestatus[$_]).provisioningstatus
$i | Add-Member -MemberType NoteProperty -Name MCOSTANDARD -Value $status
}
}
}
}
$i | export-csv $outputfilename1 -NoTypeInformation -Append -Force
}

Exchange online PS connection

PS:

#(Refer to post regarding encrypting credentials)

#Modify accordingly and save in PSProfile

[string]$ExchOnline = "outlook.office365.com"
$exolusername = "######@######.onmicrosoft.com"
$exolpassword = Get-Content "C:\TEMP\EncryptedPasswords\######.txt" | ConvertTo-SecureString  
$Excredential = new-object -typename System.Management.Automation.PSCredential -argumentlist $exolusername, $exolpassword

#Called via a function:

Function EOL-Connect($Prefix){
$EXOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://$ExchOnline/powershell-liveid/ -Credential $Excredential -Authentication Basic -AllowRedirection -ErrorAction Stop
$warningPreference = "SilentlyContinue"
if($Prefix){Import-PSSession $EXOnlineSession -prefix $Prefix -warningaction silentlycontinue  -erroraction silentlycontinue | Out-Null
write-host -backgroundcolor red -foregroundcolor yellow "Use command prefix: $Prefix"
}
else{Import-PSSession $EXOnlineSession -warningaction silentlycontinue  -erroraction silentlycontinue | Out-Null}
$warningPreference = "Continue"
}