Remediation/ExchangeOnline/EXO-HYBRID.ps1
|
# UNTESTED: this script has never been run on a real tenant. Read it, try it on a test tenant first, and use it at your own risk. <# .SYNOPSIS Has the removal of the last on-premises Exchange server decided and prepares what depends on it (procedure EXO-HYBRID). .DESCRIPTION Data read: hybrid objects, synced accounts of the target, mailboxes of the target, matrices of the sources. Decision: decisions\EXO-HYBRID.csv, Keep or Remove. Keep is proposed: this removal raises the risk of the cutover and is kept apart from the migration windows. exports\synced-duplicates.csv lists the Duplicate accounts whose target mailbox is synced, with their original addresses. The cutover writes these addresses with Set-Mailbox: this is impossible on a synced mailbox while its Exchange attributes are managed on premises. These addresses are added in on-premises AD (Set-RemoteMailbox on the Exchange server). Nothing is automated on the on-premises server: the script has no access to it. And Microsoft does not support Set-Mailbox -IsExchangeCloudManaged with certificate authentication: moving the authority is done with an administrator account, in an interactive session. It stays a human action. #> $c = Open-TcRemediation $Users = @{} foreach ($u in Get-TcRemediationTable 'Target' 'users') { $Users[([string]$u['UPN']).ToLowerInvariant()] = $u } $TargetMailboxes = @(Get-TcRemediationTable 'Target' 'mailboxes') function Test-SyncedAccount { param([string]$Upn) $u = $Users[$Upn.ToLowerInvariant()] return ($u -and (ConvertTo-TcBoolean $u['Synced'])) } $proposals = foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t $objects = @(Get-TcRemediationTable $t 'exchangeHybrid') if (-not $objects.Count -and -not (Test-TcCollectionComplete $t 'exchangeHybrid')) { Write-TcRemediationAlert 'Partial hybrid collection: confirm with Get-OnPremisesOrganization' } $syncedOnes = @($TargetMailboxes | Where-Object { Test-SyncedAccount $_['UPN'] }) Write-TcRemediationNote ("{0} hybrid object(s); {1} synced mailbox(es) of the target out of {2}" -f $objects.Count, $syncedOnes.Count, $TargetMailboxes.Count) [ordered]@{ Tenant = $t; 'Hybrid objects' = (@($objects | ForEach-Object { '{0}: {1}' -f $_['Item'], $_['Detail'] }) -join ' ; '); 'Synced mailboxes' = $syncedOnes.Count; Proposal = 'Keep' } } $duplicates = @(foreach ($s in (Get-TcSourceRole)) { $i = Get-TcRemediationTenant $s foreach ($l in @((Get-TcMember (Get-TcRemediationIdentity $s) 'rows')) | Where-Object { $_['Decision'] -eq 'Duplicate' -and (Test-SyncedAccount $_['Target UPN']) }) { $b = Get-TcRemediationTable $s 'mailboxes' | Where-Object { $_['UPN'] -eq $l['Source UPN'] } | Select-Object -First 1 $addresses = if ($b) { @([string]$b['Addresses'] -split ';\s*' | Where-Object { (Get-TcAddressDomain $_) -in (Get-TcMember $i 'Domains') }) } else { @($l['Source UPN']) } [pscustomobject][ordered]@{ Source = $s; 'Source UPN' = $l['Source UPN']; 'Target mailbox' = $l['Target UPN']; "Original addresses" = ($addresses -join '; ') } } }) Write-TcRemediationNote ("{0} duplicate(s) target a synced mailbox: their original addresses go through the on-premises AD" -f $duplicates.Count) if ($duplicates.Count) { Export-TcRemediationReport -Name 'synced-duplicates' -Rows $duplicates | Out-Null } $decisions = @(Get-TcDecision -Name 'EXO-HYBRID' -Proposal @($proposals) -Key 'Tenant' -Choice 'Keep', 'Remove') foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -in $c.Tenants }) { if ((Get-TcMember $d 'Decision') -eq 'Keep') { Add-TcManualAction -Tenant (Get-TcMember $d 'Tenant') -Object 'On-premises Exchange server' -Description 'Keep the on-premises server and manage the Exchange attributes there' ` -Instruction @('The Exchange attributes of synced objects are managed from the on-premises server: do not shut it down or delete it.', ("On this server, add the original addresses of {0} duplicate(s), see exports\synced-duplicates.csv." -f $duplicates.Count)) continue } Add-TcManualAction -Tenant (Get-TcMember $d 'Tenant') -Object 'On-premises Exchange server' -Description 'Inventory what still depends on the on-premises Exchange server' ` -Instruction @('On the server: remaining mailboxes, SMTP relays, applications and connectors.', 'An application that relays through this server stops sending when it is removed.') Add-TcManualAction -Tenant (Get-TcMember $d 'Tenant') -Object 'Attribute authority' -Description 'Move the Exchange attributes to the cloud on a pilot group' ` -Instruction @('In an interactive session with an Exchange Administrator account, since the certificate is not supported:', 'Set-Mailbox -Identity <mailbox> -IsExchangeCloudManaged $true', 'Entra Connect Sync must be version 2.5.190.0 or later. After a local change, wait for one synchronization cycle and 24 hours.') Add-TcManualAction -Tenant (Get-TcMember $d 'Tenant') -Object 'Uninstall' -Description 'Uninstall the server with Exchange Setup, after the pilot is validated' ` -Instruction @('Follow the Microsoft procedure. Never simply shut down or delete the last server.', 'Keep this removal outside the migration windows.') } Invoke-TcRemediationPlan -Verify { param($t) $o = @(Invoke-TcServiceCommand -Tenant $t -Service Exo -Command 'Get-OnPremisesOrganization') New-TcRemediationResult Info ("{0} on-premises organization(s) declared" -f $o.Count) $m = @(Invoke-TcServiceCommand -Tenant $t -Service Exo -Command 'Get-Mailbox' -Parameters @{ ResultSize = 'Unlimited' } | Where-Object { (Get-TcMember $_ 'IsDirSynced') }) New-TcRemediationResult Info ("{0} synced mailbox(es), of which {1} managed in the cloud" -f $m.Count, @($m | Where-Object IsExchangeCloudManaged).Count) } Close-TcRemediation |