# Windows 10, normal boot, elevated. # Exit 0 only when RESULT: PASS is printed. # Safe Mode fails this check even when Start is already 4. # Get-MpComputerStatus is non-authoritative and cannot stop the later checks. # WdNisDrv passes only when sc qc reports START_TYPE 4. # STATE STOPPED and Win32 exit 1077 do not prove Start is 4. # An empty Defender task list is a failure. function Get-ScStartType { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $match = [regex]::Match($Text, '(?m)START_TYPE\s*:\s*(\d+)') if (-not $match.Success) { return $null } return [int]$match.Groups[1].Value } function Get-ScState { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $match = [regex]::Match($Text, '(?m)STATE\s*:\s*(\d+)') if (-not $match.Success) { return $null } return [int]$match.Groups[1].Value } function Get-ScWin32Exit { param([string]$Text) if ([string]::IsNullOrWhiteSpace($Text)) { return $null } $match = [regex]::Match($Text, '(?m)WIN32_EXIT_CODE\s*:\s*(\d+)') if (-not $match.Success) { return $null } return [int]$match.Groups[1].Value } function Test-WdFilterListed { param([string]$FltmcText) if ([string]::IsNullOrWhiteSpace($FltmcText)) { return $null } foreach ($line in ($FltmcText -split '\r?\n')) { $trim = $line.Trim() if ($trim -match '^(?i)WdFilter(\s|$)') { return $true } } return $false } function Test-DefenderDisabled { param($Snapshot) $failures = New-Object System.Collections.Generic.List[string] $notes = New-Object System.Collections.Generic.List[string] if ($Snapshot.InSafeMode -eq $true) { [void]$failures.Add('Safe Mode is still set. This check passes only after a normal boot.') } elseif ($Snapshot.InSafeMode -ne $false) { [void]$failures.Add('Safe Mode state was not read.') } foreach ($name in @('WinDefend', 'WdNisSvc')) { $svc = $Snapshot.Services[$name] if ($null -eq $svc) { [void]$failures.Add("$name was not found.") continue } $status = [string]$svc.Status $startType = [string]$svc.StartType if ($status -ne 'Stopped' -or $startType -ne 'Disabled') { [void]$failures.Add("$name is $status, StartType $startType. Required: Stopped, Disabled.") } } $sense = $Snapshot.Services['Sense'] if ($null -eq $sense) { [void]$notes.Add('Sense is missing. That is allowed when Defender for Endpoint was never installed.') } else { $status = [string]$sense.Status $startType = [string]$sense.StartType if ($status -ne 'Stopped' -or $startType -ne 'Disabled') { [void]$failures.Add( "Sense is $status, StartType $startType. " + 'Required: Stopped and Disabled, or missing.') } } if ($Snapshot.MsMpEngRunning -eq $true) { [void]$failures.Add('MsMpEng.exe is running.') } elseif ($Snapshot.MsMpEngRunning -ne $false) { [void]$failures.Add('MsMpEng.exe state was not read.') } foreach ($name in @('WdFilter', 'WdBoot', 'WdNisDrv')) { $start = Get-ScStartType -Text $Snapshot.QcText[$name] if ($start -ne 4) { $shown = if ($null -eq $start) { 'unreadable' } else { [string]$start } [void]$failures.Add("$name START_TYPE is $shown. Required: 4.") } } $listed = Test-WdFilterListed -FltmcText $Snapshot.FltmcText if ($null -eq $listed) { [void]$failures.Add('fltmc did not return a minifilter list, so WdFilter cannot be confirmed absent.') } elseif ($listed) { [void]$failures.Add('fltmc lists WdFilter. The minifilter is loaded.') } if ($null -eq $Snapshot.Tasks) { [void]$failures.Add('No Defender tasks were returned. An empty task list fails this check.') } else { $real = @($Snapshot.Tasks | Where-Object { $null -ne $_ }) if ($real.Count -eq 0) { [void]$failures.Add('No Defender tasks were returned. An empty task list fails this check.') } foreach ($task in $real) { $state = [string]$task.State $taskName = [string]$task.TaskName if ([string]::IsNullOrWhiteSpace($taskName)) { $taskName = '(unnamed task)' } if ($state -ne 'Disabled') { $shown = if ([string]::IsNullOrWhiteSpace($state)) { 'unknown' } else { $state } [void]$failures.Add("$taskName = $shown. Required: Disabled.") } } } [pscustomobject]@{ Passed = ($failures.Count -eq 0) Failures = $failures.ToArray() Notes = $notes.ToArray() } } function Format-DefenderDisabledReport { param($Snapshot) $verdict = Test-DefenderDisabled -Snapshot $Snapshot $lines = New-Object System.Collections.Generic.List[string] if ($Snapshot.InSafeMode -eq $true) { [void]$lines.Add('Safe Mode: yes') } elseif ($Snapshot.InSafeMode -eq $false) { [void]$lines.Add('Safe Mode: no') } else { [void]$lines.Add('Safe Mode: unread') } foreach ($name in @('WinDefend', 'WdNisSvc')) { $svc = $Snapshot.Services[$name] if ($null -eq $svc) { [void]$lines.Add("${name}: not found") } else { [void]$lines.Add("${name}: $($svc.Status), $($svc.StartType)") } } $sense = $Snapshot.Services['Sense'] if ($null -eq $sense) { [void]$lines.Add('Sense: missing') } else { [void]$lines.Add("Sense: $($sense.Status), $($sense.StartType)") } if ($Snapshot.MsMpEngRunning -eq $true) { [void]$lines.Add('MsMpEng.exe: running') } elseif ($Snapshot.MsMpEngRunning -eq $false) { [void]$lines.Add('MsMpEng.exe: not running') } else { [void]$lines.Add('MsMpEng.exe: unread') } foreach ($name in @('WdFilter', 'WdBoot', 'WdNisDrv')) { $start = Get-ScStartType -Text $Snapshot.QcText[$name] $shown = if ($null -eq $start) { 'unreadable' } else { [string]$start } [void]$lines.Add("${name} START_TYPE: $shown") } $query = $null if ($Snapshot.QueryText) { $query = $Snapshot.QueryText['WdNisDrv'] } $state = Get-ScState -Text $query $win32 = Get-ScWin32Exit -Text $query $stateShown = if ($null -eq $state) { 'unreadable' } else { [string]$state } $exitShown = if ($null -eq $win32) { 'unreadable' } else { [string]$win32 } [void]$lines.Add("WdNisDrv runtime (informational): STATE $stateShown, Win32 exit $exitShown") $listed = Test-WdFilterListed -FltmcText $Snapshot.FltmcText if ($null -eq $listed) { [void]$lines.Add('fltmc WdFilter: unread') } elseif ($listed) { [void]$lines.Add('fltmc WdFilter: listed') } else { [void]$lines.Add('fltmc WdFilter: absent') } [void]$lines.Add('driverquery (informational; a match is not a failure):') if ([string]::IsNullOrWhiteSpace([string]$Snapshot.DriverQueryText)) { [void]$lines.Add('(no driverquery output)') } else { foreach ($driverLine in ([string]$Snapshot.DriverQueryText -split '\r?\n')) { if (-not [string]::IsNullOrWhiteSpace($driverLine)) { [void]$lines.Add($driverLine.TrimEnd()) } } } [void]$lines.Add('Get-MpComputerStatus (non-authoritative):') if ([string]::IsNullOrWhiteSpace([string]$Snapshot.MpText)) { [void]$lines.Add('(no status)') } else { foreach ($statusLine in ([string]$Snapshot.MpText -split '\r?\n')) { if (-not [string]::IsNullOrWhiteSpace($statusLine)) { [void]$lines.Add($statusLine.TrimEnd()) } } } [void]$lines.Add('Tasks:') if ($null -eq $Snapshot.Tasks) { [void]$lines.Add('No tasks returned') } else { $real = @($Snapshot.Tasks | Where-Object { $null -ne $_ }) if ($real.Count -eq 0) { [void]$lines.Add('No tasks returned') } foreach ($task in $real) { [void]$lines.Add("$($task.TaskName) = $($task.State)") } } foreach ($failure in @($verdict.Failures)) { [void]$lines.Add("FAIL: $failure") } if ($verdict.Passed) { [void]$lines.Add('RESULT: PASS') $code = 0 } else { [void]$lines.Add('RESULT: FAIL') $code = 1 } [pscustomobject]@{ Lines = $lines.ToArray() Passed = [bool]$verdict.Passed Code = $code } } function Get-NativeCommandText { param([Parameter(Mandatory)][scriptblock]$Command) try { $output = & $Command 2>&1 | Out-String if ($LASTEXITCODE -ne 0) { return $null } return $output } catch { return $null } } function Get-OneServiceState { param([Parameter(Mandatory)][string]$Name) try { $svc = Get-Service -Name $Name -ErrorAction Stop return @{ Status = [string]$svc.Status; StartType = [string]$svc.StartType } } catch { return $null } } function Get-DefenderDisabledSnapshot { if ($env:DEFENDER_PROCEDURE_UNDER_TEST -eq '1') { throw 'Refusing to query the live machine during tests.' } $snapshot = @{ InSafeMode = $false Services = @{} MsMpEngRunning = $false QcText = @{} QueryText = @{} FltmcText = $null DriverQueryText = $null MpText = $null Tasks = $null } try { $safeBootKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\SafeBoot\Option' $opt = Get-ItemProperty -LiteralPath $safeBootKey -ErrorAction Stop if ($opt.PSObject.Properties.Name -contains 'OptionValue' -and $null -ne $opt.OptionValue) { $snapshot.InSafeMode = $true } } catch { $snapshot.InSafeMode = $false } foreach ($name in @('WinDefend', 'WdNisSvc', 'Sense')) { $snapshot.Services[$name] = Get-OneServiceState -Name $name } try { $processes = @(Get-Process -Name MsMpEng -ErrorAction SilentlyContinue | Where-Object { $null -ne $_ }) $snapshot.MsMpEngRunning = ($processes.Count -gt 0) } catch { $snapshot.MsMpEngRunning = $null } foreach ($name in @('WdFilter', 'WdBoot', 'WdNisDrv')) { $snapshot.QcText[$name] = Get-NativeCommandText -Command ([scriptblock]::Create("sc.exe qc $name")) } $snapshot.QueryText['WdNisDrv'] = Get-NativeCommandText -Command { sc.exe query WdNisDrv } $snapshot.FltmcText = Get-NativeCommandText -Command { fltmc } try { $driverOutput = & cmd.exe /c 'driverquery | findstr /i "WdFilter WdBoot WdNis"' 2>&1 | Out-String $snapshot.DriverQueryText = $driverOutput } catch { $snapshot.DriverQueryText = $null } try { $mp = Get-MpComputerStatus -ErrorAction Stop | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, IsTamperProtected | Format-List | Out-String $snapshot.MpText = $mp.Trim() } catch { $snapshot.MpText = "Get-MpComputerStatus threw: $($_.Exception.Message)" } try { $taskPath = '\Microsoft\Windows\Windows Defender\' $snapshot.Tasks = @(Get-ScheduledTask -TaskPath $taskPath -ErrorAction Stop) } catch { $snapshot.Tasks = $null } return $snapshot } # Script-scope entry. Dot-sourcing loads the functions and does not change the machine. # DEFENDER_PROCEDURE_UNDER_TEST is set only by the offline test runner. if ($MyInvocation.InvocationName -ne '.') { if ($env:DEFENDER_PROCEDURE_UNDER_TEST -eq '1') { throw 'Refusing to run the live procedure during tests.' } $liveSnapshot = Get-DefenderDisabledSnapshot $liveReport = Format-DefenderDisabledReport -Snapshot $liveSnapshot foreach ($liveLine in @($liveReport.Lines)) { Write-Output $liveLine } if ($PSCommandPath) { exit $liveReport.Code } }