Remediation/TeamsPhone/TEL-SCHEDULES.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 Exports the schedules and holidays of the source and recreates the holidays in the target (procedure TEL-SCHEDULES). .DESCRIPTION First exports, to <output>\telephony\<tenant>: the schedules (schedules.json) and the holidays of each auto attendant (Export-CsAutoAttendantHolidays). These files outlive the closure of the source. Holidays are shared calendars: New-CsOnlineSchedule recreates them before the auto attendants, without the admin center. Only future dates are carried over, after the customer decision in decisions\TEL-SCHEDULES.csv. A calendar with no future date is reported and not created. Business hours belong to each auto attendant: they are entered with it (TEL-VOICE-APPS), from schedules.json. #> $c = Open-TcRemediation $HolidayType = 'Fixed dates (holidays)' function Get-SourceSchedule { <# Schedules of the source, read once: each change of tenant opens a Teams session again. #> param([string]$Source) if (-not $script:TcRemediationRun['CacheSourceSchedules']) { $script:TcRemediationRun['CacheSourceSchedules'] = @{} } if (-not $script:TcRemediationRun['CacheSourceSchedules'][$Source]) { $script:TcRemediationRun['CacheSourceSchedules'][$Source] = @(Invoke-TcServiceCommand -Tenant $Source -Service Teams -Command 'Get-CsOnlineSchedule') } return $script:TcRemediationRun['CacheSourceSchedules'][$Source] } $proposals = foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t $schedules = @(Get-TcRemediationTable $t 'phoneSchedules') $holidays = @($schedules | Where-Object { $_['Type'] -eq $HolidayType }) $autoAttendants = @(Get-TcRemediationTable $t 'autoAttendants') Write-TcRemediationNote ("{0} schedule(s), {1} holiday calendar(s); {2} auto attendant(s)" -f $schedules.Count, $holidays.Count, $autoAttendants.Count) if ($autoAttendants.Count) { $exportFolder = Join-Path (Get-TcRemediationOutput) "telephony\$t" Add-TcBlockAction -Tenant $t -Service Teams -Object 'Schedules export' -Description "Export the schedules and holidays of $t" -Critical -Data @{ Folder = $exportFolder; AutoAttendants = @($autoAttendants | ForEach-Object { @{ Name = $_['Name']; Id = $_['Identity'] } }) } ` -Reason 'Reads the source: nothing to undo, the files stay for closure.' ` -Block { param($a) New-Item -ItemType Directory -Force -Path $a.data.Folder | Out-Null Get-SourceSchedule -Source $a.tenant | ConvertTo-Json -Depth 10 -WarningAction SilentlyContinue | Set-Content -LiteralPath (Join-Path $a.data.Folder 'horaires.json') -Encoding utf8 foreach ($s in $a.data.AutoAttendants) { try { $bytes = Invoke-TcServiceCommand -Tenant $a.tenant -Service Teams -Command 'Export-CsAutoAttendantHolidays' -Parameters @{ Identity = (Get-TcText $s 'Id') } [System.IO.File]::WriteAllBytes((Join-Path $a.data.Folder ("holidays-{0}.csv" -f (Get-TcMember $s 'Name'))), [byte[]]$bytes) } catch { Write-TcRemediationAlert ("Holidays of {0} not exported: {1}" -f (Get-TcMember $s 'Name'), (Get-TcErrorText $_)) } } } Add-TcManualAction -Tenant 'Target' -Object "Business hours" -Description "Enter the business hours with each auto attendant" ` -Instruction @(("The {0} weekly schedules belong to the auto attendants: they are entered when each one is created in the Teams admin center." -f ($schedules.Count - $holidays.Count)), "Reference: $exportFolder\horaires.json", 'Review the time zone of each auto attendant.') } foreach ($h in $holidays) { [ordered]@{ Tenant = $t; Calendar = $h['Name']; 'Used by' = $h['Used by']; Proposal = 'Recreate' } } } $decisions = @(Get-TcDecision -Name 'TEL-SCHEDULES' -Proposal @($proposals) -Key 'Tenant', 'Calendar' -Choice 'Recreate', 'Ignore') foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Decision') -eq 'Recreate' -and (Get-TcMember $_ 'Tenant') -in $c.Tenants }) { Add-TcBlockAction -Tenant 'Target' -Service Teams -Object (Get-TcMember $d 'Calendar') -Description ("Recreate the holiday calendar {0}" -f (Get-TcMember $d 'Calendar')) -Data @{ Source = (Get-TcMember $d 'Tenant'); Name = (Get-TcMember $d 'Calendar') } ` -Test { param($a) [bool]@(Invoke-TcServiceCommand -Tenant $a.tenant -Service Teams -Command 'Get-CsOnlineSchedule' | Where-Object { (Get-TcMember $_ 'Name') -eq $a.data.Name }).Count } ` -Block { param($a) $s = @(Get-SourceSchedule -Source $a.data.Source | Where-Object { (Get-TcMember $_ 'Name') -eq $a.data.Name }) | Select-Object -First 1 if (-not $s) { throw "Calendar $($a.data.Name) not found in $($a.data.Source)" } $now = Get-Date $ranges = @((Get-TcMember (Get-TcMember $s 'FixedSchedule') 'DateTimeRanges') | Where-Object { [datetime](Get-TcMember $_ 'End') -gt $now }) if (-not $ranges.Count) { throw "Calendar $($a.data.Name): no future date, to be completed by the customer." } $culture = [System.Globalization.CultureInfo]::InvariantCulture $objects = @(foreach ($p in $ranges) { Invoke-TcServiceCommand -Tenant $a.tenant -Service Teams -Command 'New-CsOnlineDateTimeRange' -Parameters @{ Start = ([datetime](Get-TcMember $p 'Start')).ToString('d/M/yyyy H:mm', $culture); End = ([datetime](Get-TcMember $p 'End')).ToString('d/M/yyyy H:mm', $culture) } }) $n = Invoke-TcServiceCommand -Tenant $a.tenant -Service Teams -Command 'New-CsOnlineSchedule' -Parameters @{ Name = $a.data.Name; FixedSchedule = $true; DateTimeRanges = $objects } $a.undo = @{ command = 'Remove-CsOnlineSchedule'; parameters = @{ Id = (Get-TcText $n 'Id') } } } } Invoke-TcRemediationPlan -Verify { param($t) $l = @(Invoke-TcServiceCommand -Tenant 'Target' -Service Teams -Command 'Get-CsOnlineSchedule') foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Decision') -eq 'Recreate' -and (Get-TcMember $_ 'Tenant') -eq $t }) { if (@($l | Where-Object { (Get-TcMember $_ 'Name') -eq (Get-TcMember $d 'Calendar') }).Count) { New-TcRemediationResult OK ("calendar {0} present" -f (Get-TcMember $d 'Calendar')) } else { New-TcRemediationResult Blocking ("calendar {0} missing from the target" -f (Get-TcMember $d 'Calendar')) } } } Close-TcRemediation |