Remediation/ExchangeOnline/EXO-ROOMS.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
    Recreates the rooms and equipment in the target with the prefix and the same booking rules (procedure EXO-ROOMS).
 
.DESCRIPTION
    Data read: rooms and mailboxes tables of the source, matrix. Delegates and booking lists are not in the snapshot: they are read in
    the source at run time.
    A resource whose matrix row is not Create is not handled.
 
    Two steps, the same script runs again:
      1. before the cutover: creation of the mailbox, then booking rules. A delegate with no account in the target is reported;
      2. after the domains are verified: original address on the mailbox. Postponed while the domain is not accepted in the target.
 
    Limits: copying the calendar stays in the copy tool. Existing bookings point to the old address: check them after the copy.
#>

$c = Open-TcRemediation

$Copies = @('AutomateProcessing', 'BookingWindowInDays', 'MaximumDurationInMinutes', 'AllowConflicts', 'AllBookInPolicy', 'AllRequestInPolicy', 'ProcessExternalMeetingMessages')

function Get-TargetMailbox {
    param([string]$Identity)
    try { return (Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-Mailbox' -Parameters @{ Identity = $Identity }) } catch { return $null }
}

function Find-TargetAccountRow {
    <# Target account of a source account, from the matrix; returns the identity that exists. #>
    param([string]$Tenant, [string]$Account)
    $l = @((Get-TcMember (Get-TcRemediationIdentity $Tenant) 'rows')) | Where-Object { $_['Source UPN'] -eq $Account } | Select-Object -First 1
    if (-not $l -or $l['Decision'] -notin 'Create', 'Duplicate') { return $null }
    foreach ($i in @($l['Target UPN'], $l['Temporary UPN']) | Where-Object { $_ }) { if (Get-TargetMailbox $i) { return $i } }
    return $null
}

$Handled = [System.Collections.Generic.List[object]]::new()
foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t
    $i = Get-TcRemediationTenant $t
    $rooms = @(Get-TcRemediationTable $t 'roomsAndEquipment')
    if (-not $rooms.Count -and -not (Test-TcCollectionComplete $t 'roomsAndEquipment')) { Write-TcRemediationAlert 'Room collection incomplete: run Invoke-TcAssessment again'; continue }
    Write-TcRemediationNote ("{0} room(s) or equipment; {1} delegate(s) in total" -f $rooms.Count, (Get-TcMember (@($rooms | ForEach-Object { [int]$_['Delegates'] }) | Measure-Object -Sum) 'Sum'))

    foreach ($s in $rooms) {
        $b = Get-TcRemediationTable $t 'mailboxes' | Where-Object { $_['Primary address'] -eq $s['Address'] } | Select-Object -First 1
        $upn = if ($b) { $b['UPN'] } else { $s['Address'] }
        $row = @((Get-TcMember (Get-TcRemediationIdentity $t) 'rows')) | Where-Object { $_['Source UPN'] -eq $upn } | Select-Object -First 1
        if ($row -and $row['Decision'] -ne 'Create') { Write-TcRemediationNote ("{0}: matrix says {1}, resource not handled" -f $s['Address'], $row['Decision']); continue }
        if (-not $row) { Write-TcRemediationAlert ("{0}: missing from the matrix, resource not handled" -f $s['Address']); continue }

        $name = '{0}-{1}' -f (Get-TcMember $i 'Prefix'), $s['Resource']
        $alias = ('{0}-{1}' -f (Get-TcMember $i 'Prefix'), ($s['Resource'] -replace '[^A-Za-z0-9\.\-_]', '')).ToLowerInvariant()
        $kind = if ($s['Type'] -eq 'RoomMailbox') { 'Room' } else { 'Equipment' }
        $Handled.Add(@{ Tenant = $t; Name = $name; Handling = $s['Processing']; Window = $s['Booking window (days)']; Duration = $s['Max duration (min)'] })

        Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'New-Mailbox' -Parameters @{ Name = $name; Alias = $alias; $kind = $true } -Object $name -Description ("Create {0} in the target ({1})" -f $name, $kind) `
            -Test { param($a) [bool](Get-TargetMailbox $a.parameters.Name) } `
            -Undo @{ command = 'Remove-Mailbox'; parameters = @{ Identity = $name } }

        Add-TcBlockAction -Tenant 'Target' -Service Exo -Object $name -Data @{ Source = $t; Address = $s['Address']; Name = $name; Handling = $s['Processing']; Window = [int]$s['Booking window (days)']; Duration = [int]$s['Max duration (min)'] } `
            -Description ("Carry over the booking rules of {0} ({1}, {2} d, {3} min)" -f $s['Address'], $s['Processing'], $s['Booking window (days)'], $s['Max duration (min)']) `
            -Test {
                param($a)
                $x = $a.data
                if (-not (Get-TargetMailbox (Get-TcMember $x 'Name'))) { return Test-TcPostponed $a ("{0} does not exist in the target yet" -f (Get-TcMember $x 'Name')) }
                $k = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-CalendarProcessing' -Parameters @{ Identity = (Get-TcMember $x 'Name') }
                $before = @{ Identity = (Get-TcMember $x 'Name') }
                foreach ($n in $Copies) { $before[$n] = (Get-TcMember $k $n) }
                $a.undo = @{ command = 'Set-CalendarProcessing'; parameters = $before }
                return ((Get-TcText $k 'AutomateProcessing') -eq (Get-TcMember $x 'Handling') -and [int](Get-TcMember $k 'BookingWindowInDays') -eq (Get-TcMember $x 'Window') -and [int](Get-TcMember $k 'MaximumDurationInMinutes') -eq (Get-TcMember $x 'Duration'))
            } `
            -Block {
                param($a)
                $x = $a.data
                $k = Invoke-TcServiceCommand -Tenant (Get-TcMember $x 'Source') -Service Exo -Command 'Get-CalendarProcessing' -Parameters @{ Identity = (Get-TcMember $x 'Address') }
                $p = @{ Identity = (Get-TcMember $x 'Name') }
                foreach ($n in $Copies) { $p[$n] = (Get-TcMember $k $n) }
                $missing = [System.Collections.Generic.List[string]]::new()
                $lists = @{ ResourceDelegates = [System.Collections.Generic.List[string]]::new(); BookInPolicy = [System.Collections.Generic.List[string]]::new() }
                foreach ($field in 'ResourceDelegates', 'BookInPolicy') {
                    foreach ($v in @((Get-TcMember $k $field) | Where-Object { $_ })) {
                        try { $r = Invoke-TcServiceCommand -Tenant (Get-TcMember $x 'Source') -Service Exo -Command 'Get-Recipient' -Parameters @{ Identity = [string]$v } } catch { $r = $null }
                        $account = if ($r) { Find-TargetAccountRow (Get-TcMember $x 'Source') ((Get-TcText $r 'PrimarySmtpAddress')) }
                        if ($account) { $lists[$field].Add($account) } else { $missing.Add([string]$v) }
                    }
                }
                if ((Get-TcCount (Get-TcMember $lists 'ResourceDelegates'))) { $p.ResourceDelegates = @((Get-TcMember $lists 'ResourceDelegates')) }
                if ((Get-TcCount (Get-TcMember $lists 'BookInPolicy'))) { $p.BookInPolicy = @((Get-TcMember $lists 'BookInPolicy')) }
                Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Set-CalendarProcessing' -Parameters $p | Out-Null
                if ($missing.Count) { Write-TcRemediationAlert ("{0}: {1} delegate(s) with no account in the target, to add after their cutover: {2}" -f (Get-TcMember $x 'Name'), $missing.Count, ($missing -join ', ')) }
            }

        $addresses = if ($b) { @([string]$b['Addresses'] -split ';\s*' | Where-Object { (Get-TcAddressDomain $_) -in (Get-TcMember $i 'Domains') }) } else { @($s['Address']) }
        foreach ($x in $addresses) {
            $addition = 'smtp:{0}' -f $x
            Add-TcCommandAction -Tenant 'Target' -Service Exo -Command 'Set-Mailbox' -Parameters @{ Identity = $name; EmailAddresses = @{ Add = $addition } } -Object $name -Description ("Give {0} back its original address {1}" -f $name, $x) `
                -Test {
                    param($a)
                    $address = [string]$a.parameters.EmailAddresses.Add -replace '^smtp:', ''
                    $domain = Get-TcAddressDomain $address
                    if (-not (Test-TcTargetDomain $domain)) { return Test-TcPostponed $a ("{0} is not accepted in the target: run again after the domains are verified in the target" -f $domain) }
                    $m = Get-TargetMailbox $a.parameters.Identity
                    if (-not $m) { return Test-TcPostponed $a ("{0} does not exist in the target yet" -f $a.parameters.Identity) }
                    return @((Get-TcMember $m 'EmailAddresses') | Where-Object { ($_ -replace '^smtp:', '') -eq $address }).Count -gt 0
                } `
                -Undo @{ command = 'Set-Mailbox'; parameters = @{ Identity = $name; EmailAddresses = @{ Remove = $addition } } }
        }

        Add-TcManualAction -Tenant 'Target' -Object $name -Description ("Copy the calendar of {0} to {1}" -f $s['Address'], $name) `
            -Instruction @("Add the resource to the plan of the copy tool, then migrate its calendar.", "After the copy, check the bookings: they point to the old address.")
    }
}

Invoke-TcRemediationPlan -Verify {
    param($t)
    foreach ($s in $Handled | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t }) {
        if (-not (Get-TargetMailbox (Get-TcMember $s 'Name'))) { New-TcRemediationResult Blocking ("{0} missing from the target" -f (Get-TcMember $s 'Name')); continue }
        $k = Invoke-TcServiceCommand -Tenant 'Target' -Service Exo -Command 'Get-CalendarProcessing' -Parameters @{ Identity = (Get-TcMember $s 'Name') }
        if ((Get-TcText $k 'AutomateProcessing') -eq (Get-TcMember $s 'Handling') -and [int](Get-TcMember $k 'BookingWindowInDays') -eq [int](Get-TcMember $s 'Window') -and [int](Get-TcMember $k 'MaximumDurationInMinutes') -eq [int](Get-TcMember $s 'Duration')) { New-TcRemediationResult OK ("{0}: booking rules carried over, {1} delegate(s)" -f (Get-TcMember $s 'Name'), @((Get-TcMember $k 'ResourceDelegates')).Count) }
        else { New-TcRemediationResult Warning ("{0}: rules differ from the source" -f (Get-TcMember $s 'Name')) }
    }
}
Close-TcRemediation