Public/Test-DefenderDcPolicy.ps1
|
function Test-DefenderDcPolicy { <# .SYNOPSIS Verify the Defender Device Control policy state on this machine. .DESCRIPTION Reads registry + Defender engine state, prints PASS/FAIL per check, returns boolean (true if all checks passed). Prints a dynamic-test recipe the operator can drive with a real USB stick. .PARAMETER ExpectMode Audit, Enforce, or Off. The state we expect to find. Default Audit. .EXAMPLE Test-DefenderDcPolicy -ExpectMode Audit Verify policy is in Audit mode and the engine has consumed it. .EXAMPLE Test-DefenderDcPolicy -ExpectMode Off Verify the policy has been fully removed. .LINK https://lukeevanstech.github.io/defender-device-control-unmanaged/ #> [CmdletBinding()] [OutputType([bool])] param( [Alias('Mode')] [ValidateSet('Audit','Enforce','Off')] [string] $ExpectMode = 'Audit' ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $failures = 0 $failureRef = [ref]$failures Write-Host "Defender Device Control verification (ExpectMode=$ExpectMode)" -ForegroundColor Yellow $status = Get-DcComputerStatus Write-DcCheckResult 'Defender AM service enabled' { $status.AMServiceEnabled } 'AMServiceEnabled must be True' $failureRef Write-DcCheckResult 'Defender antivirus enabled' { $status.AntivirusEnabled } 'AntivirusEnabled must be True' $failureRef if ($ExpectMode -eq 'Off') { Write-DcCheckResult 'Device Control root key absent' { -not (Test-Path -LiteralPath $script:DcRoot) } "$script:DcRoot should not exist" $failureRef $featVal = Get-DcRegistryValue -Path $script:DcFeatures -Name DeviceControlEnabled Write-DcCheckResult 'Features\DeviceControlEnabled absent or 0' { $null -eq $featVal -or $featVal -eq 0 } 'Features\DeviceControlEnabled must be 0 or absent' $failureRef Write-DcCheckResult 'Engine reports DeviceControlState=Disabled' { $prop = $status.PSObject.Properties['DeviceControlState'] $null -eq $prop -or $prop.Value -eq 'Disabled' -or $prop.Value -eq 0 } 'engine view should reflect that DC is disabled' $failureRef } else { $featVal = Get-DcRegistryValue -Path $script:DcFeatures -Name DeviceControlEnabled $defEnf = Get-DcRegistryValue -Path $script:DcRoot -Name DefaultEnforcement $secured = Get-DcRegistryValue -Path $script:DcRoot -Name SecuredDevicesConfiguration $groupsXml = Get-DcRegistryValue -Path $script:DcGroupsKey -Name PolicyGroups $rulesXml = Get-DcRegistryValue -Path $script:DcRulesKey -Name PolicyRules Write-DcCheckResult 'Features\DeviceControlEnabled = 1' { $featVal -eq 1 } 'must be 1' $failureRef Write-DcCheckResult 'Device Control\DefaultEnforcement = 1 (Allow)' { $defEnf -eq 1 } 'must be 1' $failureRef Write-DcCheckResult 'SecuredDevicesConfiguration scopes Removable + CdRom + Wpd' { $secured -eq 'RemovableMediaDevices|CdRomDevices|WpdDevices' } 'must be exactly "RemovableMediaDevices|CdRomDevices|WpdDevices"' $failureRef Write-DcCheckResult 'Policy Groups\PolicyGroups REG_SZ exists' { -not [string]::IsNullOrEmpty($groupsXml) } 'must be a non-empty string' $failureRef Write-DcCheckResult 'Policy Groups XML file exists at registered path' { -not [string]::IsNullOrEmpty($groupsXml) -and (Test-Path -LiteralPath $groupsXml -PathType Leaf) } 'file at the registered path must exist' $failureRef Write-DcCheckResult 'Policy Groups XML parses as 3 Group records' { if ([string]::IsNullOrEmpty($groupsXml) -or -not (Test-Path -LiteralPath $groupsXml -PathType Leaf)) { return $false } @(Read-DcPolicyXml -Path $groupsXml).Count -eq 3 } 'must contain exactly 3 <Group> elements' $failureRef Write-DcCheckResult 'Policy Rules\PolicyRules REG_SZ exists' { -not [string]::IsNullOrEmpty($rulesXml) } 'must be a non-empty string' $failureRef Write-DcCheckResult 'Policy Rules XML file exists at registered path' { -not [string]::IsNullOrEmpty($rulesXml) -and (Test-Path -LiteralPath $rulesXml -PathType Leaf) } 'file at the registered path must exist' $failureRef Write-DcCheckResult 'Policy Rules XML parses as 3 PolicyRule records' { if ([string]::IsNullOrEmpty($rulesXml) -or -not (Test-Path -LiteralPath $rulesXml -PathType Leaf)) { return $false } @(Read-DcPolicyXml -Path $rulesXml).Count -eq 3 } 'must contain exactly 3 <PolicyRule> elements' $failureRef $expectedType = if ($ExpectMode -eq 'Audit') { 'AuditAllowed' } else { 'Deny' } Write-DcCheckResult "Rules XML uses Entry Type=$expectedType (matches ExpectMode)" { if ([string]::IsNullOrEmpty($rulesXml) -or -not (Test-Path -LiteralPath $rulesXml -PathType Leaf)) { return $false } foreach ($r in (Read-DcPolicyXml -Path $rulesXml)) { if ($r.EntryTypes -notcontains $expectedType) { return $false } } $true } "Mode $ExpectMode requires at least one $expectedType entry per rule" $failureRef Write-DcCheckResult 'Engine reports DeviceControlState != Disabled' { $prop = $status.PSObject.Properties['DeviceControlState'] $null -ne $prop -and $prop.Value -ne 'Disabled' -and $prop.Value -ne 0 } 'Get-MpComputerStatus.DeviceControlState must reflect engine consumed policy' $failureRef Write-DcCheckResult 'Engine reports DeviceControlPoliciesLastUpdated is recent (not 1601 sentinel)' { $prop = $status.PSObject.Properties['DeviceControlPoliciesLastUpdated'] if ($null -eq $prop) { return $false } $v = $prop.Value if ($null -eq $v) { return $false } try { $dt = [datetime]$v } catch { return $false } $dt.Year -ge 2000 } '1601-01-01 sentinel means engine never read the policy' $failureRef } Write-Host "" if ($failureRef.Value -eq 0) { Write-Host "Static checks: ALL PASSED" -ForegroundColor Green } else { Write-Host "Static checks: $($failureRef.Value) FAILED" -ForegroundColor Red } Write-Host "" Write-Host "Dynamic verification (operator drives, USB stick required):" -ForegroundColor Yellow Write-Host " 1. Plug in a USB stick. It should mount and get a drive letter." Write-Host " 2. Open a file from the stick (Read should always work)." Write-Host " 3. Try to copy a file TO the stick." Write-Host " Audit mode: succeeds, Defender XDR Advanced Hunting records the event." Write-Host " Enforce mode: fails with 'The media is write protected', toast shows." return ($failureRef.Value -eq 0) } |