"The Windows Firewall is on" is one of the most misleading statements in a security report. The firewall is almost always on. The interesting questions are: which profile is active, what does the default inbound action actually do, how many Allow Any Any rules has someone added over the years, are GPO settings overriding your local hardening, and is anything actually being logged when a packet is dropped?
This guide walks through a defensible Windows Firewall audit you can run from PowerShell, explains what each finding means, and ships a free PDF cheat sheet plus the Dargslan.WinFirewallAudit module so you do not have to write any of it yourself.
Table of Contents
Why a real firewall audit is needed
The CIS Microsoft Windows benchmark and NIST 800-171 control 3.13.1 both expect evidence that inbound traffic is denied by default and that exceptions are documented. In the field, the failure mode is almost never "the firewall was off". It is "someone added an Allow Any Any rule on port 3389 for the Public profile to fix a remote access ticket in 2022 and nobody removed it". That single rule defeats the entire control.
A real audit therefore has to enumerate every rule, classify it, and flag the dangerous ones. Doing that from the GUI is impractical on more than one machine. From PowerShell it is a 30-line script.
Step 1: Audit the three profiles
Windows tracks three firewall profiles — Domain, Private, Public. Each has its own enabled flag, default inbound / outbound action, and logging configuration. The cmdlet is Get-NetFirewallProfile:
Get-NetFirewallProfile | Select-Object Name, Enabled,
DefaultInboundAction, DefaultOutboundAction,
LogAllowed, LogBlocked, LogFileName
The minimum acceptable baseline is: all three profiles enabled, DefaultInboundAction = Block on all three, DefaultOutboundAction = Allow (the standard Windows posture), and LogBlocked = True on at least the Public profile. Anything else is a finding.
Step 2: Inventory the enabled rules
Once the profiles look sane, list every enabled inbound rule and resolve its port, protocol, address and program. Get-NetFirewallRule gives you the rule but the filters are separate cmdlets — you have to join them:
Get-NetFirewallRule -Direction Inbound -Enabled True |
ForEach-Object {
$f = $_ | Get-NetFirewallPortFilter
$a = $_ | Get-NetFirewallAddressFilter
[pscustomobject]@{
Name = $_.DisplayName
Action = $_.Action
Profile = $_.Profile
Protocol = $f.Protocol
LocalPort = ($f.LocalPort -join ',')
RemoteAddr = ($a.RemoteAddress -join ',')
}
}
Sort by RemoteAddr and then by LocalPort. Anything where both are Any jumps out immediately — that is the next section.
Step 3: Find Allow Any Any rules
The single most common firewall finding is an Allow rule with no port restriction and no source restriction. It usually arrives as a workaround for a broken application and stays forever. The detection rule is simple: action is Allow, RemoteAddress is Any (or empty), LocalPort is Any (or empty).
Get-NetFirewallRule -Direction Inbound -Enabled True -Action Allow |
ForEach-Object {
$a = $_ | Get-NetFirewallAddressFilter
$f = $_ | Get-NetFirewallPortFilter
if (($a.RemoteAddress -eq 'Any' -or -not $a.RemoteAddress) -and
($f.LocalPort -eq 'Any' -or -not $f.LocalPort)) {
[pscustomobject]@{ Rule = $_.DisplayName; Profile = $_.Profile }
}
}
Every hit deserves a one-line justification in your audit document. If you cannot justify it, remove it.
Step 4: Detect GPO overrides
The other classic gotcha is local hardening being silently overridden by Group Policy. Get-NetFirewallProfile -PolicyStore ActiveStore returns the effective policy after GPO merge, while -PolicyStore PersistentStore returns the local config. If they differ, GPO wins:
$local = Get-NetFirewallProfile -PolicyStore PersistentStore
$active = Get-NetFirewallProfile -PolicyStore ActiveStore
Compare-Object $local $active -Property Name, Enabled, DefaultInboundAction
Step 5: Verify logging
Logging dropped packets is a CIS recommendation and the only way to see scanning activity after the fact. Confirm LogBlocked = True on every profile, the log file is somewhere persistent, and the file size limit is large enough (the default 4 MB rolls within hours on a busy server):
Get-NetFirewallProfile | Select Name, LogBlocked, LogFileName, LogMaxSizeKilobytes
A defensible PASS / WARN / FAIL score
Pick four checks and weight them equally:
- All three profiles enabled (1 pt)
- All three default inbound = Block (1 pt)
- Zero Allow Any Any rules (1 pt)
- LogBlocked = True on at least one profile (1 pt)
4/4 = PASS, 2-3 = WARN, 0-1 = FAIL. The thresholds are simple enough to defend in a review meeting.
Dargslan.WinFirewallAudit module
The whole audit ships as a free PowerShell module:
Install-Module Dargslan.WinFirewallAudit -Scope CurrentUser
Import-Module Dargslan.WinFirewallAudit
Export-DargslanFirewallAuditReport -OutDir C:\reports
You get an HTML report with the verdict at the top, the three profiles, and the list of risky rules — ready to email to the auditor. The module also exposes Get-DargslanFirewallProfile, Get-DargslanFirewallRules, Get-DargslanFirewallRiskyRules and Get-DargslanFirewallAuditReport for ad-hoc use.
FAQ
Does this work on Windows Server Core?
Yes. The NetSecurity module is part of the base image on Server 2016 and later.
What about the legacy netsh advfirewall commands?
They still work but they do not return objects. Use them only for one-off checks; the cmdlets are the right tool for an audit.
Can I run this remotely against the fleet?
Yes — wrap Get-DargslanFirewallAuditReport in Invoke-Command -ComputerName $hosts and export the result with Export-Csv. The cheat sheet has the one-liner.
Will it modify any rules?
No. Every cmdlet in the module is read-only.
Where is the cheat sheet?
Free PDF at /cheat-sheets/windows-firewall-audit-2026 — the entire workflow on a single page.