Dargslan.WinDefenderAudit.psm1

<#
.SYNOPSIS
    Audit Microsoft Defender configuration, ASR rule state, exclusions and tamper protection. JSON / HTML compliance report.

.DESCRIPTION
    Part of the Dargslan Windows Admin Tools collection.
    Free Cheat Sheet: https://dargslan.com/cheat-sheets/windows-defender-asr-audit-2026
    Full Guide: https://dargslan.com/blog/windows-defender-asr-audit-powershell-2026
    More tools: https://dargslan.com

.LINK
    https://dargslan.com

.LINK
    https://github.com/Dargslan/powershell-admin-scripts
#>


$script:Banner = @"
+----------------------------------------------------------+
| Dargslan Windows Defender + ASR Audit
| https://dargslan.com - Free cheat sheets & eBooks |
+----------------------------------------------------------+
"@


function Get-DargslanDefenderStatus {
    <#
    .SYNOPSIS
        Return real-time protection, signatures, tamper protection and engine state.
    #>

    [CmdletBinding()]
    param()
    $s = Get-MpComputerStatus
    [pscustomobject]@{
        ComputerName              = $env:COMPUTERNAME
        AntivirusEnabled          = $s.AntivirusEnabled
        RealTimeProtectionEnabled = $s.RealTimeProtectionEnabled
        BehaviorMonitorEnabled    = $s.BehaviorMonitorEnabled
        IoavProtectionEnabled     = $s.IoavProtectionEnabled
        TamperProtected           = $s.IsTamperProtected
        AMServiceEnabled          = $s.AMServiceEnabled
        SignatureAge              = $s.AntivirusSignatureAge
        EngineVersion             = $s.AMEngineVersion
        SignatureVersion          = $s.AntivirusSignatureVersion
    }
}

function Get-DargslanAsrRules {
    <#
    .SYNOPSIS
        Return all ASR rule IDs with friendly name and current state (Disabled / Block / Audit / Warn).
    #>

    [CmdletBinding()]
    param()
    $names = @{
        'BE9BA2D9-53EA-4CDC-84E5-9B1EEEE46550' = 'Block exec content from email'
        'D4F940AB-401B-4EFC-AADC-AD5F3C50688A' = 'Block child processes from Office'
        '3B576869-A4EC-4529-8536-B80A7769E899' = 'Block Office creating exec content'
        '75668C1F-73B5-4CF0-BB93-3ECF5CB7CC84' = 'Block Office injection'
        'D3E037E1-3EB8-44C8-A917-57927947596D' = 'Block JS / VBS launching exec content'
        '5BEB7EFE-FD9A-4556-801D-275E5FFC04CC' = 'Block obfuscated scripts'
        '92E97FA1-2EDF-4476-BDD6-9DD0B4DDDC7B' = 'Block Win32 imports from macros'
        '01443614-CD74-433A-B99E-2ECDC07BFC25' = 'Block exec from unsigned USB'
        'C1DB55AB-C21A-4637-BB3F-A12568109D35' = 'Use advanced ransomware protection'
        '9E6C4E1F-7D60-472F-BA1A-A39EF669E4B2' = 'Block credential stealing from LSASS'
        'D1E49AAC-8F56-4280-B9BA-993A6D77406C' = 'Block process creation from PSExec / WMI'
        'B2B3F03D-6A65-4F7B-A9C7-1C7EF74A9BA4' = 'Block untrusted USB processes'
        '26190899-1602-49E8-8B27-EB1D0A1CE869' = 'Block Office comms apps creating child'
        '7674BA52-37EB-4A4F-A9A1-F0F9A1619A2C' = 'Block Adobe Reader child processes'
        'E6DB77E5-3DF2-4CF1-B95A-636979351E5B' = 'Block persistence via WMI subscription'
    }
    $pref = Get-MpPreference
    $ids = @($pref.AttackSurfaceReductionRules_Ids)
    $st  = @($pref.AttackSurfaceReductionRules_Actions)
    $map = @{ 0 = 'Disabled'; 1 = 'Block'; 2 = 'Audit'; 6 = 'Warn' }
    $names.GetEnumerator() | ForEach-Object {
        $idx = [Array]::IndexOf($ids, $_.Key)
        $state = if ($idx -ge 0) { $map[[int]$st[$idx]] } else { 'NotConfigured' }
        [pscustomobject]@{ Id = $_.Key; Name = $_.Value; State = $state }
    }
}

function Get-DargslanDefenderExclusions {
    <#
    .SYNOPSIS
        Return all configured Defender exclusions (paths, extensions, processes, IPs).
    #>

    [CmdletBinding()]
    param()
    $p = Get-MpPreference
    [pscustomobject]@{
        Paths      = @($p.ExclusionPath)
        Extensions = @($p.ExclusionExtension)
        Processes  = @($p.ExclusionProcess)
        IpAddresses= @($p.ExclusionIpAddress)
    }
}

function Get-DargslanDefenderAuditReport {
    <#
    .SYNOPSIS
        Combined Defender audit object with PASS / WARN / FAIL verdict.
    #>

    [CmdletBinding()]
    param()
    $st  = Get-DargslanDefenderStatus
    $asr = @(Get-DargslanAsrRules)
    $ex  = Get-DargslanDefenderExclusions
    $blocked = ($asr | Where-Object State -eq 'Block').Count
    $score = 0
    if ($st.RealTimeProtectionEnabled) { $score++ }
    if ($st.TamperProtected) { $score++ }
    if ($st.SignatureAge -le 3) { $score++ }
    if ($blocked -ge 8) { $score++ }
    if ($ex.Paths.Count -le 5) { $score++ }
    $verdict = if ($score -ge 4) { 'PASS' } elseif ($score -ge 2) { 'WARN' } else { 'FAIL' }
    [pscustomobject]@{
        Status     = $st
        AsrRules   = $asr
        Exclusions = $ex
        Score      = $score
        Verdict    = $verdict
        TimeStamp  = (Get-Date).ToString('s')
    }
}

function Export-DargslanDefenderAuditReport {
    <#
    .SYNOPSIS
        Export the audit report to HTML and JSON.
    #>

    [CmdletBinding()]
    param([string]$OutDir = (Join-Path $env:TEMP 'DargslanDefenderAudit'))
    if (-not (Test-Path $OutDir)) { New-Item -Type Directory -Path $OutDir | Out-Null }
    $r = Get-DargslanDefenderAuditReport
    $json = Join-Path $OutDir ('defender-' + $env:COMPUTERNAME + '.json')
    $html = Join-Path $OutDir ('defender-' + $env:COMPUTERNAME + '.html')
    $r | ConvertTo-Json -Depth 6 | Set-Content $json -Encoding UTF8
    $body  = "<h1>Defender Audit - $($r.Status.ComputerName)</h1>"
    $body += "<p>Verdict: <b>$($r.Verdict)</b> ($($r.Score)/5)</p>"
    $body += ($r.Status   | ConvertTo-Html -Fragment)
    $body += '<h2>ASR Rules</h2>' + ($r.AsrRules | ConvertTo-Html -Fragment)
    $body += '<h2>Exclusions</h2><pre>' + (($r.Exclusions | ConvertTo-Json -Depth 4)) + '</pre>'
    ConvertTo-Html -Body $body -Title 'Defender Audit' | Set-Content $html -Encoding UTF8
    [pscustomobject]@{ Json = $json; Html = $html; Verdict = $r.Verdict }
}