Private/Show-AACSecurityPostureView.ps1

function Show-AACSecurityPostureView {
    <#
    .SYNOPSIS
        Renders Get-AACSecurityPosture's result as a Spectre.Console view.
    .DESCRIPTION
        The scope, tiles (secure score, High and Medium recommendations,
        active alerts, plans off, failed compliance controls), then - for the
        sections read:
          Subscriptions secure score in colour, recommendations by
                            severity, alerts, plans on and off
          Recommendations one table row per recommendation, most severe
                            first: its control and category, and every
                            resource it is on (up to -MaxResource)
          Alerts severity, alert, resource, age, intent
          Compliance each standard's pass rate as a bar, then its
                            failed controls with the checks failing them
          Policy Azure Policy assignments, least compliant first,
                            and the policies with the most non-compliant
                            resources
          Plans the Defender plans that are off
        Scores: Good (70%+) green, Fair (40-69%) amber, Poor red; severities
        High red, Medium amber, Low blue. Wrap the call in
        Invoke-AACPagedOutput to page it.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [hashtable] $Posture,

        [System.Collections.IDictionary] $Scope,

        [int] $MaxResource = 50
    )

    $escape = { param($Text) [Spectre.Console.Markup]::Escape([string]$Text) }
    $glyph = Get-AACGlyph
    $stats = $Posture.Stats
    $sections = @($Posture.Sections)
    $ratingColor = @{ Good = 'green3'; Fair = 'orange1'; Poor = 'red1' }
    $badge = {
        param([string] $Severity)
        switch ($Severity) {
            'High' { '[bold white on red3] HIGH [/]' }
            'Medium' { '[bold black on orange1] MEDIUM [/]' }
            'Low' { '[black on deepskyblue1] LOW [/]' }
            default { "[grey70]$(& $escape $Severity)[/]" }
        }
    }
    $newTable = {
        param([string] $Title, [string] $Color, [object[]] $Columns)
        $table = [Spectre.Console.Table]::new()
        $table.Border = [Spectre.Console.TableBorder]::Rounded
        $table.BorderStyle = [Spectre.Console.Style]::Parse($Color)
        $table.Expand = $true
        $table.Title = [Spectre.Console.TableTitle]::new($Title)
        foreach ($column in $Columns) {
            $spectre = [Spectre.Console.TableColumn]::new("[grey62]$($column[0])[/]")
            if ($column[1]) { $spectre.Width = $column[1] }
            if ($column[2]) { $spectre.Alignment = [Spectre.Console.Justify]::Right; $spectre.NoWrap = $true }
            $table.AddColumn($spectre) | Out-Null
        }
        $table
    }
    $addRow = { param($Table, [string[]] $Cells) [Spectre.Console.TableExtensions]::AddRow($Table, [Spectre.Console.Rendering.IRenderable[]]@($Cells | ForEach-Object { [Spectre.Console.Markup]::new($_) })) | Out-Null }
    $count = { param([int] $Value, [string] $Color) if ($Value -eq 0) { '[grey42]0[/]' } else { "[$Color]$('{0:N0}' -f $Value)[/]" } }

    $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) ")[/]"
    [Spectre.Console.AnsiConsole]::WriteLine()

    $tiles = @(
        @{ Value = $(if ($null -ne $stats.SecureScore) { "$($stats.SecureScore)%" } else { '-' }); Caption = $(if ($stats.Rating) { "secure score ($($stats.Rating.ToLowerInvariant()))" } else { 'secure score' }); Color = $(if ($stats.Rating) { $ratingColor[$stats.Rating] } else { 'grey50' }) }
        if ($sections -contains 'Recommendations') {
            @{ Value = '{0:N0}' -f $stats.High; Caption = 'high recommendations'; Color = $(if ($stats.High) { 'red1' } else { 'green3' }) }
            @{ Value = '{0:N0}' -f $stats.Medium; Caption = 'medium recommendations'; Color = $(if ($stats.Medium) { 'orange1' } else { 'green3' }) }
        }
        if ($sections -contains 'Alerts') { @{ Value = '{0:N0}' -f $stats.Alerts; Caption = 'active alerts'; Color = $(if ($stats.HighAlerts) { 'red1' } elseif ($stats.Alerts) { 'orange1' } else { 'green3' }) } }
        if ($sections -contains 'Plans') { @{ Value = "$($stats.PlansOn) / $($stats.PlansOn + $stats.PlansOff)"; Caption = 'Defender plans on'; Color = $(if ($stats.PlansOff) { 'orange1' } else { 'green3' }) } }
        if ($sections -contains 'Compliance') { @{ Value = '{0:N0}' -f $stats.FailedControls; Caption = 'failed compliance controls'; Color = $(if ($stats.FailedControls) { 'red1' } else { 'green3' }) } }
        if ($sections -contains 'Policy') { @{ Value = $(if ($null -ne $stats.PolicyCompliance) { "$($stats.PolicyCompliance)%" } else { '-' }); Caption = 'Azure Policy compliance'; Color = $(if ($null -eq $stats.PolicyCompliance) { 'grey50' } elseif ($stats.PolicyCompliance -ge 90) { 'green3' } elseif ($stats.PolicyCompliance -ge 70) { 'orange1' } else { 'red1' }) } }
    )
    Show-AACTileRow -Tile $tiles
    [Spectre.Console.AnsiConsole]::WriteLine()
    foreach ($notice in @($Posture.Notice | Where-Object { $_ })) { Write-AACMarkup "[deepskyblue1]i[/] [grey70]$(& $escape $notice)[/]" }
    if (@($Posture.Notice).Count) { [Spectre.Console.AnsiConsole]::WriteLine() }

    # --- Subscriptions ---------------------------------------------------------------------------------
    if ($Posture.Subscriptions.Count) {
        $table = & $newTable "[bold]$($glyph.Bullet) Subscriptions[/]" 'grey42' @(@('Subscription', 0, $false), @('Secure score', 0, $true), @('Points', 0, $true), @('High', 0, $true), @('Medium', 0, $true), @('Low', 0, $true), @('Alerts', 0, $true), @('Plans on / off', 0, $true), @('Failed controls', 0, $true))
        foreach ($item in $Posture.Subscriptions) {
            & $addRow $table @(
                "[bold]$(& $escape $item.SubscriptionName)[/]"
                $(if ($null -ne $item.SecureScore) { "[bold $($ratingColor[$item.Rating])]$($item.SecureScore)%[/]" } else { '[grey50]no score[/]' })
                "[grey58]$(& $escape $item.Points)[/]"
                (& $count $item.High 'red1'); (& $count $item.Medium 'orange1'); (& $count $item.Low 'deepskyblue1')
                (& $count $item.Alerts 'red1')
                "[green3]$($item.PlansOn)[/] / $(if ($item.PlansOff) { "[orange1]$($item.PlansOff)[/]" } else { '[grey42]0[/]' })"
                (& $count $item.FailedControls 'red1')
            )
        }
        [Spectre.Console.AnsiConsole]::Write($table)
        [Spectre.Console.AnsiConsole]::WriteLine()
    }

    # --- Secure score controls with the most to gain -------------------------------------------------------
    $gain = @($Posture.Controls | Where-Object { $_.PotentialIncrease -gt 0 } | Sort-Object -Property @{ Expression = 'PotentialIncrease'; Descending = $true }, Control)
    if ($gain.Count) {
        $table = & $newTable "[bold]$($glyph.Bullet) Secure score controls with the most to gain[/]" 'grey42' @(@('Control', 0, $false), @('Subscription', 0, $false), @('Score', 0, $true), @('Unhealthy resources', 0, $true), @('Potential increase', 0, $true))
        foreach ($control in $gain) {
            $rating = if ($null -eq $control.Score) { '' } elseif ($control.Score -ge 70) { 'Good' } elseif ($control.Score -ge 40) { 'Fair' } else { 'Poor' }
            & $addRow $table @(
                "[white]$(& $escape $control.Control)[/]"
                "[grey70]$(& $escape $control.SubscriptionName)[/]"
                $(if ($rating) { "[$($ratingColor[$rating])]$($control.Score)%[/]" } else { '' })
                (& $count $control.UnhealthyResources 'grey85')
                "[bold $(if ($control.PotentialIncrease -ge 5) { 'red1' } elseif ($control.PotentialIncrease -ge 2) { 'orange1' } else { 'deepskyblue1' })]+$($control.PotentialIncrease)%[/]"
            )
        }
        [Spectre.Console.AnsiConsole]::Write($table)
        [Spectre.Console.AnsiConsole]::WriteLine()
    }

    # --- Recommendations, grouped ---------------------------------------------------------------------------
    if ($sections -contains 'Recommendations') {
        $groups = @($Posture.Recommendations | Group-Object -Property Recommendation | Sort-Object -Property @(
                @{ Expression = { @{ High = 0; Medium = 1; Low = 2 }[[string]$_.Group[0].Severity] } }
                @{ Expression = 'Count'; Descending = $true }
                'Name'))
        if ($groups.Count) {
            $table = & $newTable "[bold]$($glyph.Bullet) Recommendations[/] [grey58]$($glyph.Dot) $($groups.Count) on $($stats.Resources) resource(s), most severe first[/]" 'indianred1' @(@('Severity', 10, $false), @('Recommendation', 0, $false), @('Resources', 9, $true), @('Where', 0, $false))
            foreach ($group in $groups) {
                $first = $group.Group[0]
                $what = "[bold]$(& $escape $first.Recommendation)[/]"
                $context = @($first.Control, $first.Category) | Where-Object { $_ }
                if ($context) { $what += "`n[grey58]$(& $escape ($context -join " $($glyph.Dot) "))[/]" }
                $shown = @($group.Group | Select-Object -First $MaxResource)
                $where = @($shown | ForEach-Object { "[white]$(& $escape $_.Resource)[/] [grey50]$(& $escape (@($_.ResourceGroup, $_.SubscriptionName) | Where-Object { $_ }) -join ' / ')[/]" })
                if ($group.Count -gt $shown.Count) { $where += "[grey50]... and $($group.Count - $shown.Count) more: -PassThru, -CsvPath or -HtmlPath lists them all[/]" }
                & $addRow $table @((& $badge $first.Severity), $what, "[bold]$($group.Count)[/]", ($where -join "`n"))
            }
            [Spectre.Console.AnsiConsole]::Write($table)
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
        else {
            Show-AACPanel -Content '[bold green3]No unhealthy recommendations[/] [grey58]in this scope.[/]' -BorderColor 'green3' -AllowMarkup
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }

    # --- Alerts ----------------------------------------------------------------------------------------------
    if ($sections -contains 'Alerts') {
        if ($Posture.Alerts.Count) {
            $table = & $newTable "[bold]$($glyph.Bullet) Active security alerts[/] [grey58]$($glyph.Dot) $($Posture.Alerts.Count)[/]" 'red3' @(@('Severity', 10, $false), @('Alert', 0, $false), @('Resource', 0, $false), @('Age', 7, $true), @('Intent', 0, $false))
            foreach ($alert in $Posture.Alerts) {
                & $addRow $table @(
                    (& $badge $alert.Severity)
                    "[bold]$(& $escape $alert.Alert)[/]$(if ($alert.Description) { "`n[grey58]$(& $escape $alert.Description)[/]" })"
                    "[white]$(& $escape $alert.Resource)[/]`n[grey50]$(& $escape (@($alert.ResourceGroup, $alert.SubscriptionName) | Where-Object { $_ }) -join ' / ')[/]"
                    $(if ($null -ne $alert.AgeDays) { "$($alert.AgeDays)d" } else { '' })
                    "[grey70]$(& $escape $alert.Intent)[/]"
                )
            }
            [Spectre.Console.AnsiConsole]::Write($table)
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
        else {
            Write-AACMarkup "[green3]$($glyph.Bullet)[/] [grey70]No active security alerts.[/]"
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }

    # --- Regulatory compliance ----------------------------------------------------------------------------------
    if ($sections -contains 'Compliance' -and $Posture.Standards.Count) {
        $bars = @($Posture.Standards | Where-Object { $null -ne $_.PassRate } | ForEach-Object {
                @{ Label = "$($_.Standard) ($($_.SubscriptionName))"; Value = $_.PassRate; Color = $(if ($_.PassRate -ge 70) { 'green3' } elseif ($_.PassRate -ge 40) { 'orange1' } else { 'red1' }) }
            })
        if ($bars.Count) {
            Show-AACBarChart -Item $bars -Title 'Regulatory compliance: controls passed (%)'
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
        $failed = @($Posture.ComplianceControls | Where-Object State -EQ 'Failed')
        foreach ($standard in @($failed | Group-Object -Property Standard)) {
            $table = & $newTable "[bold]$($glyph.Bullet) $(& $escape $standard.Name)[/] [grey58]$($glyph.Dot) $($standard.Count) failed control(s)[/]" 'orange1' @(@('Control', 12, $false), @('Description', 0, $false), @('Subscription', 0, $false), @('Failed', 7, $true), @('Failing checks', 0, $false))
            foreach ($control in $standard.Group) {
                & $addRow $table @("[bold]$(& $escape $control.Control)[/]", "[white]$(& $escape $control.Description)[/]", "[grey70]$(& $escape $control.SubscriptionName)[/]", "[red1]$($control.FailedAssessments)[/]", "[grey58]$(& $escape $control.FailingChecks)[/]")
            }
            [Spectre.Console.AnsiConsole]::Write($table)
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }

    # --- Azure Policy ------------------------------------------------------------------------------------------------
    if ($sections -contains 'Policy' -and $Posture.PolicyAssignments.Count) {
        $table = & $newTable "[bold]$($glyph.Bullet) Azure Policy assignments[/] [grey58]$($glyph.Dot) least compliant first[/]" 'mediumpurple2' @(@('Assignment', 0, $false), @('Assigned at', 0, $false), @('Compliance', 0, $true), @('Non-compliant', 0, $true), @('Compliant', 0, $true), @('Exempt', 0, $true))
        foreach ($assignment in $Posture.PolicyAssignments) {
            $rate = $assignment.ComplianceRate
            & $addRow $table @(
                "[bold]$(& $escape $assignment.Assignment)[/]$(if ($assignment.Enforcement -eq 'DoNotEnforce') { ' [grey50](not enforced)[/]' })"
                "[grey70]$(& $escape $assignment.Scope)[/]"
                $(if ($null -eq $rate) { '[grey50]-[/]' } else { "[bold $(if ($rate -ge 90) { 'green3' } elseif ($rate -ge 70) { 'orange1' } else { 'red1' })]$rate%[/]" })
                (& $count $assignment.NonCompliant 'red1'); (& $count $assignment.Compliant 'green3'); (& $count $assignment.Exempt 'grey70')
            )
        }
        [Spectre.Console.AnsiConsole]::Write($table)
        [Spectre.Console.AnsiConsole]::WriteLine()
        $policies = @($Posture.Findings | Where-Object Section -EQ 'Policy' | Group-Object -Property Title | Sort-Object -Property @{ Expression = 'Count'; Descending = $true }, Name)
        if ($policies.Count) {
            $table = & $newTable "[bold]$($glyph.Bullet) Non-compliant resources by policy[/]" 'mediumpurple2' @(@('Policy', 0, $false), @('Resources', 9, $true), @('Where', 0, $false))
            foreach ($policy in $policies) {
                $shown = @($policy.Group | Select-Object -First $MaxResource)
                $where = @($shown | ForEach-Object { "[white]$(& $escape $_.Resource)[/] [grey50]$(& $escape (@($_.ResourceGroup, $_.SubscriptionName) | Where-Object { $_ }) -join ' / ')[/]" })
                if ($policy.Count -gt $shown.Count) { $where += "[grey50]... and $($policy.Count - $shown.Count) more: -PassThru, -CsvPath or -HtmlPath lists them all[/]" }
                & $addRow $table @("[bold]$(& $escape $policy.Name)[/]`n[grey58]$(& $escape $policy.Group[0].Category)[/]", "[bold]$($policy.Count)[/]", ($where -join "`n"))
            }
            [Spectre.Console.AnsiConsole]::Write($table)
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }

    # --- Defender plans that are off --------------------------------------------------------------------------------
    if ($sections -contains 'Plans') {
        $off = @($Posture.Plans | Where-Object { -not $_.Enabled })
        if ($off.Count) {
            Write-AACMarkup "[bold orange1]$($glyph.Bullet) Defender plans that are off[/] [grey58]$($glyph.Dot) no threat protection or alerts for these workloads[/]"
            foreach ($group in @($off | Group-Object -Property SubscriptionName)) {
                Write-AACMarkup " [white]$(& $escape $group.Name)[/] [grey70]$(& $escape (@($group.Group | ForEach-Object { $_.Plan }) -join ', '))[/]"
            }
            [Spectre.Console.AnsiConsole]::WriteLine()
        }
    }
    Write-AACMarkup '[grey42]Add -PassThru (or pipe the command) for the findings; -CsvPath, -PdfPath or -HtmlPath for a report with every finding and its portal link.[/]'
}