# Copyright (c) 2026 Comitari. Read-only wrapper for HardeningKitty. # Run ONLY Invoke-HardeningKitty -Mode Audit. Never invoke HailMary, GPO or Config. [CmdletBinding()] param( [Parameter(Mandatory=$true)][string]$InstallRoot, [Parameter(Mandatory=$true)][string]$CsvFile, [Parameter(Mandatory=$true)][string]$MetadataFile, [Parameter(Mandatory=$true)][ValidateSet('GPO','Intune')][string]$Source, [Parameter(Mandatory=$true)][string]$Profile ) $ErrorActionPreference = 'Stop' function Get-MachineFindingList { param([string]$ProfileName, [string]$AuditSource) if ($ProfileName -ne 'auto') { if ($ProfileName -eq 'community') { return 'finding_list_0x6d69636b_machine.csv' } if ($ProfileName -match '_user\.csv$') { throw 'A machine audit running as SYSTEM cannot assess an interactive user baseline.' } if ($ProfileName -notmatch '^finding_list_[A-Za-z0-9_.-]+\.csv$' -or $ProfileName.Contains('..')) { throw 'Invalid finding list override. Only a basename starting finding_list_ is allowed.' } return $ProfileName } # Get-CimInstance is supported, unlike the deprecated wmic.exe command. $os = Get-CimInstance -ClassName Win32_OperatingSystem $cv = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $build = [int]$cv.CurrentBuildNumber $display = if ($cv.PSObject.Properties['DisplayVersion']) { [string]$cv.DisplayVersion } else { '' } $version = $display.ToLowerInvariant() if ([int]$os.ProductType -eq 1) { if ($build -ge 22000) { if ($version -notin @('21h2','22h2','23h2','24h2','25h2','26h2')) { throw "Unsupported Windows 11 DisplayVersion '$display' (build $build); set profile explicitly." } return ('finding_list_msft_security_baseline_windows_11_' + $version + '_machine.csv') } if ($version -eq '20h2' -or $version -eq '21h1') { return 'finding_list_msft_security_baseline_windows_10_20h2_21h1_machine.csv' } if ($version -notin @('21h2','22h2')) { throw "Unsupported Windows 10 DisplayVersion '$display' (build $build); set profile explicitly." } return ('finding_list_msft_security_baseline_windows_10_' + $version + '_machine.csv') } # Domain controllers and member servers use distinct security baselines. $role = if ([int]$os.ProductType -eq 2) { 'dc' } else { 'member' } switch ($build) { 9600 { return 'finding_list_cis_microsoft_windows_server_2012_r2_3.0.0_machine.csv' } 14393 { return 'finding_list_cis_microsoft_windows_server_2016_1607_3.0.0_machine.csv' } 17763 { return 'finding_list_cis_microsoft_windows_server_2019_1809_3.0.0_machine.csv' } 20348 { return ('finding_list_msft_security_baseline_windows_server_2022_21h2_' + $role + '_machine.csv') } 26100 { return ('finding_list_msft_security_baseline_windows_server_2025_24h2_' + $role + '_machine.csv') } default { throw "Unsupported Windows Server build $build; set profile explicitly." } } } try { if (-not (Test-Path -LiteralPath $InstallRoot -PathType Container)) { throw 'Installed package data missing.' } $listName = Get-MachineFindingList -ProfileName $Profile -AuditSource $Source $listPath = Join-Path (Join-Path $InstallRoot 'lists') $listName if (-not (Test-Path -LiteralPath $listPath -PathType Leaf)) { throw "Required finding list not bundled: $listName" } $module = Join-Path $InstallRoot 'HardeningKitty.psm1' if (-not (Test-Path -LiteralPath $module -PathType Leaf)) { throw 'HardeningKitty module not bundled.' } $osInfo = Get-CimInstance -ClassName Win32_OperatingSystem $cvInfo = Get-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' $metaDisplay = if ($cvInfo.PSObject.Properties['DisplayVersion']) { [string]$cvInfo.DisplayVersion } else { '' } $metadata = [ordered]@{ hostname = [Environment]::MachineName os_caption = [string]$osInfo.Caption product_type = [int]$osInfo.ProductType build = [string]$cvInfo.CurrentBuildNumber display_version = $metaDisplay benchmark = $listName source = $Source execution_identity = [Security.Principal.WindowsIdentity]::GetCurrent().Name } $json = $metadata | ConvertTo-Json -Depth 5 [System.IO.File]::WriteAllText($MetadataFile, $json, [System.Text.UTF8Encoding]::new($false)) # HardeningKitty supports write modes, but this wrapper never exposes them. Import-Module -Name $module -Force -ErrorAction Stop # Upstream may emit non-terminating errors for unsupported individual tests. # We detect skipped checks by comparing CSV rows to the input finding list. $ErrorActionPreference = 'Continue' Invoke-HardeningKitty -Mode Audit -Source $Source -FileFindingList $listPath -Report -ReportFile $CsvFile -SkipUserInformation -SkipMachineInformation | Out-Null if ((-not (Test-Path -LiteralPath $CsvFile -PathType Leaf)) -or ((Get-Item -LiteralPath $CsvFile).Length -lt 40)) { throw 'Audit did not produce a valid CSV report.' } exit 0 } catch { [Console]::Error.WriteLine('HardeningKitty audit failed: ' + $_.Exception.Message) exit 1 }