function Write-PersonaSummary { <# .SYNOPSIS Renders the rule-match table, outcome totals, and reconciliation result (FR-019, FR-020, FR-021). .DESCRIPTION Emitted every summaryInterval users and once at completion. Every business rule appears, including disabled rules and rules with zero matches. A rule that never fired and a rule that is not in the configuration look identical if zero-match rules are omitted, and the difference is exactly what an operator investigating "why did nobody get classified as Tier0" needs to see. Reconciliation is displayed on every summary, not only when it fails. A check that is only visible when broken gives an operator no reason to believe it ran at all. .PARAMETER Counters The run counter set. .PARAMETER SummaryType Interim or Final. Final is emitted regardless of interval, including when the interval is 0 (FR-020). .PARAMETER Mode Preview or Enforce, shown in the header so a screenshot of a summary is self-describing. .PARAMETER StartedUtc Run start timestamp. When supplied, the header shows elapsed wall-clock time since the run began. Optional so callers that only care about counts are not forced to thread a clock through. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object] $Counters, [ValidateSet('Interim', 'Final')] [string] $SummaryType = 'Interim', [ValidateSet('Preview', 'Enforce')] [string] $Mode = 'Preview', [Nullable[datetime]] $StartedUtc ) $reconciled = Test-PersonaReconciliation -Counters $Counters # $StartedUtc arrives here already unwrapped to a plain [datetime] - PowerShell # collapses [Nullable[datetime]] to DateTime (or $null) at the call boundary, so # a null check is used rather than .Value / .HasValue. $elapsed = ($null -ne $StartedUtc) ? ('{0:hh\:mm\:ss}' -f ([DateTime]::UtcNow - $StartedUtc)) : $null Write-Host '' Write-Host ('=' * 100) -ForegroundColor DarkGray Write-Host ( $elapsed ` ? ("{0} summary - mode: {1} - elapsed: {2} - processed: {3}" -f $SummaryType, $Mode, $elapsed, $Counters.Processed) ` : ("{0} summary - mode: {1} - processed: {2}" -f $SummaryType, $Mode, $Counters.Processed) ) -ForegroundColor Cyan Write-Host ('=' * 100) -ForegroundColor DarkGray Write-Host ('{0,-28} {1,-40} {2,-9} {3,10} {4,8}' -f 'Rule ID', 'Name', 'Priority', 'Enabled', 'Matches') -ForegroundColor DarkGray foreach ($entry in $Counters.RuleCounts) { # A disabled rule is dimmed rather than hidden: it is part of the # configuration and its absence from the output would read as a deletion. $colour = if (-not $entry.Enabled) { 'DarkGray' } elseif ($entry.Matches -gt 0) { 'Green' } else { 'Gray' } Write-Host ('{0,-28} {1,-40} {2,-9} {3,10} {4,8}' -f $entry.RuleId, ($entry.Name.Length -gt 40 ? $entry.Name.Substring(0, 37) + '...' : $entry.Name), $entry.Priority, $entry.Enabled, $entry.Matches) -ForegroundColor $colour } Write-Host '' Write-Host ('Outcomes Matched: {0} Unclassified: {1} EvaluationError: {2}' -f $Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError) Write-Host ('Actions Unchanged: {0} WouldUpdate: {1} Updated: {2} UpdateFailed: {3} Skipped: {4}' -f $Counters.Unchanged, $Counters.WouldUpdate, $Counters.Updated, $Counters.UpdateFailed, $Counters.Skipped) if ($reconciled) { Write-Host ('Reconciliation PASS {0} = {1} + {2} + {3}' -f $Counters.Processed, $Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError) -ForegroundColor Green } else { Write-Host ('Reconciliation FAIL {0} != {1} + {2} + {3} - this is an engine defect (FR-021)' -f $Counters.Processed, $Counters.Matched, $Counters.Unclassified, $Counters.EvaluationError) -ForegroundColor Red } Write-Host ('=' * 100) -ForegroundColor DarkGray Write-Host '' }