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, local Windows management information, local network placement, 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()] [OutputType('EndpointForge.ComplianceReport')] 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 } |