Private/Show-AACM365AssessmentView.ps1

function Show-AACM365AssessmentView {
    <#
    .SYNOPSIS
        Renders Invoke-AACM365Assessment's result as a Spectre.Console view.
    .DESCRIPTION
        The scope; tiles (Secure Score, MFA registration, Conditional Access,
        Global Administrators, sharing, devices); the key settings; the
        Conditional Access policies; the privileged role assignments; what
        Graph refused, with the permission it needs; 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'; Info = 'grey62' }
    $statusColor = @{ Good = 'green3'; Warning = 'red1'; Review = 'orange1'; Info = 'grey70'; Unknown = 'grey50' }
    $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() }
    $percentColor = { param($Value) if ($null -eq $Value) { 'grey50' } elseif ($Value -ge 70) { 'green3' } elseif ($Value -ge 40) { 'orange1' } else { 'red1' } }

    $facts = [System.Collections.Generic.List[string]]::new()
    if ($stats.Tenant) { $facts.Add("[white]$(& $escape $stats.Tenant)[/]") }
    if ($script:AACSession) { $facts.Add((& $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-AACStatusLine Warning $line }
    [Spectre.Console.AnsiConsole]::WriteLine()

    Show-AACTileRow -Tile @(
        @{ Value = $(if ($null -ne $stats.SecureScore) { "$($stats.SecureScore)%" } else { '-' }); Caption = 'Secure Score'; Color = (& $percentColor $stats.SecureScore) }
        @{ Value = $(if ($null -ne $stats.MfaRegistered) { "$($stats.MfaRegistered)%" } else { '-' }); Caption = "MFA registered ($($stats.AdminsWithoutMfa) admins without)"; Color = $(if ($stats.AdminsWithoutMfa) { 'red1' } else { & $percentColor $stats.MfaRegistered }) }
        @{ Value = "$($stats.ConditionalAccessOn)/$($stats.ConditionalAccess)"; Caption = 'Conditional Access on'; Color = 'deepskyblue1' }
        @{ Value = '{0:N0}' -f $stats.GlobalAdmins; Caption = 'Global Administrators'; Color = $(if ($stats.GlobalAdmins -gt 5 -or $stats.GlobalAdmins -lt 2) { 'orange1' } else { 'green3' }) }
        @{ Value = '{0:N0}' -f $stats.ManagedDevices; Caption = "devices ($($stats.NonCompliant) non-compliant)"; Color = $(if ($stats.NonCompliant) { 'orange1' } else { 'green3' }) }
        @{ Value = '{0:N0}' -f ($stats.Critical + $stats.High); Caption = "critical and high findings"; Color = $(if ($stats.Critical + $stats.High) { 'red1' } else { 'green3' }) }
    )
    [Spectre.Console.AnsiConsole]::WriteLine()

    $keySettings = @($Assessment.Settings | Where-Object { $_.Status -in 'Warning', 'Review', 'Good' -and $_.Area -ne 'Tenant' })
    if ($keySettings.Count) {
        $table = & $newTable (& $title 'Security settings' 'warnings first') @('Area', 'Setting', 'Value')
        foreach ($s in $keySettings | Sort-Object -Property @{ Expression = { @{ Warning = 0; Review = 1; Good = 2 }[$_.Status] } }, Area | Select-Object -First 25) { & $addRow $table @("[grey70]$(& $escape $s.Area)[/]", (& $escape $s.Setting), "[$($statusColor[$s.Status])]$(& $escape $s.Value)[/]") }
        & $write $table
    }

    $ca = @($Assessment.ConditionalAccess)
    if ($ca.Count) {
        $table = & $newTable (& $title 'Conditional Access' "$($ca.Count) policies") @('Policy', 'State', 'Users', 'Applications', 'Grant')
        foreach ($p in $ca | Select-Object -First $Top) { & $addRow $table @("[white]$(& $escape $p.Policy)[/]", $(switch ($p.State) { 'On' { '[green3]On[/]' } 'Report-only' { '[orange1]Report-only[/]' } default { '[grey50]Off[/]' } }), (& $escape $p.Users), (& $escape $p.Applications), (& $escape $p.Grant)) }
        & $write $table
    }

    $privileged = @($Assessment.RoleAssignments | Where-Object Privileged -EQ 'Yes')
    if ($privileged.Count) {
        $table = & $newTable (& $title 'Privileged role assignments' "$(@($privileged.PrincipalId | Select-Object -Unique).Count) accounts") @('Role', 'Principal', 'Assignment', 'MFA')
        foreach ($r in $privileged | Select-Object -First $Top) { & $addRow $table @((& $escape $r.Role), "[white]$(& $escape $(if ($r.UserPrincipalName) { $r.UserPrincipalName } else { $r.Principal }))[/]", $(if ($r.Assignment -eq 'Active') { '[orange1]Active[/]' } else { '[green3]Eligible[/]' }), $(switch ($r.MfaRegistered) { 'Yes' { '[green3]Yes[/]' } 'No' { '[red1]No[/]' } default { '[grey50]-[/]' } })) }
        & $write $table
    }

    $refused = @($Assessment.Permissions | Where-Object Status -EQ 'Not read')
    if ($refused.Count) {
        $table = & $newTable (& $title 'Not read' 'Graph refused these - grant the permission (admin consent) to the app you sign in with') @('Data', 'Permission', 'Why')
        foreach ($p in $refused) { & $addRow $table @((& $escape $p.Data), "[orange1]$(& $escape $p.Permission)[/]", "[grey62]$(& $escape ($p.Reason -replace '\s+', ' '))[/]") }
        & $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)[/]"
            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 setting, policy, role, user, device and Secure Score control, with row details); -CsvPath a CSV per table; -PassThru (or a pipe) returns the object.[/]'
}