Public/Test-KritHardenCompliance.ps1

function Test-KritHardenCompliance {
    <#
    .SYNOPSIS
        Runs every installed compliance probe (HotCakeX Confirm-SystemCompliance + scipag
        Invoke-HardeningKitty in Audit mode) and emits a normalised result set.

    .DESCRIPTION
        Audit-only - never mutates system state. Probes run sequentially with timeouts
        so a hung tool doesn't lock up the whole pass. Result rows carry:
          - Source (HotCakeX | HardeningKitty | MicrosoftSCT | DSC)
          - Category (Defender / Firewall / SmartScreen / BitLocker / etc.)
          - Control (specific check name)
          - Outcome (Pass | Fail | Warning | Information | NotApplicable)
          - Detail (free-form)
          - Recommendation (short text)
          - Severity (Critical | Warning | Info)

        Plus an aggregate score per source.

    .PARAMETER MaxProbeSeconds
        Per-probe timeout. Default 300 (5 min).

    .PARAMETER HardeningKittyList
        Which HardeningKitty finding list to apply. Default: 'finding_list_0x6d69636b_machine.csv' (Windows 11 23H2 baseline).
        Pass 'all' to run every shipped list (much longer).

    .EXAMPLE
        Test-KritHardenCompliance
        $r = Test-KritHardenCompliance -Quiet
        New-KritHardenReport -ComplianceResult $r -OutDir C:\drop\harden

    .NOTES
        Author: Joshua Finley - Kritical Pty Ltd
        Audit-only in v1.0.0. Apply path lands in v1.1.0.
    #>

    [CmdletBinding()]
    [OutputType([pscustomobject])]
    param(
        [int]    $MaxProbeSeconds = 300,
        [string] $HardeningKittyList,
        [switch] $SkipHotCakeX,
        [switch] $SkipHardeningKitty,
        [switch] $Quiet,
        [switch] $NoBanner
    )
    if (-not $NoBanner.IsPresent -and -not $Quiet.IsPresent) {
        Write-KritHardenBanner -Title 'Compliance Probe (audit-only)' -Compact
    }

    $findings = [System.Collections.Generic.List[pscustomobject]]::new()
    $sourceSummary = [System.Collections.Generic.List[pscustomobject]]::new()

    # ---- Source 1: HotCakeX Confirm-SystemCompliance ----
    if (-not $SkipHotCakeX.IsPresent) {
        $hc = Get-Module -ListAvailable -Name 'Harden-Windows-Security-Module' -ErrorAction SilentlyContinue |
              Sort-Object Version -Descending | Select-Object -First 1
        if ($hc) {
            try {
                Import-Module 'Harden-Windows-Security-Module' -Force -ErrorAction Stop
                if (Get-Command -Name 'Confirm-SystemCompliance' -ErrorAction SilentlyContinue) {
                    if (-not $Quiet.IsPresent) { Write-Host 'Running HotCakeX Confirm-SystemCompliance...' -ForegroundColor DarkCyan }
                    $job = Start-Job -ScriptBlock {
                        Import-Module 'Harden-Windows-Security-Module' -Force
                        Confirm-SystemCompliance -ExportToCSV -DetailedDisplay 2>&1
                    }
                    if (Wait-Job -Job $job -Timeout $MaxProbeSeconds) {
                        Receive-Job -Job $job | Out-Null
                        # HotCakeX writes a CSV alongside; parse if present
                        $csv = Get-ChildItem -LiteralPath (Get-Location) -Filter 'Compliance-Check-*.csv' -ErrorAction SilentlyContinue |
                               Sort-Object LastWriteTime -Descending | Select-Object -First 1
                        if ($csv) {
                            $rows = Import-Csv -LiteralPath $csv.FullName
                            foreach ($r in $rows) {
                                $findings.Add([pscustomobject]@{
                                    Source         = 'HotCakeX'
                                    Category       = ($r.PSObject.Properties['Category'].Value)
                                    Control        = ($r.PSObject.Properties['Name'].Value)
                                    Outcome        = if ($r.PSObject.Properties['Compliant'].Value -eq 'True') { 'Pass' } else { 'Fail' }
                                    Detail         = ($r.PSObject.Properties['Value'].Value)
                                    Recommendation = ''
                                    Severity       = if ($r.PSObject.Properties['Compliant'].Value -eq 'True') { 'Info' } else { 'Warning' }
                                })
                            }
                            $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Tool=$hc.Name; Version=$hc.Version; Findings=$rows.Count; CsvPath=$csv.FullName })
                        } else {
                            $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Tool=$hc.Name; Version=$hc.Version; Findings=0; CsvPath=$null; Note='no CSV emitted' })
                        }
                    } else {
                        Stop-Job -Job $job -ErrorAction SilentlyContinue
                        $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Status='TIMEOUT'; Note="exceeded $MaxProbeSeconds sec" })
                    }
                    Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                } else {
                    $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Status='SKIPPED'; Note='Confirm-SystemCompliance not exported by installed module version' })
                }
            } catch {
                $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Status='ERROR'; Note=$_.Exception.Message })
            }
        } else {
            $sourceSummary.Add([pscustomobject]@{ Source='HotCakeX'; Status='NOT-INSTALLED'; Note='Install-KritHardenModules to install' })
        }
    }

    # ---- Source 2: scipag/HardeningKitty (Audit mode) ----
    if (-not $SkipHardeningKitty.IsPresent) {
        $hk = Get-Module -ListAvailable -Name 'HardeningKitty' -ErrorAction SilentlyContinue |
              Sort-Object Version -Descending | Select-Object -First 1
        if ($hk) {
            try {
                Import-Module 'HardeningKitty' -Force -ErrorAction Stop
                if (Get-Command -Name 'Invoke-HardeningKitty' -ErrorAction SilentlyContinue) {
                    if (-not $Quiet.IsPresent) { Write-Host 'Running HardeningKitty in Audit mode...' -ForegroundColor DarkCyan }
                    $hkArgs = @{ Mode = 'Audit'; Log = $true; Report = $true }
                    if ($HardeningKittyList) { $hkArgs.FileFindingList = $HardeningKittyList }
                    $job = Start-Job -ArgumentList $hkArgs -ScriptBlock {
                        param($a)
                        Import-Module 'HardeningKitty' -Force
                        Invoke-HardeningKitty @a
                    }
                    if (Wait-Job -Job $job -Timeout $MaxProbeSeconds) {
                        Receive-Job -Job $job | Out-Null
                        # HardeningKitty writes a CSV report alongside; parse newest
                        $csv = Get-ChildItem -LiteralPath (Get-Location) -Filter 'hardeningkitty_report_*.csv' -ErrorAction SilentlyContinue |
                               Sort-Object LastWriteTime -Descending | Select-Object -First 1
                        if ($csv) {
                            $rows = Import-Csv -LiteralPath $csv.FullName
                            foreach ($r in $rows) {
                                $outcome = switch (($r.PSObject.Properties['Result'].Value)) {
                                    'Passed'  { 'Pass' }
                                    'Failed'  { 'Fail' }
                                    default   { 'Information' }
                                }
                                $findings.Add([pscustomobject]@{
                                    Source         = 'HardeningKitty'
                                    Category       = ($r.PSObject.Properties['Category'].Value)
                                    Control        = ($r.PSObject.Properties['Name'].Value)
                                    Outcome        = $outcome
                                    Detail         = ($r.PSObject.Properties['Result'].Value) + ' / Expected=' + ($r.PSObject.Properties['RecommendedValue'].Value)
                                    Recommendation = ($r.PSObject.Properties['RecommendedValue'].Value)
                                    Severity       = ($r.PSObject.Properties['Severity'].Value)
                                })
                            }
                            $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Tool=$hk.Name; Version=$hk.Version; Findings=$rows.Count; CsvPath=$csv.FullName })
                        } else {
                            $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Tool=$hk.Name; Version=$hk.Version; Findings=0; CsvPath=$null; Note='no CSV emitted' })
                        }
                    } else {
                        Stop-Job -Job $job -ErrorAction SilentlyContinue
                        $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Status='TIMEOUT'; Note="exceeded $MaxProbeSeconds sec" })
                    }
                    Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
                } else {
                    $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Status='SKIPPED'; Note='Invoke-HardeningKitty not exported by installed version' })
                }
            } catch {
                $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Status='ERROR'; Note=$_.Exception.Message })
            }
        } else {
            $sourceSummary.Add([pscustomobject]@{ Source='HardeningKitty'; Status='NOT-INSTALLED'; Note='Install-KritHardenModules to install' })
        }
    }

    # Aggregate
    $byOutcome = $findings | Group-Object Outcome | ForEach-Object {
        [pscustomobject]@{ Outcome=$_.Name; Count=$_.Count }
    }

    $platform = $null
    if (Get-Command Get-KritPlatform -ErrorAction SilentlyContinue) { $platform = Get-KritPlatform }
    $result = [pscustomobject]@{
        Timestamp       = (Get-Date).ToUniversalTime()
        FindingCount    = $findings.Count
        ByOutcome       = @($byOutcome)
        SourceSummary   = @($sourceSummary)
        Findings        = @($findings)
        Platform        = $platform
    }

    if (-not $Quiet.IsPresent) {
        Write-Host ''
        Write-Host "=== Compliance probe complete ===" -ForegroundColor Yellow
        Write-Host ("Findings: $($findings.Count)")
        $byOutcome | Format-Table -AutoSize | Out-String | Write-Host
        $sourceSummary | Format-Table -AutoSize | Out-String | Write-Host
    }
    $result
}