Remediation/EntraID/ENT-ADMINS.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
    Reduces the permanent Global Administrators, account by account, according to the customer decisions (procedure ENT-ADMINS).
 
.DESCRIPTION
    The first preview creates decisions\ENT-ADMINS.csv: one row per account that holds the role, with a proposal. Only the rows
    marked Remove are applied.
    Guards, whatever the decisions:
      - emergency accounts and the accounts that run the migration keep the role;
      - in each tenant, at least two Global Administrators stay in place.
#>

$c = Open-TcRemediation

$GlobalAdminId = '62e90394-69f5-4237-9190-012177145e10'
$AdminMinimum = 2

$proposals = foreach ($t in $c.Tenants) {
    Write-TcRemediationTenant $t 'Global Administrators read'
    $i = Get-TcRemediationTenant $t
    $activeOnes = @(Get-TcRemediationTable $t 'adminRoles' | Where-Object { $_['Role'] -eq 'Global Administrator' -and $_['Assignment'] -eq 'Enabled' -and $_['Principal type'] -eq 'user' })
    Write-TcRemediationNote ("{0} active assignment(s) of the Global Administrator role" -f $activeOnes.Count)
    foreach ($r in $activeOnes) {
        $u = Get-TcRemediationTable $t 'users' | Where-Object { $_['UPN'] -eq $r['Principal'] } | Select-Object -First 1
        [ordered]@{ Tenant = $t; Account = $r['Principal']; Name = $(if ($u) { $u['Name'] }); 'Days inactive' = $(if ($u) { $u['Days inactive'] })
                    Proposal = $(if (Test-TcProtectedAccount $r['Principal'] $i) { 'Keep' } else { '' }) }
    }
}

$decisions = @(Get-TcDecision -Name 'ENT-ADMINS' -Proposal @($proposals) -Key 'Tenant', 'Account' -Choice 'Remove', 'Keep')

foreach ($t in $c.Tenants) {
    $i = Get-TcRemediationTenant $t
    $activeOnes = @(Get-TcRemediationTable $t 'adminRoles' | Where-Object { $_['Role'] -eq 'Global Administrator' -and $_['Assignment'] -eq 'Enabled' -and $_['Principal type'] -eq 'user' })
    $remaining = $activeOnes.Count
    foreach ($d in $decisions | Where-Object { (Get-TcMember $_ 'Tenant') -eq $t -and (Get-TcMember $_ 'Decision') -eq 'Remove' }) {
        if (Test-TcProtectedAccount (Get-TcMember $d 'Account') $i) { Write-TcRemediationAlert ("{0}: {1} is a protected account, the Remove decision is ignored" -f $t, (Get-TcMember $d 'Account')); continue }
        if ($remaining - 1 -lt $AdminMinimum) { Write-TcRemediationAlert ("{0}: {1} keeps the role, otherwise fewer than {2} Global Administrators would remain" -f $t, (Get-TcMember $d 'Account'), $AdminMinimum); continue }
        $remaining--
        Add-TcBlockAction -Tenant $t -Service Graph -Object (Get-TcMember $d 'Account') -Description ("Remove the Global Administrator role from {0}" -f (Get-TcMember $d 'Account')) -Data @{ Account = (Get-TcMember $d 'Account'); Role = $GlobalAdminId } `
            -Test {
                param($a)
                $list = @(Invoke-TcGraph -Tenant $a.tenant -Uri ("v1.0/roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '{0}'&`$expand=principal" -f $a.data.Role) -All)
                $a.data.Attribution = $list | Where-Object { (Get-TcMember (Get-TcMember $_ 'principal') 'userPrincipalName') -eq $a.data.Account } | Select-Object -First 1
                return -not $a.data.Attribution
            } `
            -Block {
                param($a)
                $x = $a.data.Attribution
                $a.undo = @{ type = 'Graph'; tenant = $a.tenant; method = 'POST'; uri = 'v1.0/roleManagement/directory/roleAssignments'; body = @{ principalId = (Get-TcMember $x 'principalId'); roleDefinitionId = (Get-TcMember $x 'roleDefinitionId'); directoryScopeId = (Get-TcMember $x 'directoryScopeId') } }
                Invoke-TcGraph -Tenant $a.tenant -Method DELETE -Uri ("v1.0/roleManagement/directory/roleAssignments/{0}" -f (Get-TcMember $x 'id'))
            }
    }
}

Invoke-TcRemediationPlan -Verify {
    param($t)
    $n = @(Invoke-TcGraph -Tenant $t -Uri ("v1.0/roleManagement/directory/roleAssignments?`$filter=roleDefinitionId eq '{0}'" -f $GlobalAdminId) -All).Count
    if ($n -lt 5) { New-TcRemediationResult OK "$n role assignment(s), below the threshold of five" } else { New-TcRemediationResult Warning "$n role assignments: the goal is fewer than five" }
}
Close-TcRemediation