Dargslan.WinFirewallAudit.psm1
|
<# .SYNOPSIS Audit Windows Firewall profiles and rules. Detect risky Allow Any Any rules, disabled profiles and GPO overrides. JSON / HTML compliance report. .DESCRIPTION Part of the Dargslan Windows Admin Tools collection. Free Cheat Sheet: https://dargslan.com/cheat-sheets/windows-firewall-audit-2026 Full Guide: https://dargslan.com/blog/windows-firewall-audit-powershell-2026 More tools: https://dargslan.com .LINK https://dargslan.com .LINK https://github.com/Dargslan/powershell-admin-scripts #> $script:Banner = @" +----------------------------------------------------------+ | Dargslan Windows Firewall Audit | https://dargslan.com - Free cheat sheets & eBooks | +----------------------------------------------------------+ "@ function Get-DargslanFirewallProfile { <# .SYNOPSIS Return the state of all three firewall profiles (Domain / Private / Public). #> [CmdletBinding()] param() Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction, AllowInboundRules, NotifyOnListen, LogAllowed, LogBlocked, LogFileName } function Get-DargslanFirewallRules { <# .SYNOPSIS Return enabled inbound rules with the resolved port / protocol / address. #> [CmdletBinding()] param([ValidateSet('Inbound','Outbound')]$Direction = 'Inbound') Get-NetFirewallRule -Direction $Direction -Enabled True | ForEach-Object { $f = $_ | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue $a = $_ | Get-NetFirewallAddressFilter -ErrorAction SilentlyContinue [pscustomobject]@{ Name = $_.DisplayName Action = $_.Action Profile = $_.Profile Protocol = $f.Protocol LocalPort = ($f.LocalPort -join ',') RemoteAddr = ($a.RemoteAddress -join ',') Program = ($_ | Get-NetFirewallApplicationFilter -ErrorAction SilentlyContinue).Program } } } function Get-DargslanFirewallRiskyRules { <# .SYNOPSIS Detect Allow rules that are too permissive (Any protocol, Any address, Any port). #> [CmdletBinding()] param() Get-DargslanFirewallRules -Direction Inbound | Where-Object { $_.Action -eq 'Allow' -and ($_.RemoteAddr -eq 'Any' -or [string]::IsNullOrEmpty($_.RemoteAddr)) -and ($_.LocalPort -eq 'Any' -or [string]::IsNullOrEmpty($_.LocalPort)) } } function Get-DargslanFirewallAuditReport { <# .SYNOPSIS Combined audit object with score and PASS / WARN / FAIL verdict. #> [CmdletBinding()] param() $profiles = Get-DargslanFirewallProfile $risky = @(Get-DargslanFirewallRiskyRules) $enabled = ($profiles | Where-Object Enabled).Count $score = 0 if ($enabled -eq 3) { $score++ } if (($profiles | Where-Object DefaultInboundAction -eq 'Block').Count -eq 3) { $score++ } if ($risky.Count -eq 0) { $score++ } if (($profiles | Where-Object LogBlocked -eq 'True').Count -ge 1) { $score++ } $verdict = if ($score -ge 4) { 'PASS' } elseif ($score -ge 2) { 'WARN' } else { 'FAIL' } [pscustomobject]@{ ComputerName = $env:COMPUTERNAME TimeStamp = (Get-Date).ToString('s') ProfilesEnabled = $enabled RiskyRuleCount = $risky.Count Score = $score Verdict = $verdict Profiles = $profiles RiskyRules = $risky } } function Export-DargslanFirewallAuditReport { <# .SYNOPSIS Export the audit report to HTML and JSON. #> [CmdletBinding()] param([string]$OutDir = (Join-Path $env:TEMP 'DargslanFirewallAudit')) if (-not (Test-Path $OutDir)) { New-Item -Type Directory -Path $OutDir | Out-Null } $r = Get-DargslanFirewallAuditReport $json = Join-Path $OutDir ('firewall-' + $env:COMPUTERNAME + '.json') $html = Join-Path $OutDir ('firewall-' + $env:COMPUTERNAME + '.html') $r | ConvertTo-Json -Depth 6 | Set-Content -Path $json -Encoding UTF8 $body = "<h1>Firewall Audit - $($r.ComputerName)</h1><p>Verdict: <b>$($r.Verdict)</b> ($($r.Score)/4)</p>" $body += ($r.Profiles | ConvertTo-Html -Fragment) $body += '<h2>Risky Rules</h2>' $body += ($r.RiskyRules | ConvertTo-Html -Fragment) ConvertTo-Html -Body $body -Title 'Firewall Audit' | Set-Content -Path $html -Encoding UTF8 [pscustomobject]@{ Json = $json; Html = $html; Verdict = $r.Verdict } } |