Public/Test-MDEConfiguration.ps1
|
function Test-MDEConfiguration { <# .SYNOPSIS Runs all MDE configuration validation tests. .DESCRIPTION Executes a comprehensive validation of Microsoft Defender for Endpoint configuration settings and returns the results. Tests are organised into categories. Use -Category to run only specific categories and -ExcludeTest to skip individual tests by TestName wildcard. Write-Progress is displayed during the run and cleared on completion. .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). Registry locations checked: - Intune: HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender\Policy Manager - SSM/GPO/SCCM: HKLM:\SOFTWARE\Policies\Microsoft\Windows Defender Note: Some tests (Edge SmartScreen, Exclusion Visibility) are not applicable for Security Settings Management as only Antivirus, ASR, EDR, and Firewall policies are supported. Policy verification sub-tests are only executed when their parent test runs. If a parent test is excluded via -Category or -ExcludeTest its corresponding policy verification sub-test is also skipped. .PARAMETER Category Restrict the run to one or more named categories. When omitted all categories are included. Valid values: 'Device State', 'Protection Settings', 'Onboarding', 'Network Protection', 'ASR Rules', 'Tamper Protection', 'Exclusion Settings' .PARAMETER ExcludeTest One or more TestName wildcard patterns. Any result whose TestName matches at least one pattern is removed from output. Patterns support standard PowerShell wildcards (* ? []). For tests that emit multiple results (e.g. Test-MDESignatureAge emits 'Antivirus Signature Age' and 'Antispyware Signature Age'), each result is matched independently so you can exclude individual result TestNames. Filtering is applied after invocation to keep behaviour consistent across all single- and multi-result tests. .EXAMPLE Test-MDEConfiguration Runs all MDE configuration validation tests. .EXAMPLE Test-MDEConfiguration -IncludeOnboarding Runs all tests including MDE onboarding status check. .EXAMPLE Test-MDEConfiguration -IncludePolicyVerification Runs all tests with policy registry verification sub-tests. .EXAMPLE Test-MDEConfiguration -Category 'Device State', 'Tamper Protection' Runs only Device State and Tamper Protection category tests. .EXAMPLE Test-MDEConfiguration -ExcludeTest '*SmartScreen*', 'Antispyware Signature Age' Runs all tests but excludes any result whose TestName matches *SmartScreen* or equals 'Antispyware Signature Age'. .OUTPUTS Array of PSCustomObjects with validation results. #> [CmdletBinding()] param( [Parameter()] [switch]$IncludeOnboarding, [Parameter()] [switch]$IncludePolicyVerification, [Parameter()] [ValidateSet('Device State', 'Protection Settings', 'Onboarding', 'Network Protection', 'ASR Rules', 'Tamper Protection', 'Exclusion Settings')] [string[]]$Category, [Parameter()] [string[]]$ExcludeTest ) $results = @() Write-Verbose "Starting MDE configuration validation..." # Check for elevation $isElevated = Test-IsElevated if (-not $isElevated) { Write-Warning "Some tests may require elevated privileges. Consider running as Administrator." } # Query the Defender CIM snapshots once and share them with every test that # accepts them. This avoids each test independently calling Get-MpPreference / # Get-MpComputerStatus (30-40 CIM round-trips reduced to 2). On failure the # snapshot is left as $null so the individual tests self-query and handle the # failure exactly as they do when called standalone. $mpPreference = $null try { $mpPreference = Get-MpPreference -ErrorAction Stop } catch { Write-Verbose "Get-MpPreference unavailable; tests will self-query: $_" } $mpStatus = $null try { $mpStatus = Get-MpComputerStatus -ErrorAction Stop } catch { Write-Verbose "Get-MpComputerStatus unavailable; tests will self-query: $_" } # Each descriptor: Category, PrimaryTestName, ScriptBlock, PolicyVerification (hashtable or $null) # Category is used for -Category filter (BEFORE invocation). # PolicyVerification sub-test runs only when its descriptor's ScriptBlock was invoked AND # -IncludePolicyVerification is set. $testDescriptors = [System.Collections.Generic.List[hashtable]]::new() $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'Service Status' ScriptBlock = { Test-MDEServiceStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'Passive Mode' ScriptBlock = { Test-MDEPassiveMode -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'Anti-Spyware Protection' ScriptBlock = { Test-MDEAntiSpywareEnabled -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'IOAV Protection' ScriptBlock = { Test-MDEIoavProtectionEnabled -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'Network Inspection System (NIS)' ScriptBlock = { Test-MDENISEnabled -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Antivirus Signature Age' ScriptBlock = { Test-MDESignatureAge -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Antivirus Signature Version' ScriptBlock = { Test-MDESignatureInfo -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Device State' PrimaryTestName = 'Real-Time Protection' ScriptBlock = { Test-MDERealTimeProtection -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Real-Time Protection' SettingKey = 'RealTimeProtection' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Cloud-Delivered Protection' ScriptBlock = { Test-MDECloudProtection -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Cloud-Delivered Protection' SettingKey = 'CloudProtection' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Cloud Block Level' ScriptBlock = { Test-MDECloudBlockLevel -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Cloud Block Level' SettingKey = 'CloudBlockLevel' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Cloud Extended Timeout' ScriptBlock = { Test-MDECloudExtendedTimeout -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Cloud Extended Timeout' SettingKey = 'CloudExtendedTimeout' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Automatic Sample Submission' ScriptBlock = { Test-MDESampleSubmission -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Automatic Sample Submission' SettingKey = 'SampleSubmission' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Behavior Monitoring' ScriptBlock = { Test-MDEBehaviorMonitoring -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Behavior Monitoring' SettingKey = 'BehaviorMonitoring' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'Network Protection' ScriptBlock = { Test-MDENetworkProtection -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Network Protection' SettingKey = 'NetworkProtection' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'PUA Protection' ScriptBlock = { Test-MDEPUAProtection -MpPreference $mpPreference } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'Network Protection (Windows Server)' ScriptBlock = { Test-MDENetworkProtectionWindowsServer } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'Datagram Processing (Windows Server)' ScriptBlock = { Test-MDEDatagramProcessingWindowsServer } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Exclusion Settings' PrimaryTestName = 'Auto Exclusions (Windows Server)' ScriptBlock = { Test-MDEAutoExclusionsWindowsServer -MpPreference $mpPreference } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'ASR Rules' PrimaryTestName = 'Attack Surface Reduction Rules' ScriptBlock = { Test-MDEAttackSurfaceReduction -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Attack Surface Reduction Rules' SettingKey = 'AttackSurfaceReduction' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Threat Default Actions' ScriptBlock = { Test-MDEThreatDefaultActions -MpPreference $mpPreference -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Tamper Protection' PrimaryTestName = 'Troubleshooting Mode' ScriptBlock = { Test-MDETroubleshootingMode -MpPreference $mpPreference } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Tamper Protection' PrimaryTestName = 'Tamper Protection' ScriptBlock = { Test-MDETamperProtection -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Tamper Protection' PrimaryTestName = 'Tamper Protection for Exclusions' ScriptBlock = { Test-MDETamperProtectionForExclusions -MpComputerStatus $mpStatus } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Exclusion Settings' PrimaryTestName = 'Exclusion Visibility (Local Admins)' ScriptBlock = { Test-MDEExclusionVisibilityLocalAdmins -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Exclusion Visibility (Local Admins)' SettingKey = 'HideExclusionsFromLocalAdmins' IsApplicableToSSM = $false } }) $testDescriptors.Add(@{ Category = 'Exclusion Settings' PrimaryTestName = 'Exclusion Visibility (Local Users)' ScriptBlock = { Test-MDEExclusionVisibilityLocalUsers -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Exclusion Visibility (Local Users)' SettingKey = 'HideExclusionsFromLocalUsers' IsApplicableToSSM = $false } }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen' ScriptBlock = { Test-MDESmartScreen } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen PUA' ScriptBlock = { Test-MDESmartScreenPUA } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen Prompt Override' ScriptBlock = { Test-MDESmartScreenPromptOverride } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen Download Override' ScriptBlock = { Test-MDESmartScreenDownloadOverride } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen Domain Exclusions' ScriptBlock = { Test-MDESmartScreenDomainExclusions } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Network Protection' PrimaryTestName = 'SmartScreen App Reputation Exclusions' ScriptBlock = { Test-MDESmartScreenAppRepExclusions } PolicyVerification = $null }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Catchup Quick Scan' ScriptBlock = { Test-MDEDisableCatchupQuickScan -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Catchup Quick Scan' SettingKey = 'CatchupQuickScan' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Real Time Scan Direction' ScriptBlock = { Test-MDERealTimeScanDirection -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Real Time Scan Direction' SettingKey = 'RealTimeScanDirection' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Signature Update Fallback Order' ScriptBlock = { Test-MDESignatureUpdateFallbackOrder -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Signature Update Fallback Order' SettingKey = 'SignatureFallbackOrder' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Signature Update Interval' ScriptBlock = { Test-MDESignatureUpdateInterval -MpPreference $mpPreference } PolicyVerification = @{ ParentTestName = 'Signature Update Interval' SettingKey = 'SignatureUpdateInterval' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'Disable Local Admin Merge' ScriptBlock = { Test-MDEDisableLocalAdminMerge } PolicyVerification = @{ ParentTestName = 'Disable Local Admin Merge' SettingKey = 'DisableLocalAdminMerge' IsApplicableToSSM = $true } }) $testDescriptors.Add(@{ Category = 'Protection Settings' PrimaryTestName = 'File Hash Computation' ScriptBlock = { Test-MDEFileHashComputation -MpPreference $mpPreference } PolicyVerification = $null }) if ($IncludeOnboarding) { $testDescriptors.Add(@{ Category = 'Onboarding' PrimaryTestName = 'MDE Onboarding Status' ScriptBlock = { Test-MDEOnboardingStatus } PolicyVerification = $null }) } $testDescriptors.Add(@{ Category = 'Onboarding' PrimaryTestName = 'MDE Device Tags' ScriptBlock = { Test-MDEDeviceTags } PolicyVerification = $null }) # Apply -Category filter before invocation $testsToRun = @(if ($Category.Count -gt 0) { $testDescriptors | Where-Object { $_.Category -in $Category } } else { $testDescriptors }) $total = $testsToRun.Count for ($i = 0; $i -lt $total; $i++) { $descriptor = $testsToRun[$i] $pctComplete = if ($total -gt 0) { [int](($i / $total) * 100) } else { 0 } Write-Progress -Activity 'MDE Validation' -Status $descriptor.PrimaryTestName -PercentComplete $pctComplete $testResults = @(& $descriptor.ScriptBlock) # Apply -ExcludeTest filter after invocation (handles multi-result tests consistently) if ($ExcludeTest.Count -gt 0) { $testResults = @($testResults | Where-Object { $tn = $_.TestName -not ($ExcludeTest | Where-Object { $tn -like $_ }) }) } $results += $testResults # Run policy verification sub-test when the parent was invoked and the flag is set if ($IncludePolicyVerification -and $null -ne $descriptor.PolicyVerification) { $pv = $descriptor.PolicyVerification $pvResult = @(Test-MDEPolicyRegistryVerification -ParentTestName $pv.ParentTestName ` -SettingKey $pv.SettingKey -IsApplicableToSSM $pv.IsApplicableToSSM) if ($ExcludeTest.Count -gt 0) { $pvResult = @($pvResult | Where-Object { $tn = $_.TestName -not ($ExcludeTest | Where-Object { $tn -like $_ }) }) } $results += $pvResult } } Write-Progress -Activity 'MDE Validation' -Completed Write-Verbose "MDE configuration validation completed." return $results } |