Public/Get-M365TenantSummary.ps1
|
function Get-M365TenantSummary { <# .SYNOPSIS Prints a compact, colored console overview of the tenant's security posture. .DESCRIPTION Gathers the key signals (users, MFA coverage, admins without MFA, Conditional Access policies, stale accounts, expired app secrets, permanent Global Admins) and prints a colored summary. Also returns a structured object for further processing. Read-only. .PARAMETER PassThru Return the summary object (it is returned anyway; use to suppress nothing extra). .EXAMPLE Get-M365TenantSummary #> [CmdletBinding()] [OutputType([pscustomobject])] param() Assert-TTGraph Write-TTLog -Level INFO -Message "Building tenant summary ..." $tenant = (Get-MgContext).TenantId # MFA $mfa = @(); try { $mfa = @(Get-M365MfaStatus) } catch { } $users = $mfa.Count $mfaReg = @($mfa | Where-Object MfaRegistered).Count $mfaPct = if ($users) { [math]::Round(100 * $mfaReg / $users) } else { 0 } $adminBad = @($mfa | Where-Object { $_.IsAdmin -and -not $_.MfaRegistered }).Count # Conditional Access $caTotal = 0; $caEnabled = 0 try { $pol = @(Get-MgIdentityConditionalAccessPolicy -All -ErrorAction Stop) $caTotal = $pol.Count $caEnabled = @($pol | Where-Object State -eq 'enabled').Count } catch { } # Stale users $stale = 0; try { $stale = @(Get-M365StaleUsers -InactiveDays 90).Count } catch { } # Expired app secrets $expiredSecrets = 0 try { $now = Get-Date $apps = Get-MgApplication -All -Property 'passwordCredentials,keyCredentials' -ErrorAction Stop foreach ($a in $apps) { foreach ($c in @($a.PasswordCredentials) + @($a.KeyCredentials)) { if ($c.EndDateTime -and ([datetime]$c.EndDateTime -lt $now)) { $expiredSecrets++ } } } } catch { } # Permanent Global Admins (PIM) $permGa = 0 try { $base = 'https://graph.microsoft.com/v1.0/roleManagement/directory' $active = Get-TTGraphCollection "$base/roleAssignmentScheduleInstances?`$expand=roleDefinition&`$top=100" $permGa = @($active | Where-Object { $_.assignmentType -eq 'Assigned' -and $_.roleDefinition.displayName -eq 'Global Administrator' }).Count } catch { } $summary = [pscustomobject]@{ Tenant = $tenant Users = $users MfaCoveragePct = $mfaPct AdminsWithoutMfa = $adminBad CaPoliciesTotal = $caTotal CaPoliciesEnabled = $caEnabled StaleUsers90d = $stale ExpiredAppSecrets = $expiredSecrets PermanentGlobalAdmins = $permGa GeneratedAt = (Get-Date -Format 'yyyy-MM-dd HH:mm') } # --- Console output ----------------------------------------------------- function Write-Row { param($Label, $Value, $Good) $color = if ($Good -eq $true) { 'Green' } elseif ($Good -eq $false) { 'Red' } else { 'Gray' } Write-Host (" {0,-26}" -f $Label) -NoNewline Write-Host $Value -ForegroundColor $color } Write-Host "" Write-Host " TenantToolbox - Tenant Summary" -ForegroundColor Cyan Write-Host " Tenant $tenant - $($summary.GeneratedAt)" -ForegroundColor DarkGray Write-Host (" " + ("-" * 44)) -ForegroundColor DarkGray Write-Row 'Users' $users $null Write-Row 'MFA coverage' "$mfaPct %" ($mfaPct -ge 90) Write-Row 'Admins without MFA' $adminBad ($adminBad -eq 0) Write-Row 'CA policies (enabled)' "$caEnabled / $caTotal" ($caEnabled -gt 0) Write-Row 'Stale users (90d)' $stale ($stale -eq 0) Write-Row 'Expired app secrets' $expiredSecrets ($expiredSecrets -eq 0) Write-Row 'Permanent Global Admins' $permGa ($permGa -le 2) Write-Host (" " + ("-" * 44)) -ForegroundColor DarkGray Write-Host "" $summary } |