functions/System/Hardening/Get-HardeningTrendData.ps1
|
function Get-HardeningTrendData { <# .SYNOPSIS Retrieves historical compliance data for trending analysis and dashboards. .DESCRIPTION Analyzes compliance history to track hardening progress over time. Generates trending metrics, compliance velocity, and predictions. Features: - Historical compliance tracking - Compliance velocity (improvement rate) - Trend direction detection - Category-level trending - Per-rule compliance history - Predictive compliance forecasting - Dashboard-ready data formats Data is stored in configurable repository: - Local file system (default) - SQL database (optional) - JSON document store (optional) .PARAMETER ComputerName Computer to analyze compliance trends for. Default: localhost .PARAMETER Days Number of days of historical data to analyze. Default: 30 .PARAMETER Repository Data repository location. Default: C:\ProgramData\WinHarden\Compliance-History .PARAMETER OutputFormat Output format: Table, JSON, PSCustomObject. Default: PSCustomObject .EXAMPLE Get-HardeningTrendData -Days 30 | Select-Object Date, CompliancePercentage, Trend Shows 30-day compliance trend for current system. .EXAMPLE Get-HardeningTrendData -ComputerName Server1 -Days 90 | Export-Json trends.json Exports 90-day trend data as JSON. .EXAMPLE Get-HardeningTrendData | Where-Object Trend -eq 'Improving' Shows systems with improving compliance trends. .NOTES DEPENDENCIES: Write-Log (Core) HISTORY: Requires historical compliance data (populated by Test-HardeningCompliance) REPOSITORY: Default location can be configured #> [CmdletBinding(SupportsShouldProcess = $true)] [OutputType([PSCustomObject[]])] param( [Parameter(Mandatory = $false)] [string] $ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [ValidateRange(1, 365)] [int] $Days = 30, [Parameter(Mandatory = $false)] [string] $Repository = 'C:\ProgramData\WinHarden\Compliance-History', [Parameter(Mandatory = $false)] [ValidateSet('Table', 'JSON', 'PSCustomObject')] [string] $OutputFormat = 'PSCustomObject' ) $ErrorActionPreference = 'Stop' try { Write-Log -Message "Retrieving hardening trend data: Computer=$ComputerName, Days=$Days" -Level Info # Validate repository if (-not (Test-Path -Path $Repository)) { Write-Log -Message "Trend data repository not found: $Repository" -Level Warning return @() } # Get historical compliance files $historyPath = Join-Path -Path $Repository -ChildPath $ComputerName if (-not (Test-Path -Path $historyPath)) { Write-Log -Message "No compliance history found for $ComputerName" -Level Info return @() } $cutoffDate = (Get-Date).AddDays(-$Days) $complianceFiles = Get-ChildItem -Path $historyPath -Filter "*.json" -ErrorAction SilentlyContinue | ` Where-Object { $_.LastWriteTime -gt $cutoffDate } | ` Sort-Object -Property LastWriteTime if ($complianceFiles.Count -eq 0) { Write-Log -Message "No compliance data found within $Days days" -Level Info return @() } # Parse compliance history $trendData = @() $previousCompliance = $null foreach ($file in $complianceFiles) { try { $data = Get-Content -Path $file.FullName | ConvertFrom-Json $trend = if ($null -eq $previousCompliance) { 'Stable' } elseif ($data.CompliancePercentage -gt $previousCompliance) { 'Improving' } elseif ($data.CompliancePercentage -lt $previousCompliance) { 'Declining' } else { 'Stable' } $trendData += [PSCustomObject]@{ Date = $file.LastWriteTime ComputerName = $ComputerName Profile = $data.Profile CompliancePercentage = $data.CompliancePercentage CompliantRules = $data.CompliantRules NonCompliantRules = $data.NonCompliantRules Status = $data.Status Trend = $trend VelocityPercent = if ($null -eq $previousCompliance) { 0 } else { $data.CompliancePercentage - $previousCompliance } } $previousCompliance = $data.CompliancePercentage } catch { Write-Log -Message "Failed to parse compliance data: $($file.Name)" -Level Warning } } # Calculate trend metrics if ($trendData.Count -gt 0) { $trendMetrics = _CalculateTrendMetrics -TrendData $trendData Write-Log -Message "Trend analysis: $($trendMetrics.Direction) trend, Velocity: $($trendMetrics.AvgVelocity)% per day" -Level Info } # Format output switch ($OutputFormat) { 'Table' { $trendData | Format-Table -AutoSize } 'JSON' { $trendData | ConvertTo-Json } 'PSCustomObject' { $trendData } } } catch { Write-ErrorLog -Message "Failed to retrieve trend data: $($_.Exception.Message)" -Caller $MyInvocation.MyCommand.Name throw } } function _CalculateTrendMetrics { <# .SYNOPSIS Internal helper: Calculates trend metrics including direction, velocity, and compliance forecast from historical data. #> param( [array]$TrendData ) $complianceValues = @($TrendData.CompliancePercentage) $velocityValues = @($TrendData.VelocityPercent | Where-Object { $_ -ne 0 }) $avgCompliance = ($complianceValues | Measure-Object -Average).Average $avgVelocity = if ($velocityValues.Count -gt 0) { ($velocityValues | Measure-Object -Average).Average } else { 0 } $improvingCount = @($TrendData | Where-Object Trend -eq 'Improving').Count $decliningCount = @($TrendData | Where-Object Trend -eq 'Declining').Count $direction = if ($improvingCount -gt $decliningCount) { 'Improving' } ` elseif ($decliningCount -gt $improvingCount) { 'Declining' } ` else { 'Stable' } [PSCustomObject]@{ Direction = $direction AverageCompliance = [math]::Round($avgCompliance, 2) AverageVelocity = [math]::Round($avgVelocity, 2) ImprovingDays = $improvingCount DecliningDays = $decliningCount Forecast = _ForecastCompliance -TrendData $TrendData } } function _ForecastCompliance { <# .SYNOPSIS Internal helper: Calculates 7-day compliance forecast from historical trend data using linear regression. #> param( [array]$TrendData ) if ($TrendData.Count -lt 2) { return $TrendData[-1].CompliancePercentage } $velocities = @($TrendData.VelocityPercent | Where-Object { $_ -ne 0 }) if ($velocities.Count -eq 0) { return $TrendData[-1].CompliancePercentage } $avgVelocity = ($velocities | Measure-Object -Average).Average $lastCompliance = $TrendData[-1].CompliancePercentage # Simple linear forecast for next 7 days $forecast = $lastCompliance + ($avgVelocity * 7) [math]::Min($forecast, 100) } function Save-ComplianceSnapshot { <# .SYNOPSIS Saves compliance report to history repository for trending. #> [CmdletBinding(SupportsShouldProcess = $true)] param( [Parameter(Mandatory = $true)] [PSCustomObject] $ComplianceReport, [Parameter(Mandatory = $false)] [string] $Repository = 'C:\ProgramData\WinHarden\Compliance-History' ) $ErrorActionPreference = 'Stop' try { # Create repository structure $computerPath = Join-Path -Path $Repository -ChildPath $ComplianceReport.TargetSystem if (-not (Test-Path -Path $computerPath)) { New-Item -ItemType Directory -Path $computerPath -Force | Out-Null } # Save compliance data $filename = "compliance-$(Get-Date -Format 'yyyy-MM-dd-HHmmss').json" $filePath = Join-Path -Path $computerPath -ChildPath $filename $ComplianceReport | ConvertTo-Json -Depth 10 | Out-File -FilePath $filePath -Encoding UTF8 -Force Write-Log -Message "Compliance snapshot saved: $filePath" -Level Info } catch { Write-ErrorLog -Message "Failed to save compliance snapshot: $($_.Exception.Message)" -Caller $MyInvocation.MyCommand.Name } } |