Private/Show-AACDefenderAssessmentView.ps1

function Show-AACDefenderAssessmentView {
    <#
    .SYNOPSIS
        Renders Invoke-AACDefenderAssessment's result as a Spectre.Console view.
    .DESCRIPTION
        The scope; tiles (secure score, recommendations, attack paths, active
        alerts, vulnerabilities, plans, failed compliance controls); secure
        score by subscription; the subscriptions; the top recommendations by
        unhealthy resources; the attack paths; the active alerts; the plans
        that are off; and the Critical, 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 = 15
    )

    $escape = { param($Text) [Spectre.Console.Markup]::Escape([string]$Text) }
    $glyph = Get-AACGlyph
    $stats = $Assessment.Stats
    $color = @{ Critical = 'red1'; High = 'red1'; Medium = 'orange1'; Low = 'deepskyblue1'; Informational = 'grey62' }
    $sev = { param([string] $Severity) if ($Severity) { "[$(if ($color.Contains($Severity)) { $color[$Severity] } else { 'grey62' })]$(& $escape $Severity)[/]" } else { '[grey50]-[/]' } }
    $scoreText = { param($Value) if ($null -eq $Value) { '[grey50]-[/]' } else { "[$(if ($Value -ge 70) { 'green3' } elseif ($Value -ge 40) { 'orange1' } else { 'red1' })]$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 }
    $title = { param([string] $Text, [string] $Note) "[bold]$($glyph.Bullet) $Text[/]$(if ($Note) { " [grey58]$($glyph.Dot) $Note[/]" })" }
    $write = { param($Table) [Spectre.Console.AnsiConsole]::Write($Table); [Spectre.Console.AnsiConsole]::WriteLine() }

    $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.SecureScore) { "$($stats.SecureScore)%" } else { '-' }); Caption = 'secure score'; Color = $(if ($null -eq $stats.SecureScore) { 'grey50' } elseif ($stats.SecureScore -ge 70) { 'green3' } elseif ($stats.SecureScore -ge 40) { 'orange1' } else { 'red1' }) }
        @{ Value = '{0:N0}' -f $stats.Recommendations; Caption = "recommendations ($($stats.HighRecommendations) High)"; Color = $(if ($stats.HighRecommendations) { 'red1' } else { 'orange1' }) }
        @{ Value = '{0:N0}' -f $stats.AttackPaths; Caption = "attack paths ($($stats.CriticalAttackPaths) Critical/High)"; Color = $(if ($stats.CriticalAttackPaths) { 'red1' } elseif ($stats.AttackPaths) { 'orange1' } else { 'green3' }) }
        @{ Value = '{0:N0}' -f $stats.ActiveAlerts; Caption = "active alerts ($($stats.HighAlerts) High)"; Color = $(if ($stats.HighAlerts) { 'red1' } elseif ($stats.ActiveAlerts) { 'orange1' } else { 'green3' }) }
        @{ Value = '{0:N0}' -f $stats.Vulnerabilities; Caption = "vulnerabilities ($($stats.HighVulnerabilities) High+)"; Color = $(if ($stats.HighVulnerabilities) { 'red1' } elseif ($stats.Vulnerabilities) { 'orange1' } else { 'green3' }) }
        @{ Value = "$($stats.PlansOn)/$($stats.PlansOn + $stats.PlansOff)"; Caption = 'Defender plans on'; Color = $(if ($stats.PlansOff) { 'orange1' } else { 'green3' }) }
        @{ Value = '{0:N0}' -f $stats.FailedControls; Caption = 'failed compliance controls'; Color = $(if ($stats.FailedControls) { 'orange1' } else { 'green3' }) }
    )
    [Spectre.Console.AnsiConsole]::WriteLine()

    $subs = @($Assessment.Subscriptions)
    if ($subs.Count) {
        $table = & $newTable (& $title 'Subscriptions' 'lowest secure score first') @('Subscription', 'Score', 'Plans on', 'Unhealthy', 'High', 'Attack paths', 'Alerts', 'Findings') @('Plans on', 'Unhealthy', 'High', 'Attack paths', 'Alerts', 'Findings')
        foreach ($s in $subs | Select-Object -First $Top) { & $addRow $table @("[bold]$(& $escape $s.Subscription)[/]", (& $scoreText $s.SecureScore), "$($s.PlansOn)/$($s.Plans)", "$($s.UnhealthyResources)", $(if ($s.HighRecommendations) { "[red1]$($s.HighRecommendations)[/]" } else { '0' }), "$($s.AttackPaths)", "$($s.ActiveAlerts)", "$($s.Findings)") }
        & $write $table
    }

    $recs = @($Assessment.Recommendations | Where-Object UnhealthyResources)
    if ($recs.Count) {
        $table = & $newTable (& $title 'Recommendations' "highest risk first$(if ($recs.Count -gt $Top) { ", $Top of $($recs.Count)" })") @('Recommendation', 'Severity', 'Risk', 'Unhealthy', 'Healthy', 'Control') @('Unhealthy', 'Healthy')
        foreach ($r in $recs | Select-Object -First $Top) { & $addRow $table @("[white]$(& $escape $r.Recommendation)[/]", (& $sev $r.Severity), (& $sev $r.RiskLevel), "[orange1]$($r.UnhealthyResources)[/]", "$($r.HealthyResources)", "[grey70]$(& $escape $r.Control)[/]") }
        & $write $table
    }

    $paths = @($Assessment.AttackPaths)
    if ($paths.Count) {
        $table = & $newTable (& $title 'Attack paths' "$($paths.Count) in all") @('Attack path', 'Risk', 'Path')
        foreach ($p in $paths | Select-Object -First $Top) { & $addRow $table @("[white]$(& $escape $p.AttackPath)[/]", (& $sev $p.RiskLevel), "[grey85]$(& $escape ($p.Path -replace ' → ', " $($glyph.Arrow) "))[/]") }
        & $write $table
    }

    $active = @($Assessment.Alerts | Where-Object Active -EQ 'Yes')
    if ($active.Count) {
        $table = & $newTable (& $title 'Active alerts' "$($active.Count) active, most severe first") @('Alert', 'Severity', 'Resource', 'Tactics', 'Age') @('Age')
        foreach ($al in $active | Select-Object -First $Top) { & $addRow $table @("[white]$(& $escape $al.Alert)[/]", (& $sev $al.Severity), (& $escape $al.Resource), "[grey70]$(& $escape $al.Tactics)[/]", "$($al.AgeDays)d") }
        & $write $table
    }

    $off = @($Assessment.Plans | Where-Object State -EQ 'Off')
    if ($off.Count) {
        $table = & $newTable (& $title 'Defender plans off' 'with the resources each would protect') @('Subscription', 'Plan', 'Resources') @('Resources')
        foreach ($p in $off | Sort-Object Resources -Descending | Select-Object -First $Top) { & $addRow $table @((& $escape $p.SubscriptionName), "[white]$(& $escape $p.Plan)[/]", $(if ($p.Resources) { "[orange1]$($p.Resources)[/]" } else { '[grey50]0[/]' })) }
        & $write $table
    }

    $serious = @($Assessment.Findings | Where-Object { $_.Severity -in 'Critical', 'High', 'Medium' })
    if ($serious.Count) {
        Write-AACMarkup (& $title 'Findings' "Critical, High and Medium: $($serious.Count) ($($stats.Low) low)")
        foreach ($f in $serious | Select-Object -First 30) {
            Write-AACMarkup " [$($color[$f.Severity])]$($f.Severity.ToUpperInvariant().PadRight(8))[/] [white]$(& $escape $f.Finding)[/] [grey62]$(& $escape $f.Item)[/] [grey42]$(& $escape $f.Area)$(if ($f.SubscriptionName) { " $($glyph.Dot) $(& $escape $f.SubscriptionName)" })[/]"
            Write-AACMarkup " [grey85]$(& $escape $f.Detail)[/]"
            if ($f.Recommendation) { Write-AACMarkup " [grey50]$($glyph.Arrow) $(& $escape $f.Recommendation)[/]" }
        }
        if ($serious.Count -gt 30) { Write-AACMarkup " [grey50]... and $($serious.Count - 30) more: -HtmlPath has them all.[/]" }
        [Spectre.Console.AnsiConsole]::WriteLine()
    }
    Write-AACMarkup '[grey42]-HtmlPath writes the tabbed report (every recommendation, resource, alert, vulnerability, compliance control and setting, with row details); -CsvPath a CSV per table; -PassThru (or a pipe) returns the object.[/]'
}