Private/Core/Get-ResourceConstrainedFixes.ps1
|
# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0 # https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/ # AI/LLM use: see AI-USAGE.md for required attribution function Get-ResourceConstrainedFixes { <# .SYNOPSIS Filters audit findings to free or low-cost remediation actions only. .DESCRIPTION Reads RemediationCosts.json and filters findings to those with Free or Low cost tiers. Returns findings sorted by impact (severity weight) descending. .PARAMETER Findings Array of audit finding objects. .PARAMETER MaxCostTier Maximum cost tier to include. Default: Low. Options: Free, Low, Medium. .PARAMETER RemediationData Pre-loaded remediation cost data. If not provided, loads from Data/RemediationCosts.json. #> [CmdletBinding()] param( [Parameter(Mandatory)] [PSCustomObject[]]$Findings, [ValidateSet('Free', 'Low', 'Medium')] [string]$MaxCostTier = 'Low', [hashtable]$RemediationData ) if (-not $RemediationData) { $remPath = Join-Path $PSScriptRoot '../../Data/RemediationCosts.json' if (Test-Path $remPath) { $RemediationData = Get-Content -Path $remPath -Raw | ConvertFrom-Json -AsHashtable } else { Write-Warning "RemediationCosts.json not found at $remPath" return @() } } $tierOrder = @{ 'Free' = 0; 'Low' = 1; 'Medium' = 2; 'High' = 3; 'Enterprise' = 4 } $maxTierIndex = $tierOrder[$MaxCostTier] ?? 1 $severityWeights = @{ 'Critical' = 10 'High' = 6 'Medium' = 3 'Low' = 1 'Info' = 0 } $effortHours = @{ 'Minimal' = 0.25 'Low' = 1 'Medium' = 4 'High' = 16 'Major' = 80 } $results = [System.Collections.Generic.List[PSCustomObject]]::new() foreach ($finding in $Findings) { if ($finding.Status -notin @('FAIL', 'WARN')) { continue } $checkId = $finding.CheckId ?? $finding.Id ?? '' $prefix = if ($checkId -match '^([A-Z0-9]+)-') { $Matches[1] } else { '' } # Look up cost: specific override first, then category default $costInfo = $null if ($RemediationData.overrides.$checkId) { $costInfo = $RemediationData.overrides.$checkId } elseif ($RemediationData.categoryDefaults.$prefix) { $costInfo = $RemediationData.categoryDefaults.$prefix } if (-not $costInfo) { continue } $checkTier = $costInfo.costTier ?? 'Medium' $checkTierIndex = $tierOrder[$checkTier] ?? 2 if ($checkTierIndex -gt $maxTierIndex) { continue } $severity = $finding.Severity ?? 'Medium' $weight = $severityWeights[$severity] ?? 3 $statusMult = if ($finding.Status -eq 'WARN') { 0.5 } else { 1.0 } $impact = $weight * $statusMult $effort = $costInfo.effort ?? 'Medium' $hours = $effortHours[$effort] ?? 4 $results.Add([PSCustomObject]@{ PSTypeName = 'PSGuerrilla.ResourceConstrainedFix' CheckId = $checkId CheckName = $finding.Name ?? $finding.CheckName ?? $checkId Description = $finding.Description ?? '' Severity = $severity Status = $finding.Status CostTier = $checkTier Effort = $effort EstimatedHours = $hours Impact = $impact ImpactPerHour = if ($hours -gt 0) { [Math]::Round($impact / $hours, 2) } else { $impact } Category = $finding.Category ?? $prefix RemediationSteps = $finding.RemediationSteps ?? $costInfo.notes ?? '' }) } return @($results | Sort-Object -Property Impact -Descending) } |