Public/Compare-HardeningLensResult.ps1
|
function Compare-HardeningLensResult { <# .SYNOPSIS Compares two Hardening Lens scan results and identifies security posture drift. .DESCRIPTION Accepts current schema 1.1 results and validated schema 1.0 legacy results. The comparison output always uses schema 1.1 and marks provenance that is unavailable in legacy inputs explicitly. .PARAMETER Reference Earlier scan object or JSON file path. .PARAMETER Difference Current scan object or JSON file path. .PARAMETER OutputPath Optional output file path. .PARAMETER Format Output format when OutputPath is specified. .PARAMETER AllowCrossTargetComparison Allows results from different computer names to be compared. By default, a mismatch is rejected to prevent accidental cross-target drift reports. .PARAMETER AllowCrossBaselineComparison Allows results from different baseline names to be compared. Baseline version changes with the same name remain supported by default. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object]$Reference, [Parameter(Mandatory)] [object]$Difference, [string]$OutputPath, [ValidateSet('Json', 'Markdown')] [string]$Format = 'Markdown', [switch]$AllowCrossTargetComparison, [switch]$AllowCrossBaselineComparison ) $referenceResult = Read-HLScanResult -InputObject $Reference $differenceResult = Read-HLScanResult -InputObject $Difference # Validate the comparison-critical contract before reading nested properties or # building ID maps. The full validator is used as an additional gate when the # module version provides it. Assert-HLComparableScanResult -ScanResult $referenceResult -InputName Reference Assert-HLComparableScanResult -ScanResult $differenceResult -InputName Difference if ($null -ne (Get-Command -Name Assert-HLScanResult -CommandType Function -ErrorAction SilentlyContinue)) { Assert-HLScanResult -ScanResult $referenceResult Assert-HLScanResult -ScanResult $differenceResult } $legacyInputNames = @() if ([string]$referenceResult.schemaVersion -ceq '1.0') { $legacyInputNames += 'Reference' } if ([string]$differenceResult.schemaVersion -ceq '1.0') { $legacyInputNames += 'Difference' } if ($legacyInputNames.Count -gt 0) { Write-Verbose ('Legacy result schema 1.0 accepted for: {0}. Provenance and timing fields unavailable in those inputs are emitted as null or empty values.' -f ($legacyInputNames -join ', ')) } $referenceComputer = [string]$referenceResult.system.ComputerName $differenceComputer = [string]$differenceResult.system.ComputerName if (-not $AllowCrossTargetComparison -and -not [string]::IsNullOrWhiteSpace($referenceComputer) -and -not [string]::IsNullOrWhiteSpace($differenceComputer) -and $referenceComputer -ine $differenceComputer) { throw "Cannot compare results from different targets ('$referenceComputer' and '$differenceComputer'). Use -AllowCrossTargetComparison only for an intentional cross-target comparison." } $referenceBaseline = [string]$referenceResult.baseline.name $differenceBaseline = [string]$differenceResult.baseline.name if (-not $AllowCrossBaselineComparison -and -not [string]::IsNullOrWhiteSpace($referenceBaseline) -and -not [string]::IsNullOrWhiteSpace($differenceBaseline) -and $referenceBaseline -ine $differenceBaseline) { throw "Cannot compare results from different baselines ('$referenceBaseline' and '$differenceBaseline'). Use -AllowCrossBaselineComparison only for an intentional cross-baseline comparison." } $comparison = Compare-HLScanResult -Reference $referenceResult -Difference $differenceResult if (-not [string]::IsNullOrWhiteSpace($OutputPath)) { $resolvedOutputPath = ConvertTo-HLFullPath -Path $OutputPath $parent = Split-Path -Path $resolvedOutputPath -Parent if (-not [string]::IsNullOrWhiteSpace($parent) -and -not (Test-Path -LiteralPath $parent -PathType Container)) { [void](New-Item -Path $parent -ItemType Directory -Force) } $content = if ($Format -eq 'Json') { ($comparison | ConvertTo-Json -Depth 30) + [Environment]::NewLine } else { (ConvertTo-HLComparisonMarkdown -Comparison $comparison) + [Environment]::NewLine } Write-HLUtf8File -Path $resolvedOutputPath -Content $content } $PSCmdlet.WriteObject($comparison, $false) } |