src/Get-ScoutGraphPermissionImpact.ps1
|
#Requires -Version 7.0 Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' <# .SYNOPSIS For each Microsoft Graph permission, the collectors that go empty without it. .DESCRIPTION AB#6765. The pre-flight used to end on one word. `$isCritical` was a hardcoded list of four check names, so a denied permission outside those four downgraded the run to a `Warn` and still printed **"READY — Full ARM + Entra ID scan supported"** — over a scan that would render the affected worksheet empty. One word cannot stand in for 236 outcomes, and a list of what you will actually get cannot lie the way a verdict word can. This function derives that list instead of restating it. It joins two things that already exist and are already maintained for their own reasons: - `Get-ScoutEntraQueryCatalog` — every Graph query Scout issues, with its permission and the synthetic TYPE it stamps on the rows. - the collector manifests — each declares the `ResourceTypes` it reads. So criticality is a derived property: a permission is critical when a collector consumes the data it unlocks, and adding a collector changes the answer without anyone editing a list. Two of Scout's Graph queries turn out to have no consumer at all, which the table reports rather than hides — a permission asked for and not needed belongs in the output. .PARAMETER CollectorRoot The collector manifest tree. Defaults to `manifests/collectors` beside the module. .OUTPUTS One row per permission: Permission, Queries, Collectors, CollectorCount, IsConsumed. .NOTES Tracks ADO AB#6765 (Feature AB#6743, Epic AB#6731). #> function Get-ScoutGraphPermissionImpact { [CmdletBinding()] [OutputType([PSCustomObject])] param( [string] $CollectorRoot ) if ([string]::IsNullOrWhiteSpace($CollectorRoot)) { $moduleRoot = Split-Path -Parent $PSScriptRoot $CollectorRoot = Join-Path $moduleRoot 'manifests' 'collectors' } # Same standalone-dot-source contract as the pre-flight that calls this. if (-not (Get-Command Get-ScoutEntraQueryCatalog -ErrorAction SilentlyContinue)) { . (Join-Path $PSScriptRoot 'collect/Get-ScoutEntraQueryCatalog.ps1') } $catalog = @(Get-ScoutEntraQueryCatalog) # type -> collectors that read it. Built once from the manifests rather than asked per # permission, because the tree is ~240 files and this runs inside an interactive pre-flight. $byType = @{} if (Test-Path -LiteralPath $CollectorRoot) { foreach ($file in Get-ChildItem -LiteralPath $CollectorRoot -Filter '*.psd1' -Recurse -File) { $definition = $null try { $definition = Import-PowerShellDataFile -LiteralPath $file.FullName } catch { # A manifest that will not parse is a real problem, but it is not this # function's problem to fail on -- the collector runtime reports it. Write-Verbose "Get-ScoutGraphPermissionImpact: could not read '$($file.FullName)': $($_.Exception.Message)" continue } if ($null -eq $definition -or -not $definition.ContainsKey('ResourceTypes')) { continue } $collectorName = '{0}/{1}' -f $file.Directory.Name, $file.BaseName foreach ($type in @($definition.ResourceTypes)) { if ([string]::IsNullOrWhiteSpace($type)) { continue } $key = ([string] $type).ToLowerInvariant() if (-not $byType.ContainsKey($key)) { $byType[$key] = [System.Collections.Generic.List[string]]::new() } if (-not $byType[$key].Contains($collectorName)) { $byType[$key].Add($collectorName) } } } } else { Write-Verbose "Get-ScoutGraphPermissionImpact: no collector tree at '$CollectorRoot' — every permission will report zero consumers." } $catalog | Group-Object -Property { $_.Permission } | Sort-Object Name | ForEach-Object { $queries = @($_.Group | ForEach-Object { $_.Name } | Sort-Object) $collectors = @( $_.Group | ForEach-Object { $key = ([string] $_.Type).ToLowerInvariant() if ($byType.ContainsKey($key)) { $byType[$key] } } ) | Sort-Object -Unique [PSCustomObject]@{ Permission = $_.Name Queries = $queries Collectors = @($collectors) CollectorCount = @($collectors).Count # The derived criticality. Nothing hardcoded, nothing to keep in sync. IsConsumed = (@($collectors).Count -gt 0) } } } |