checks/library/dormant-service-principals.ps1

# Enabled, tenant-owned service principals with no recent sign-in (beta signInActivity).
# If the beta endpoint/feature is unavailable the run-checks orchestrator records a loud skip.
$now = Get-Date
$items = @()
$sel = 'id,appId,displayName,accountEnabled,servicePrincipalType,appOwnerOrganizationId,signInActivity'
foreach ($sp in Get-GraphAll "https://graph.microsoft.com/beta/servicePrincipals?`$select=$sel&`$top=999") {
    if (-not $sp.accountEnabled) { continue }
    if ($sp.servicePrincipalType -eq 'ManagedIdentity') { continue }
    if (Test-MicrosoftFirstParty $sp) { continue }
    $last = To-Date $sp.signInActivity.lastSignInDateTime
    if (-not $last) { $last = To-Date $sp.signInActivity.lastNonInteractiveSignInDateTime }
    if (-not $last) {
        $items += [pscustomobject]@{ Title = $sp.displayName; Detail = "No recorded sign-in activity; appId $($sp.appId)" }
    } elseif (($now - $last).Days -gt 90) {
        $items += [pscustomobject]@{ Title = $sp.displayName; Detail = "Last sign-in $($last.ToString('yyyy-MM-dd')) (>90d); appId $($sp.appId)" }
    }
}
[pscustomobject]@{
    Name     = 'Dormant service principals (90+ days inactive)'
    Severity = 'high'
    Fix      = 'Review and disable or delete unused service principals after confirming no automation depends on them.'
    Items    = $items
}