Public/Test-M365HygienePermission.ps1
|
<# .SYNOPSIS Compares the scopes actually granted to the scopes each check needs. .DESCRIPTION Run this after signing in and before a long audit. Consent can be narrower than what was requested -- an administrator may have approved part of the request, or a policy may restrict what a user can consent to. Without this comparison the first sign that a check lacks its permission is a 403 partway through the run. This is a read-only comparison of the token's scope claim. It cannot detect the other half of delegated access: whether the signed-in user's directory role lets them see the data the scope covers. That surfaces at call time and is handled by each check reporting Skipped rather than clean. .PARAMETER CheckId Limit the comparison to these checks. .EXAMPLE Test-M365HygienePermission | Where-Object { -not $_.Satisfied } Lists the checks that will not be able to run. #> function Test-M365HygienePermission { [CmdletBinding()] [OutputType([pscustomobject])] param([string[]] $CheckId) $context = Get-MgContext if (-not $context) { throw 'Not connected to Microsoft Graph. Run Connect-M365Hygiene first.' } $granted = [System.Collections.Generic.HashSet[string]]::new( [string[]] @($context.Scopes), [StringComparer]::OrdinalIgnoreCase) $registry = Get-HygieneCheckRegistry $ids = if ($CheckId) { $CheckId } else { @($registry.Keys) } foreach ($id in $ids) { if (-not $registry.Contains($id)) { Write-Error "Unknown check '$id'." continue } $entry = $registry[$id] $missing = @($entry.RequiredScopes | Where-Object { -not $granted.Contains($_) }) [pscustomobject]@{ PSTypeName = 'M365TenantHygiene.PermissionStatus' CheckId = $entry.Id CheckName = $entry.Name RequiredScopes = $entry.RequiredScopes MissingScopes = $missing Satisfied = ($missing.Count -eq 0) } } } |