Remediation/EntraID/TENANT-CLOSURE.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 Cleans a source tenant, object by object, on decision and on written authorization (procedure TENANT-CLOSURE). .DESCRIPTION The target is refused: this script only touches the sources. If the target is asked for, it stops before any action. Two decisions are required: - decisions\TENANT-CLOSURE.csv: one row per tenant. Enter Allowed, the decision maker, the date and the reference of the written document in Note. If a field is missing, no action is planned for this tenant. - decisions\TENANT-CLOSURE-OBJECTS.csv: one row per object. Delete or Keep. A proposal is not a decision. It only aims at blocked accounts and guests, groups and applications. Order: applications, groups, accounts, domains. All the actions are critical: a failure stops the rest of the plan. Never listed: emergency accounts, kept accounts, migration team accounts, the applications of this module (Remove-TcAccess removes them). Accounts and Microsoft 365 groups stay in the recycle bin for 30 days: Undo-TcRemediation restores them within this delay. Distribution lists are not deleted through Graph: they go away with the tenant. Deleting the tenant has no API: it is done in the Microsoft Entra admin center, once all subscriptions are deprovisioned, by a person. Keep to the Microsoft grace periods. #> $c = Open-TcRemediation if ('Target' -in $c.Tenants) { throw 'This is the target tenant: this script refuses to handle it. No action, no decision.' } $authorizations = @(Get-TcDecision -Name 'TENANT-CLOSURE' -Proposal @($c.Tenants | ForEach-Object { [ordered]@{ Tenant = $_; Object = 'Written authorization to close (reference in Note)' } }) -Key 'Tenant' -Choice 'Allowed') $proposals = foreach ($t in $c.Tenants) { Write-TcRemediationTenant $t $i = Get-TcRemediationTenant $t $apps = @(Get-TcRemediationTable $t 'applications' | Where-Object { $_['Name'] -notmatch (Get-TcRemediationOption 'ProtectedApplicationPattern') }) $groups = @(Get-TcRemediationTable $t 'groups' | Where-Object { $_['Type'] -in 'Microsoft 365', 'Security' }) $accounts = @(Get-TcRemediationTable $t 'users' | Where-Object { -not (Test-TcProtectedAccount $_['UPN'] $i) }) $domains = @(Get-TcRemediationTable $t 'domains' | Where-Object { -not $_['Initial'] }) Write-TcRemediationNote ("Read: {0} application(s), {1} group(s), {2} account(s), {3} custom domain(s)" -f $apps.Count, $groups.Count, $accounts.Count, $domains.Count) foreach ($x in $apps) { [ordered]@{ Tenant = $t; Rank = '1'; Type = 'Application'; Object = $x['Name']; Id = $x['AppId']; Proposal = 'Delete' } } foreach ($x in $groups) { [ordered]@{ Tenant = $t; Rank = '2'; Type = "Group $($x['Type'])"; Object = $x['Name']; Id = $x['Id']; Proposal = 'Delete' } } foreach ($x in $accounts) { [ordered]@{ Tenant = $t; Rank = '3'; Type = "Account $($x['Type'])"; Object = $x['UPN']; Id = $x['Id']; Proposal = $(if ($x['Type'] -eq 'Guest' -or -not (ConvertTo-TcBoolean $x['Enabled'])) { 'Delete' } else { '' }) } } foreach ($x in $domains) { [ordered]@{ Tenant = $t; Rank = '4'; Type = 'Domain'; Object = $x['Domain']; Id = $x['Domain']; Proposal = '' } } } $decisions = @(Get-TcDecision -Name 'TENANT-CLOSURE-OBJECTS' -Proposal @($proposals) -Key 'Tenant', 'Type', 'Id' -Choice 'Delete', 'Keep') foreach ($t in $c.Tenants) { $a = $authorizations | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t } | Select-Object -First 1 if (-not $a -or -not $a.'Decision maker' -or -not $a.Date -or -not ([string]$a.Note).Trim()) { Write-TcRemediationAlert "${t}: no complete written authorization (decision, decision maker, date, reference). No action planned." continue } Write-TcRemediationNote ("{0}: authorization by {1} on {2}, reference {3}" -f $t, $a.'Decision maker', $a.Date, $a.Note) $i = Get-TcRemediationTenant $t foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t -and (Get-TcMember $_ 'Decision') -eq 'Delete' } | Sort-Object Rank, Object) { if ((Get-TcMember $d 'Rank') -eq '3' -and (Test-TcProtectedAccount (Get-TcMember $d 'Object') $i)) { Write-TcRemediationAlert ("{0} is a protected account: decision Delete ignored" -f (Get-TcMember $d 'Object')); continue } switch ((Get-TcMember $d 'Rank')) { '1' { $uri = "v1.0/applications(appId='$((Get-TcMember $d 'Id'))')" Add-TcGraphAction -Tenant $t -Method DELETE -Uri $uri -Object (Get-TcMember $d 'Object') -Description "Remove the application $((Get-TcMember $d 'Object'))" -Critical -Reason 'Application can be restored for 30 days from the deleted applications of Entra.' -Test { param($x) Test-TcGraphAbsent -Tenant (Get-TcMember $x 'tenant') -Uri (Get-TcMember $x 'uri') } } '2' { $uri = "v1.0/groups/$((Get-TcMember $d 'Id'))" if ((Get-TcMember $d 'Type') -eq 'Microsoft 365 group') { Add-TcGraphAction -Tenant $t -Method DELETE -Uri $uri -Object (Get-TcMember $d 'Object') -Description "Delete the group $((Get-TcMember $d 'Object'))" -Critical -Undo @{ type = 'Graph'; tenant = $t; method = 'POST'; uri = "v1.0/directory/deletedItems/$((Get-TcMember $d 'Id'))/restore" } -Test { param($x) Test-TcGraphAbsent -Tenant (Get-TcMember $x 'tenant') -Uri (Get-TcMember $x 'uri') } } else { Add-TcGraphAction -Tenant $t -Method DELETE -Uri $uri -Object (Get-TcMember $d 'Object') -Description "Delete the security group $((Get-TcMember $d 'Object'))" -Critical -Reason 'A deleted security group cannot be restored.' -Test { param($x) Test-TcGraphAbsent -Tenant (Get-TcMember $x 'tenant') -Uri (Get-TcMember $x 'uri') } } } '3' { $uri = "v1.0/users/$((Get-TcMember $d 'Id'))" Add-TcGraphAction -Tenant $t -Method DELETE -Uri $uri -Object (Get-TcMember $d 'Object') -Description "Delete the account $((Get-TcMember $d 'Object'))" -Critical -Undo @{ type = 'Graph'; tenant = $t; method = 'POST'; uri = "v1.0/directory/deletedItems/$((Get-TcMember $d 'Id'))/restore" } -Test { param($x) Test-TcGraphAbsent -Tenant (Get-TcMember $x 'tenant') -Uri (Get-TcMember $x 'uri') } } '4' { $uri = "v1.0/domains/$((Get-TcMember $d 'Id'))" Add-TcGraphAction -Tenant $t -Method DELETE -Uri $uri -Object (Get-TcMember $d 'Object') -Description "Remove the domain $((Get-TcMember $d 'Object'))" -Critical -Reason 'To go back: add the domain again and verify it again.' -Test { param($x) Test-TcGraphAbsent -Tenant (Get-TcMember $x 'tenant') -Uri (Get-TcMember $x 'uri') } } } } Add-TcManualAction -Tenant $t -Object "Deletion of tenant $t" -Description "Delete the tenant $t in the Microsoft Entra admin center" -Who 'Customer and migration team' -Instruction @( 'Check: invoices paid, subscriptions canceled then deprovisioned (CLOSE-05), Azure subscriptions deleted, one Global Administrator left.', 'Run Remove-TcAccess for this tenant, then, in Microsoft Entra: Overview, Manage tenants, Delete.', 'No API allows this deletion.') } Invoke-TcRemediationPlan Close-TcRemediation |