Private/Show-AACPolicyAssessmentView.ps1
|
function Show-AACPolicyAssessmentView { <# .SYNOPSIS Renders Invoke-AACPolicyAssessment's result as a Spectre.Console view. .DESCRIPTION The scope; tiles (overall compliance, assignments, non-compliant resources, exemptions to look at, findings by severity); compliance by subscription and by category as bar charts; the assignments, least compliant first; the exemptions expired or expiring; and the High and Medium findings with what to do. Wrap the call in Invoke-AACPagedOutput to page it. #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable] $Assessment, [System.Collections.IDictionary] $Scope, [int] $Top = 25 ) $escape = { param($Text) [Spectre.Console.Markup]::Escape([string]$Text) } $glyph = Get-AACGlyph $stats = $Assessment.Stats $color = @{ High = 'red1'; Medium = 'orange1'; Low = 'deepskyblue1'; Info = 'grey62' } $rateColor = @{ Good = 'green3'; Warning = 'orange1'; Poor = 'red1'; 'No data' = 'grey50' } $percent = { param($Value, [string] $Rating) if ($null -eq $Value) { '[grey50]-[/]' } else { "[$($rateColor[$Rating])]$Value%[/]" } } $newTable = { param([string] $TitleText, [string[]] $Headers, [string[]] $Right = @()) $t = [Spectre.Console.Table]::new(); $t.Border = [Spectre.Console.TableBorder]::Rounded; $t.BorderStyle = [Spectre.Console.Style]::Parse('deepskyblue3_1'); $t.Expand = $true if ($TitleText) { $t.Title = [Spectre.Console.TableTitle]::new($TitleText) } foreach ($header in $Headers) { $column = [Spectre.Console.TableColumn]::new("[grey62]$header[/]"); if ($header -in $Right) { $column.Alignment = [Spectre.Console.Justify]::Right }; $t.AddColumn($column) | Out-Null } $t } $addRow = { param($Table, [string[]] $Cells) [Spectre.Console.TableExtensions]::AddRow($Table, [Spectre.Console.Rendering.IRenderable[]]@($Cells | ForEach-Object { [Spectre.Console.Markup]::new($_) })) | Out-Null } $facts = [System.Collections.Generic.List[string]]::new() if ($script:AACSession) { $facts.Add("[white]$(& $escape $script:AACSession.Account)[/]") } if ($Scope) { foreach ($key in $Scope.Keys) { $facts.Add("$(& $escape $key): $(& $escape $Scope[$key])") } } $facts.Add((Get-Date).ToString('d MMM yyyy HH:mm')) Write-AACMarkup "[grey58]$($facts -join " $($glyph.Dot) ")[/]" foreach ($line in @($Assessment['Notices'])) { Write-AACMarkup "[orange1]$(& $escape $line)[/]" } [Spectre.Console.AnsiConsole]::WriteLine() Show-AACTileRow -Tile @( @{ Value = $(if ($null -ne $stats.CompliancePercent) { "$($stats.CompliancePercent)%" } else { '-' }); Caption = 'resources compliant'; Color = $rateColor[$stats.Rating] } @{ Value = '{0:N0}' -f $stats.Assignments; Caption = "assignments, $($stats.NotEnforced) not enforced"; Color = 'deepskyblue1' } @{ Value = '{0:N0}' -f $stats.NonCompliant; Caption = "non-compliant of $('{0:N0}' -f $stats.Resources)"; Color = $(if ($stats.NonCompliant) { 'orange1' } else { 'green3' }) } @{ Value = '{0:N0}' -f $stats.ExpiringExemptions; Caption = "exemptions expired or expiring ($($stats.Exemptions) in all)"; Color = $(if ($stats.ExpiringExemptions) { 'orange1' } else { 'green3' }) } @{ Value = '{0:N0}' -f $stats.High; Caption = 'high findings'; Color = $(if ($stats.High) { 'red1' } else { 'green3' }) } @{ Value = '{0:N0}' -f $stats.Medium; Caption = 'medium findings'; Color = $(if ($stats.Medium) { 'orange1' } else { 'green3' }) } ) [Spectre.Console.AnsiConsole]::WriteLine() $bars = @($Assessment.Subscriptions | Where-Object { $null -ne $_.CompliancePercent } | Select-Object -First 15 | ForEach-Object { @{ Label = $_.Subscription; Value = $_.CompliancePercent } }) if ($bars.Count) { Show-AACBarChart -Item $bars -Title 'Compliance by subscription (%), least compliant first'; [Spectre.Console.AnsiConsole]::WriteLine() } $bars = @($Assessment.Categories | Where-Object { $null -ne $_.CompliancePercent } | Select-Object -First 15 | ForEach-Object { @{ Label = $_.Category; Value = $_.CompliancePercent } }) if ($bars.Count) { Show-AACBarChart -Item $bars -Title 'Compliance by category (%)'; [Spectre.Console.AnsiConsole]::WriteLine() } $rows = @($Assessment.Assignments) if ($rows.Count) { $table = & $newTable "[bold]$($glyph.Bullet) Assignments[/] [grey58]$($glyph.Dot) least compliant first$(if ($rows.Count -gt $Top) { ", $Top of $($rows.Count)" })[/]" @('Assignment', 'Scope', 'Assigns', 'Enforcement', 'Compliance', 'Non-compliant', 'Exemptions') @('Non-compliant', 'Exemptions') foreach ($row in $rows | Select-Object -First $Top) { & $addRow $table @( "[bold]$(& $escape $row.Assignment)[/]" "$(& $escape $row.Scope)`n[grey50]$(& $escape $row.ScopeType)[/]" "$(& $escape $row.Definition)`n[grey50]$(& $escape $row.Kind)$(if ($row.Kind -eq 'Initiative') { ", $($row.Policies) $(if ($row.Policies -eq 1) { 'policy' } else { 'policies' })" }) $($glyph.Dot) $(& $escape $row.PolicyType)[/]" $(if ($row.Enforcement -eq 'DoNotEnforce') { '[orange1]DoNotEnforce[/]' } else { '[grey70]Default[/]' }) (& $percent $row.CompliancePercent $row.Rating) $(if ($row.NonCompliant) { "[orange1]$('{0:N0}' -f $row.NonCompliant)[/]" } else { '0' }) "$($row.Exemptions)" ) } [Spectre.Console.AnsiConsole]::Write($table) [Spectre.Console.AnsiConsole]::WriteLine() } $attention = @($Assessment.Exemptions | Where-Object { $_.Status -in 'Expired', 'Expiring' }) if ($attention.Count) { $table = & $newTable "[bold]$($glyph.Bullet) Exemptions to look at[/] [grey58]$($glyph.Dot) expired or expiring soon[/]" @('Exemption', 'Assignment', 'Scope', 'Category', 'Expires', 'Status') foreach ($row in $attention | Select-Object -First $Top) { & $addRow $table @("[white]$(& $escape $row.Exemption)[/]", (& $escape $row.Assignment), (& $escape $row.Scope), (& $escape $row.Category), "$(& $escape $row.ExpiresOn)`n[grey50]$(if ($row.DaysLeft -lt 0) { "$(-$row.DaysLeft) days ago" } else { "in $($row.DaysLeft) days" })[/]", $(if ($row.Status -eq 'Expired') { '[red1]Expired[/]' } else { '[orange1]Expiring[/]' })) } [Spectre.Console.AnsiConsole]::Write($table) [Spectre.Console.AnsiConsole]::WriteLine() } $serious = @($Assessment.Findings | Where-Object { $_.Severity -in 'High', 'Medium' }) if ($serious.Count) { Write-AACMarkup "[bold]$($glyph.Bullet) Findings[/] [grey58]$($glyph.Dot) High and Medium, $($serious.Count) in all ($($stats.Low) low, $($stats.Info) info)[/]" foreach ($f in $serious | Select-Object -First 40) { Write-AACMarkup " [$($color[$f.Severity])]$($f.Severity.ToUpperInvariant().PadRight(6))[/] [white]$(& $escape $f.Item)[/] [grey62]$(& $escape $f.Finding)[/] [grey42]$(& $escape $f.Area)$(if ($f.Scope) { " $($glyph.Dot) $(& $escape $f.Scope)" })[/]" Write-AACMarkup " [grey85]$(& $escape $f.Detail)[/]" if ($f.Recommendation) { Write-AACMarkup " [grey50]$($glyph.Arrow) $(& $escape $f.Recommendation)[/]" } } if ($serious.Count -gt 40) { Write-AACMarkup " [grey50]... and $($serious.Count - 40) more: -HtmlPath has them all.[/]" } [Spectre.Console.AnsiConsole]::WriteLine() } elseif (@($Assessment.Findings).Count) { Write-AACMarkup "[green3]$($glyph.Tick)[/] [grey70]No High or Medium findings; $($stats.Low) low and $($stats.Info) info - -HtmlPath has them.[/]" [Spectre.Console.AnsiConsole]::WriteLine() } Write-AACMarkup '[grey42]-HtmlPath has the management group tree, compliance by policy and every definition; -PassThru (or a pipe) returns the objects; -CsvPath writes a CSV per table.[/]' } |