Remediation/Coverage/FINAL-AUDIT.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
    Produces the final audit of each source tenant: what is left, subscriptions, services (procedure FINAL-AUDIT).
 
.DESCRIPTION
    Report final-audit-<tenant>.csv: one row per category, with the number of objects and what to confirm. The script reads the last
    snapshot: run Invoke-TcAssessment just before for an up-to-date state. It writes nothing.
 
    Azure: a read role can be missing in the sources. An empty table therefore does not prove there is no subscription: the script says
    so. Closing the Azure subscriptions is done in the portal, by a person.
    This audit comes before TENANT-CLOSURE and LICENSE-REMOVAL.
#>

$c = Open-TcRemediation

function Get-RestRow {
    param([string]$Tenant, [string]$Category, [int]$Number, [string]$ToConfirm = '')
    return [ordered]@{ Tenant = $Tenant; 'Category' = $Category; Number = $Number; 'State' = $(if ($Number) { 'To review' } else { 'Nothing read' }); 'To confirm' = $ToConfirm }
}

foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t
    $u = @(Get-TcRemediationTable $t 'users')
    $activeMembers = @($u | Where-Object { $_['Type'] -eq 'Member' -and (ConvertTo-TcBoolean $_['Enabled']) })
    $licenses = @(Get-TcRemediationTable $t 'licenses')
    $paid = @($licenses | Where-Object { [int]$_['Purchased'] -lt 10000 })
    $azure = @(Get-TcRemediationTable $t 'azureSubscriptions')
    $l = [System.Collections.Generic.List[object]]::new()
    $l.Add((Get-RestRow $t 'Active member accounts' $activeMembers.Count 'All blocked, except kept accounts (post-cutover check).'))
    $l.Add((Get-RestRow $t 'Blocked member accounts' @($u | Where-Object { $_['Type'] -eq 'Member' -and -not (ConvertTo-TcBoolean $_['Enabled']) }).Count))
    $l.Add((Get-RestRow $t 'Guest accounts' @($u | Where-Object { $_['Type'] -eq 'Guest' }).Count))
    $l.Add((Get-RestRow $t 'Mailboxes' @(Get-TcRemediationTable $t 'mailboxes').Count 'Data reconciled and backup in place.'))
    $l.Add((Get-RestRow $t 'Groups' @(Get-TcRemediationTable $t 'groups').Count))
    $l.Add((Get-RestRow $t 'SharePoint sites' @(Get-TcRemediationTable $t 'sites').Count 'Content copied, no more activity.'))
    $l.Add((Get-RestRow $t 'OneDrive' @(Get-TcRemediationTable $t 'oneDrive').Count))
    $l.Add((Get-RestRow $t 'Teams' @(Get-TcRemediationTable $t 'teams').Count))
    $l.Add((Get-RestRow $t 'Registered applications' @(Get-TcRemediationTable $t 'applications').Count 'The applications of this module go with Remove-TcAccess.'))
    $l.Add((Get-RestRow $t 'Devices managed by Intune' @(Get-TcRemediationTable $t 'managedDevices').Count 'Re-enrolled in the target or removed.'))
    $l.Add((Get-RestRow $t 'Autopilot devices' @(Get-TcRemediationTable $t 'autopilotDevices').Count 'Removed from the source before their registration in the target.'))
    $l.Add((Get-RestRow $t 'Phone numbers' @(Get-TcRemediationTable $t 'phoneNumbers').Count))
    $l.Add((Get-RestRow $t 'Power Platform environments' @(Get-TcRemediationTable $t 'powerEnvironments').Count))
    $l.Add((Get-RestRow $t 'Custom domains' @(Get-TcRemediationTable $t 'domains' | Where-Object { -not $_['Initial'] }).Count 'All moved to the target.'))
    $l.Add((Get-RestRow $t 'Paid subscriptions' $paid.Count (($paid | ForEach-Object { '{0} ({1})' -f $_['SKU'], $_['Purchased'] }) -join ', ')))
    $l.Add((Get-RestRow $t 'Azure subscriptions read' $azure.Count 'To check in the Azure portal: the read does not have the required role.'))
    [void](Export-TcRemediationReport -Name "audit-final-$t" -Rows $l.ToArray())
    Write-TcRemediationNote ("{0} active member(s), {1} mailbox(es), {2} site(s), {3} paid subscription(s)" -f $activeMembers.Count, @(Get-TcRemediationTable $t 'mailboxes').Count, @(Get-TcRemediationTable $t 'sites').Count, $paid.Count)
    if (-not $azure.Count) { Write-TcRemediationAlert "${t}: no Azure subscription read, which does not prove there is none. Check in the portal." }
    if (-not (Test-TcCollectionComplete $t 'users')) { Write-TcRemediationAlert "${t}: user collection incomplete, the audit is not reliable." }
}

Invoke-TcRemediationPlan
Close-TcRemediation