Public/Get-MDEValidationReport.ps1
|
function Get-MDEValidationReport { <# .SYNOPSIS Generates a formatted MDE validation report. .DESCRIPTION Runs all MDE configuration validation tests and generates a report in the specified format. .PARAMETER OutputFormat The format of the output report. Valid values are 'Console', 'HTML', 'JSON', or 'Object'. Default is 'Console'. .PARAMETER OutputPath The path to save the HTML or JSON report. Used when OutputFormat is 'HTML' or 'JSON'. When OutputFormat is 'JSON' and -OutputPath is omitted, the JSON string is returned to the pipeline instead. .PARAMETER IncludeOnboarding Include MDE onboarding status check (requires elevated privileges). .PARAMETER IncludePolicyVerification Include policy registry verification sub-tests. These sub-tests verify that settings returned by Get-MpPreference match the corresponding registry/policy entries based on the device's management type (Intune vs Security Settings Management). .PARAMETER AsExitCode When present, all format-specific side effects (console output, file writes) still occur normally, but the return value is replaced by [int] equal to the number of results with Status 'Fail' (0 = all clear). Designed for automation scenarios such as scheduled tasks and Intune remediation scripts where the caller needs a numeric exit code. .PARAMETER Category Restrict validation to one or more named categories. Forwarded to Test-MDEConfiguration. Valid values: 'Device State', 'Protection Settings', 'Onboarding', 'Network Protection', 'ASR Rules', 'Tamper Protection', 'Exclusion Settings'. .PARAMETER ExcludeTest One or more TestName wildcard patterns to exclude from the results. Forwarded to Test-MDEConfiguration. Supports standard PowerShell wildcards (* ? []). .EXAMPLE Get-MDEValidationReport Displays a console-formatted validation report. .EXAMPLE Get-MDEValidationReport -OutputFormat HTML -OutputPath "C:\Reports\MDEReport.html" Generates an HTML report and saves it to the specified path. .EXAMPLE Get-MDEValidationReport -OutputFormat Object Returns validation results as PowerShell objects. .EXAMPLE Get-MDEValidationReport -OutputFormat JSON Returns the full validation report as a JSON string to the pipeline. .EXAMPLE Get-MDEValidationReport -OutputFormat JSON -OutputPath "C:\Reports\MDEReport.json" Generates a JSON report and saves it to the specified path. Returns the file path. .EXAMPLE Get-MDEValidationReport -IncludePolicyVerification Displays a validation report with policy registry verification sub-tests. .EXAMPLE exit (Get-MDEValidationReport -OutputFormat JSON -OutputPath report.json -AsExitCode) Writes a JSON report then returns the number of failed tests as an integer. Pass the result to exit() in a scheduled task or Intune remediation script so the platform can detect non-compliant state. Returns 0 when all tests pass. .EXAMPLE Get-MDEValidationReport -Category 'Device State', 'Tamper Protection' Runs only Device State and Tamper Protection tests and displays the console report. .EXAMPLE Get-MDEValidationReport -OutputFormat Object -ExcludeTest '*SmartScreen*' Returns result objects for all tests except those with SmartScreen in their TestName. .OUTPUTS Console output, HTML file path (string), JSON file path (string), JSON string, [int] fail count (when -AsExitCode is set), or array of PSCustomObjects depending on OutputFormat and switches. #> [CmdletBinding()] param( [Parameter()] [ValidateSet('Console', 'HTML', 'JSON', 'Object')] [string]$OutputFormat = 'Console', [Parameter()] [string]$OutputPath, [Parameter()] [switch]$IncludeOnboarding, [Parameter()] [switch]$IncludePolicyVerification, [Parameter()] [switch]$AsExitCode, [Parameter()] [ValidateSet('Device State', 'Protection Settings', 'Onboarding', 'Network Protection', 'ASR Rules', 'Tamper Protection', 'Exclusion Settings')] [string[]]$Category, [Parameter()] [string[]]$ExcludeTest ) # Build splatted args for Test-MDEConfiguration so optional arrays are omitted when empty $testParams = @{ IncludeOnboarding = $IncludeOnboarding.IsPresent IncludePolicyVerification = $IncludePolicyVerification.IsPresent } if ($Category.Count -gt 0) { $testParams['Category'] = $Category } if ($ExcludeTest.Count -gt 0) { $testParams['ExcludeTest'] = $ExcludeTest } # Run all validation tests $results = Test-MDEConfiguration @testParams # Get OS information for the report header $osInfo = Get-MDEOperatingSystemInfo # Get management status for the report header $managedByStatus = Get-MDESecuritySettingsManagementStatus # Get MDE onboarding status for the report header $onboardingStatus = Get-MDEOnboardingStatusString # $reportOutput holds the normal return value; overridden by -AsExitCode at the end. $reportOutput = $null switch ($OutputFormat) { 'Object' { $reportOutput = $results } 'Console' { Write-Host "`n========================================" -ForegroundColor Cyan Write-Host " MDE Configuration Validation Report" -ForegroundColor Cyan Write-Host " Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" -ForegroundColor Cyan Write-Host " Computer: $env:COMPUTERNAME" -ForegroundColor Cyan Write-Host " OS: $osInfo" -ForegroundColor Cyan Write-Host " Managed By: $managedByStatus" -ForegroundColor Cyan Write-Host " MDE Onboarding: $onboardingStatus" -ForegroundColor Cyan Write-Host "========================================`n" -ForegroundColor Cyan foreach ($result in $results) { $statusColor = switch ($result.Status) { 'Pass' { 'Green' } 'Fail' { 'Red' } 'Warning' { 'Yellow' } 'Info' { 'Cyan' } 'NotApplicable' { 'Gray' } default { 'White' } } $statusSymbol = switch ($result.Status) { 'Pass' { '[PASS]' } 'Fail' { '[FAIL]' } 'Warning' { '[WARN]' } 'Info' { '[INFO]' } 'NotApplicable' { '[N/A]' } default { '[???]' } } Write-Host "$statusSymbol " -ForegroundColor $statusColor -NoNewline Write-Host "$($result.TestName)" -ForegroundColor White Write-Host " $($result.Message)" -ForegroundColor Gray if ($result.Recommendation) { Write-Host " Recommendation: $($result.Recommendation)" -ForegroundColor Yellow } Write-Host "" } # Summary $passCount = @($results | Where-Object { $_.Status -eq 'Pass' }).Count $failCount = @($results | Where-Object { $_.Status -eq 'Fail' }).Count $warnCount = @($results | Where-Object { $_.Status -eq 'Warning' }).Count $totalCount = @($results).Count Write-Host "========================================" -ForegroundColor Cyan Write-Host " Summary: $passCount/$totalCount Passed" -ForegroundColor $(if ($failCount -eq 0) { 'Green' } else { 'Yellow' }) Write-Host " Passed: $passCount | Failed: $failCount | Warnings: $warnCount" -ForegroundColor Cyan Write-Host "========================================`n" -ForegroundColor Cyan } 'HTML' { if ([string]::IsNullOrEmpty($OutputPath)) { $tempDir = if ($env:TEMP) { $env:TEMP } elseif ($env:TMPDIR) { $env:TMPDIR } else { '/tmp' } $OutputPath = Join-Path $tempDir "MDEValidationReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').html" } # Resolve to absolute path to ensure Split-Path works correctly $OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) # Force create the output directory if it doesn't exist $outputDirectory = Split-Path -Path $OutputPath -Parent if (-not [string]::IsNullOrEmpty($outputDirectory) -and -not (Test-Path -Path $outputDirectory)) { try { New-Item -Path $outputDirectory -ItemType Directory -Force | Out-Null } catch { Write-Error "Failed to create output directory: $outputDirectory. Error: $_" return } } $htmlContent = ConvertTo-MDEHtmlReport -Results $results -ComputerName $env:COMPUTERNAME -OSInfo $osInfo -ManagedByStatus $managedByStatus -OnboardingStatus $onboardingStatus $htmlContent | Out-File -FilePath $OutputPath -Encoding UTF8 Write-Host "HTML report saved to: $OutputPath" -ForegroundColor Green $reportOutput = $OutputPath } 'JSON' { $moduleVersion = try { $MyInvocation.MyCommand.Module.Version.ToString() } catch { '0.0.0' } $envelope = [ordered]@{ metadata = [ordered]@{ computerName = $env:COMPUTERNAME os = $osInfo managedBy = $managedByStatus mdeOnboarding = $onboardingStatus generated = (Get-Date).ToString('o') moduleVersion = $moduleVersion } summary = [ordered]@{ total = @($results).Count pass = @($results | Where-Object { $_.Status -eq 'Pass' }).Count fail = @($results | Where-Object { $_.Status -eq 'Fail' }).Count warning = @($results | Where-Object { $_.Status -eq 'Warning' }).Count info = @($results | Where-Object { $_.Status -eq 'Info' }).Count notApplicable = @($results | Where-Object { $_.Status -eq 'NotApplicable' }).Count } results = @($results | ForEach-Object { [ordered]@{ TestName = $_.TestName Category = $_.Category Status = $_.Status Message = $_.Message Expected = $_.Expected Actual = $_.Actual Recommendation = $_.Recommendation Timestamp = $_.Timestamp.ToString('o') } }) } $jsonOutput = $envelope | ConvertTo-Json -Depth 5 if (-not [string]::IsNullOrEmpty($OutputPath)) { $OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) $outputDirectory = Split-Path -Path $OutputPath -Parent if (-not [string]::IsNullOrEmpty($outputDirectory) -and -not (Test-Path -Path $outputDirectory)) { try { New-Item -Path $outputDirectory -ItemType Directory -Force | Out-Null } catch { Write-Error "Failed to create output directory: $outputDirectory. Error: $_" return } } $jsonOutput | Out-File -FilePath $OutputPath -Encoding UTF8 Write-Host "JSON report saved to: $OutputPath" -ForegroundColor Green $reportOutput = $OutputPath } else { $reportOutput = $jsonOutput } } } # When -AsExitCode is set, return the number of failed tests as [int] instead of # the normal format-specific output (0 = all clear; non-zero = remediation needed). if ($AsExitCode) { return [int]@($results | Where-Object { $_.Status -eq 'Fail' }).Count } if ($null -ne $reportOutput) { return $reportOutput } } |