PowerShell Firewall Rule Audit: Export All Rules to CSV

PowerShell Firewall Rule Audit: Export All Rules to CSV

PowerShell Tips Editor 4 min read
PowerShell Firewall Rule Audit: Export All Rules to CSV

Windows Firewall rules accumulate over years of software installs, support troubleshooting, and manual admin changes. By the time an organization has 400 inbound rules on a server, nobody knows which ones are still needed and which ones were added to “fix something” in 2019 and never removed. CIS Benchmarks treat firewall rule hygiene as a scored control. PowerShell’s NetSecurity module gives you Get-NetFirewallRule and its companion filter cmdlets to extract, analyze, and export every rule with full detail — far more usable than the GUI for bulk inspection.

Get-NetFirewallRule Overview and Key Properties

Get-NetFirewallRule returns firewall rule objects, but those objects do not include the port, address, or application filter details directly. Those details are stored in separate filter objects linked by InstanceID. The most important properties on the rule object itself are Name, DisplayName, Enabled, Direction, Action, Profile, and PolicyStoreSource (which tells you whether the rule came from Group Policy or was set locally).

# Get all enabled inbound allow rules
$inboundAllow = Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True

Write-Host "Enabled inbound allow rules: $($inboundAllow.Count)"

# Quick overview of rule sources
$inboundAllow | Group-Object PolicyStoreSourceType |
    Select-Object Name, Count | Format-Table -AutoSize
Enabled inbound allow rules: 347

Name          Count
----          -----
GroupPolicy     112
Local           235

Joining Rules with Port and Address Filter Objects

To get the full picture of what a rule permits, you must join the rule to its NetFirewallPortFilter and NetFirewallAddressFilter objects. Both are retrieved by passing the rule object through Get-NetFirewallPortFilter and Get-NetFirewallAddressFilter via the pipeline. The join is on InstanceID and happens implicitly when you pipe a rule object into these cmdlets.

$fullRules = foreach ($rule in $inboundAllow) {
    $port    = $rule | Get-NetFirewallPortFilter
    $address = $rule | Get-NetFirewallAddressFilter
    $app     = $rule | Get-NetFirewallApplicationFilter

    [PSCustomObject]@{
        Name           = $rule.Name
        DisplayName    = $rule.DisplayName
        Enabled        = $rule.Enabled
        Direction      = $rule.Direction
        Action         = $rule.Action
        Protocol       = $port.Protocol
        LocalPort      = $port.LocalPort -join ', '
        RemotePort     = $port.RemotePort -join ', '
        RemoteAddress  = $address.RemoteAddress -join ', '
        LocalAddress   = $address.LocalAddress -join ', '
        Program        = $app.Program
        PolicySource   = $rule.PolicyStoreSourceType
        Profile        = $rule.Profile
    }
}

Filtering for Enabled Inbound Rules Allowing Any Address

Rules that allow inbound traffic from Any remote address are the highest priority for review. Combined with a common port like 3389 (RDP) or 5985 (WinRM), these represent significant attack surface. Filter your joined rule set for RemoteAddress equal to Any to isolate them.

$anySourceRules = $fullRules | Where-Object {
    $_.RemoteAddress -eq 'Any' -or $_.RemoteAddress -contains '*'
}

Write-Host "Rules allowing inbound from Any address: $($anySourceRules.Count)"
$anySourceRules | Select-Object DisplayName, Protocol, LocalPort, Profile |
    Format-Table -AutoSize

Detecting Rules with Any Port (*) — High Risk

An inbound allow rule scoped to any port from any address is essentially equivalent to disabling the firewall for that profile. These rules warrant immediate investigation. The LocalPort value of Any or the wildcard * is the indicator.

$highRisk = $fullRules | Where-Object {
    ($_.LocalPort -eq 'Any' -or $_.LocalPort -contains '*') -and
    ($_.RemoteAddress -eq 'Any' -or $_.RemoteAddress -contains '*') -and
    $_.Action -eq 'Allow'
}

if ($highRisk.Count -gt 0) {
    Write-Warning "HIGH RISK: $($highRisk.Count) rule(s) allow any inbound port from any address"
    $highRisk | Select-Object DisplayName, Protocol, PolicySource | Format-Table -AutoSize
}
WARNING: HIGH RISK: 3 rule(s) allow any inbound port from any address

DisplayName                        Protocol  PolicySource
-----------                        --------  ------------
Legacy App Inbound (Dev)           Any       Local
WinRM Open (Troubleshooting 2021)  Any       Local
Test Rule - DELETE ME               TCP       Local

Comparing Rules Against a Baseline with Compare-Object

Export a known-good rule set as a baseline CSV and compare subsequent exports against it with Compare-Object. Rules present in the current state but absent from the baseline (SideIndicator => =>) are new and need review. Rules in the baseline but absent now (<=) were removed — which may or may not be intentional.

# Create baseline (run once on a known-good system)
$fullRules | Export-Csv -Path '.\firewall-baseline.csv' -NoTypeInformation

# Compare current state to baseline
$baseline = Import-Csv '.\firewall-baseline.csv'
$current  = $fullRules

$diff = Compare-Object -ReferenceObject $baseline -DifferenceObject $current -Property Name, LocalPort, RemoteAddress, Action

$diff | Where-Object { $_.SideIndicator -eq '=>' } |
    ForEach-Object { Write-Warning "NEW rule not in baseline: $($_.Name)" }

$diff | Where-Object { $_.SideIndicator -eq '<=' } |
    ForEach-Object { Write-Host "Rule removed since baseline: $($_.Name)" }

Exporting Full Audit Report to CSV

Export the complete joined rule set with a timestamp. This CSV becomes the source of truth for compliance review and the input for any baseline comparison runs.

$outPath = ".\FirewallAudit-$env:COMPUTERNAME-$(Get-Date -Format 'yyyyMMdd-HHmm').csv"
$fullRules | Export-Csv -Path $outPath -NoTypeInformation -Encoding UTF8
Write-Host "Exported $($fullRules.Count) rules to $outPath"

Common Errors

  • Get-NetFirewallPortFilter returns separate objects — must join on InstanceID. You cannot select port information directly from the rule object. Always pipe rule objects through Get-NetFirewallPortFilter, Get-NetFirewallAddressFilter, and Get-NetFirewallApplicationFilter to retrieve the associated filter data. Attempting to access $rule.LocalPort directly will return $null.
  • Rules managed by Group Policy cannot be modified locally — script must flag these. Rules with PolicyStoreSourceType -eq 'GroupPolicy' are read-only on the local machine. Any attempt to modify or remove them with Set-NetFirewallRule or Remove-NetFirewallRule will fail with an access error. Flag these in your report and note that changes must be made in the GPO, not on the endpoint.

Related Cmdlets / See Also

Wrapping Up

A firewall audit export is a low-cost, high-value security operation. Run it quarterly, baseline on a known-good state, and compare diffs to detect unauthorized changes. Pay special attention to any-source, any-port inbound allow rules — those are the ones that turn up in breach post-mortems. Flag GPO-managed rules in the report so remediation is routed to the right team.

Send-Item -To