Public/Get-EFComplianceReport.ps1
|
function Get-EFComplianceReport { <# .SYNOPSIS Checks this computer against a selected checklist without applying fixes. .DESCRIPTION Reads each selected item and compares it with the expected answer in a checklist. Items can cover Windows settings, restart and update state, storage, applications, scheduled jobs, files, certificates, events, processes, account relationships, and approved network services. This command does not apply a fix or change Windows. TcpPort, DnsResolution, HttpEndpointHealth, WindowsUpdateAvailable, and LocalGroupMembership are network-active. They are blocked unless AllowNetworkChecks is supplied. Contacted services, identity providers, or monitoring tools may record the activity. Update scans never install updates or change update settings. File-text results do not contain matching lines, and event results do not contain event messages or event data. In script output, a checklist is called a baseline and each checklist item is called a control. Those names are retained so existing PowerShell automation remains compatible. The report includes results, a score, guidance, and a predictable ExitCode for scripts: 0 when checked items match, 2 when an item does not match, or 3 when one or more items could not be checked. Use Test-EFEndpointCompliance when a simple Boolean answer is needed. .PARAMETER Baseline The checklist to use: a built-in name, a checklist JSON file, or an object returned by Get-EFBaseline. Review every target and network-active item before running it. .PARAMETER ControlId One or more checklist item IDs to check. Every item is checked by default. .PARAMETER AllowNetworkChecks Allows the five network-active types after their destinations, requested account identities, update options, and purposes have been reviewed. This is an explicit acknowledgement, not a network authorization system. .PARAMETER NoProgress Suppresses the progress display for non-interactive automation hosts. .EXAMPLE Get-EFComplianceReport .EXAMPLE $report = Get-EFComplianceReport -Baseline .\contoso-baseline.json $report.Results | Where-Object Status -ne 'Compliant' .EXAMPLE Get-EFComplianceReport -Baseline .\Contoso.EverydayChecks.json -AllowNetworkChecks -NoProgress Runs the expanded report-only examples, including approved network-active items. .OUTPUTS EndpointForge.ComplianceReport #> [CmdletBinding()] param( [Parameter(Position = 0)] [AllowNull()] [object]$Baseline = 'EnterpriseRecommended', [string[]]$ControlId, [switch]$AllowNetworkChecks, [switch]$NoProgress ) $null = Test-EFWindows -Throw $correlationId = [guid]::NewGuid().ToString() $resolvedBaseline = Resolve-EFBaseline -Baseline $Baseline $controls = @($resolvedBaseline.Controls) if ($PSBoundParameters.ContainsKey('ControlId') -and $null -ne $ControlId -and $ControlId.Length -gt 0) { $missingIds = @($ControlId | Where-Object { $_ -notin $controls.Id }) if ($missingIds.Count -gt 0) { throw [System.ArgumentException]::new("Unknown control Id(s): $($missingIds -join ', ')") } $controls = @($controls | Where-Object { $_.Id -in @($ControlId) }) } $networkControls = @($controls | Where-Object { Test-EFControlUsesNetwork -Control $_ }) if ($networkControls.Count -gt 0 -and -not $AllowNetworkChecks) { $networkTypes = @($networkControls.Type | Sort-Object -Unique) -join ', ' throw [System.InvalidOperationException]::new( "This checklist contains $($networkControls.Count) network-active item(s): $networkTypes. " + 'Review their destinations and purpose, then add -AllowNetworkChecks to run them.' ) } Write-EFLog -Message "Compliance evaluation started for baseline '$($resolvedBaseline.Name)'." ` -CorrelationId $correlationId -Data @{ controlCount = $controls.Count } $controlIndex = 0 $baselineCommandArgument = Get-EFBaselineCommandArgument -Baseline $resolvedBaseline $evaluationContext = @{ AllowNetworkChecks = [bool]$AllowNetworkChecks Cache = @{} } $results = @( foreach ($control in $controls) { $controlIndex++ if (-not $NoProgress) { Write-Progress -Id 1102 -Activity 'EndpointForge checklist check' ` -Status "Checking: $($control.Title)" ` -PercentComplete ([math]::Round(($controlIndex / $controls.Count) * 100)) } $controlResult = Get-EFControlState -Control $control -EvaluationContext $evaluationContext if ($controlResult.Status -eq 'NonCompliant' -and $controlResult.Remediable) { $controlResult.RecommendedAction = if ($null -ne $baselineCommandArgument) { "Preview this supported fix before approval. For scripts: Invoke-EFEndpointRemediation $baselineCommandArgument -ControlId '$($controlResult.ControlId)' -WhatIf" } else { 'Create a fix plan from this same in-memory checklist, then preview the supported change before approval.' } } $controlResult } ) if (-not $NoProgress) { Write-Progress -Id 1102 -Activity 'EndpointForge checklist check' -Completed } $compliantCount = @($results | Where-Object Status -eq 'Compliant').Count $nonCompliantCount = @($results | Where-Object Status -eq 'NonCompliant').Count $notApplicableCount = @($results | Where-Object Status -eq 'NotApplicable').Count $errorCount = @($results | Where-Object Status -eq 'Error').Count $knownCount = $compliantCount + $nonCompliantCount $attemptedCount = $knownCount + $errorCount $score = if ($knownCount -eq 0) { 100 } else { [math]::Round(($compliantCount / $knownCount) * 100, 1) } $coveragePercent = if ($attemptedCount -eq 0) { 100 } else { [math]::Round(($knownCount / $attemptedCount) * 100, 1) } $dataStatus = if ($errorCount -eq 0) { 'Complete' } elseif ($knownCount -eq 0) { 'Failed' } else { 'Partial' } $isCompliant = $nonCompliantCount -eq 0 -and $errorCount -eq 0 $exitCode = if ($errorCount -gt 0) { 3 } elseif ($nonCompliantCount -gt 0) { 2 } else { 0 } $status = if ($nonCompliantCount -gt 0) { 'NonCompliant' } elseif ($errorCount -gt 0) { 'Incomplete' } else { 'Compliant' } $automaticFixCount = @($results | Where-Object { $_.Status -eq 'NonCompliant' -and $_.Remediable }).Count $summaryText = if ($isCompliant) { "$compliantCount applicable checklist item(s) have the expected result." } elseif ($errorCount -gt 0) { "The check is incomplete: $nonCompliantCount item(s) do not match and $errorCount could not be checked." } else { "$nonCompliantCount checklist item(s) need attention. $score% of the items that could be checked match." } $nextStep = if ($isCompliant) { 'No supported fix is needed.' } elseif ($errorCount -gt 0) { 'Review the items that could not be checked. Some protected information requires PowerShell to be opened with Run as Administrator (also called an elevated session).' } elseif ($automaticFixCount -gt 0) { 'Create a fix plan to see what EndpointForge can safely preview and what needs a person to review.' } else { 'Review the details and follow your organization''s approved manual guidance. EndpointForge does not change these report-only items.' } $report = [pscustomobject]@{ PSTypeName = 'EndpointForge.ComplianceReport' ComputerName = $env:COMPUTERNAME BaselineName = [string]$resolvedBaseline.Name BaselineVersion = [string]$resolvedBaseline.Version ChecklistName = [string]$resolvedBaseline.Name ChecklistVersion = [string]$resolvedBaseline.Version ChecklistItemCount = $controls.Count NetworkCheckCount = $networkControls.Count NetworkChecksAllowed = [bool]$AllowNetworkChecks CorrelationId = $correlationId EvaluatedAtUtc = [DateTime]::UtcNow IsCompliant = $isCompliant Status = $status Score = $score DataStatus = $dataStatus CoveragePercent = $coveragePercent ExitCode = $exitCode Summary = $summaryText NextStep = $nextStep CompliantCount = $compliantCount NonCompliantCount = $nonCompliantCount NotApplicableCount = $notApplicableCount ErrorCount = $errorCount AutomaticFixCount = $automaticFixCount Results = $results } Write-EFLog -Message "Compliance evaluation completed for baseline '$($resolvedBaseline.Name)'." ` -Level $(if ($isCompliant) { 'Information' } else { 'Warning' }) -CorrelationId $correlationId ` -Data @{ score = $score; exitCode = $exitCode; nonCompliant = $nonCompliantCount; errors = $errorCount } return $report } |