Remediation/Consolidation/CON-VOICE.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
    Draws up the single telephony plan, all sources together, from the snapshot (procedure CON-VOICE).
 
.DESCRIPTION
    No connection: the script reads the snapshot and the identity matrix, then produces four reports for the workshop with the customer:
    numbers, voice users, auto attendants and queues, licenses.
    A resource account shows as a voice account in the snapshot: the script separates them (VoiceApp function), otherwise the need for
    Teams Phone licenses would be overstated.
    Order to follow next: emergency addresses, resource accounts, auto attendants and queues, then numbers.
    The Porting moment column stays to fill with the customer; the target provider comes from the decision TEL-ROUTING-TARGET.
    Limit: approving the plan and the rollback of each port belong to the customer.
#>

$c = Open-TcRemediation

$TypeFile = 'TEL-ROUTING-TARGET'
$targetType = if (Test-TcDecisionFile $TypeFile) { @(Read-TcDecisionRow $TypeFile | ForEach-Object { (Get-TcMember $_ 'Decision') } | Where-Object { $_ }) | Select-Object -First 1 }

$targetAccounts = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
foreach ($u in Get-TcRemediationTable 'Target' 'teamsUsers') { [void]$targetAccounts.Add([string]$u['UPN']) }

function Get-TargetAccount {
    <# Target account of a matrix row: the final UPN, otherwise the temporary one, otherwise nothing while it does not exist. #>
    param($Row)
    foreach ($u in (Get-TcMember $Row 'Target UPN'), (Get-TcMember $Row 'Temporary UPN')) { if ($u -and $targetAccounts.Contains([string]$u)) { return [string]$u } }
    return ''
}

$theNumbers = [System.Collections.Generic.List[object]]::new()
$theUsers = [System.Collections.Generic.List[object]]::new()
$theApplications = [System.Collections.Generic.List[object]]::new()
$peopleTotal = 0; $resourceTotal = 0

foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t
    if ($t -eq 'Target') {
        $types = @(Get-TcRemediationTable $t 'phoneNumbers' | Group-Object { $_['Type'] } | ForEach-Object { '{0}: {1}' -f (Get-TcMember $_ 'Name'), (Get-TcCount $_) })
        Write-TcRemediationNote ("Target: {0} number(s) ({1}), {2} voice route(s)" -f @(Get-TcRemediationTable $t 'phoneNumbers').Count, ($types -join ', '), @(Get-TcRemediationTable $t 'voiceRoutes').Count)
        continue
    }
    $matrix = @{}
    foreach ($l in @((Get-TcMember (Get-TcRemediationIdentity $t) 'rows'))) { $matrix[((Get-TcText $l 'Source UPN')).ToLowerInvariant()] = $l }
    $voice = @(Get-TcRemediationTable $t 'teamsUsers' | Where-Object { ConvertTo-TcBoolean $_['Enterprise Voice'] })
    $people = @($voice | Where-Object { $_['Features'] -notmatch 'VoiceApp' })
    $resources = @(Get-TcRemediationTable $t 'resourceAccounts')
    $numbers = @(Get-TcRemediationTable $t 'phoneNumbers')
    $peopleTotal += $people.Count; $resourceTotal += $resources.Count
    Write-TcRemediationNote ("{0} voice account(s): {1} user(s) and {2} resource account(s); {3} number(s)" -f $voice.Count, $people.Count, ($voice.Count - $people.Count), $numbers.Count)

    foreach ($n in $numbers) {
        $source = [string]$n['Assigned to']
        $l = $matrix[$source.ToLowerInvariant()]
        $theNumbers.Add([ordered]@{ Tenant = $t; 'Number' = $n['Number']; Type = $n['Type']; Assignment = $n['Assignment']; 'Source account' = $source
                                  'Target account' = $(if ($l) { Get-TargetAccount $l }); 'Target type' = $targetType; 'Porting moment' = '' })
    }
    foreach ($u in $people) {
        $l = $matrix[([string]$u['UPN']).ToLowerInvariant()]
        $theUsers.Add([ordered]@{ Tenant = $t; 'Source account' = $u['UPN']; Name = $u['Name']; 'Number' = $u['Number']; 'Matrix decision' = $(if ($l) { (Get-TcMember $l 'Decision') }); 'Target account' = $(if ($l) { Get-TargetAccount $l }) })
    }
    foreach ($s in Get-TcRemediationTable $t 'autoAttendants') { $theApplications.Add([ordered]@{ Tenant = $t; Type = 'Auto attendant'; Name = $s['Name']; 'Resource accounts' = $s['Resource accounts']; 'Detail' = ('{0} flow(s), {1} schedule(s), {2}' -f $s["Call flows"], $s['Schedules'], $s['Time zone']) }) }
    foreach ($q in Get-TcRemediationTable $t 'callQueues') { $theApplications.Add([ordered]@{ Tenant = $t; Type = "Call queue"; Name = $q['Name']; 'Resource accounts' = $q['Resource accounts']; 'Detail' = ('{0}, {1} agent(s), {2} list(s)' -f $q['Routing'], $q['Agents'], $q['Lists']) }) }

    $freeOnes = @($numbers | Where-Object { -not $_['Assigned to'] }).Count
    if ($freeOnes) { Write-TcRemediationAlert ("{0} number(s) with no assignment: to keep or to drop with the customer" -f $freeOnes) }
    $missing = @($people | Where-Object { -not $matrix[([string]$_['UPN']).ToLowerInvariant()] }).Count
    if ($missing) { Write-TcRemediationAlert ("{0} voice user(s) missing from the matrix" -f $missing) }
}

$licenses = @(foreach ($sku in 'MCOEV', 'PHONESYSTEM_VIRTUALUSER') {
    $l = Get-TcRemediationTable 'Target' 'licenses' | Where-Object { $_['SKU'] -eq $sku } | Select-Object -First 1
    $available = if ($l) { [int]$l['Available'] } else { 0 }
    $need = if ($sku -eq 'MCOEV') { $peopleTotal } else { $resourceTotal }
    [ordered]@{ License = $sku; Need = $need; 'Available in target' = $available; 'To order' = [math]::Max(0, $need - $available) }
})
foreach ($l in $licenses) { if ((Get-TcMember $l 'To order')) { Write-TcRemediationAlert ("{0}: {1} required, {2} available in the target" -f (Get-TcMember $l 'License'), (Get-TcMember $l 'Need'), (Get-TcMember $l 'Available in target')) } }
if (-not $targetType) { Write-TcRemediationAlert 'Provider of the numbers not decided: run TEL-ROUTING-TARGET. The number of calling plans stays unknown.' }

Close-TcRemediationBlock
Export-TcRemediationReport -Name 'phone-plan-numbers' -Rows @($theNumbers) | Out-Null
Export-TcRemediationReport -Name 'phone-plan-users' -Rows @($theUsers) | Out-Null
Export-TcRemediationReport -Name 'phone-plan-applications' -Rows @($theApplications) | Out-Null
Export-TcRemediationReport -Name 'phone-plan-licenses' -Rows @($licenses) | Out-Null

Invoke-TcRemediationPlan
Close-TcRemediation