Shore Up
A clerk feeding a stack of small permit slips through a guarded gate, each slip stamped allow or deny, with an identical copy dropped into a labelled backup drawer beside the gate.
WindowsSecurity

Automate Windows Firewall Rule Deployment with PowerShell and netsh

Ketan Aagja9 min read
No ratings yet

Before you run this

This guide builds and deploys Windows Defender Firewall rules from a table (a CSV), using the NetSecurity PowerShell module, with netsh advfirewall shown as the older equivalent. The goal is a repeatable, idempotent way to push the same rule set to one host or many, instead of clicking through wf.msc on each box.

  • It needs elevation. Creating, changing, or deleting firewall rules requires an elevated PowerShell session (Run as administrator). On a domain, pushing to remote machines also needs rights on those machines and WinRM reachable. If a Group Policy firewall rule set is in force, GPO rules win over local rules — check that before you spend an afternoon debugging.
  • Read it, then test it. Run the script against a single test VM or a lab machine first, not a production server. Every New-NetFirewallRule call below supports -WhatIf — use it, read the output, and only then run for real.
  • A wrong inbound/outbound rule can lock you out. If you manage the box over RDP or WinRM and you deploy a default-block posture or delete the wrong allow rule, you will cut your own session. Keep an out-of-band path open — console / iDRAC / iLO / hypervisor console — so you can recover without the network.
  • Back up the ruleset before you touch it, and do it in a maintenance window. Windows Firewall's own export/import is your rollback path:
# Elevated. Snapshot the ENTIRE firewall config before making changes.
netsh advfirewall export "C:\path\to\fw-backup-$(Get-Date -Format yyyyMMdd-HHmmss).wfw"

To roll the whole config back to that snapshot:

netsh advfirewall import "C:\path\to\fw-backup-YYYYMMDD-HHMMSS.wfw"

Adding a rule is reversible (remove the rule). Importing a .wfw replaces the current profile configuration wholesale — treat it as the "undo everything" button, not a merge.

What I'm assuming

  • Windows Server 2019/2022 or Windows 10/11, with Windows PowerShell 5.1 (the built-in one). The NetSecurity module ships in-box on all of these — nothing to install.
  • Local rules, not domain GPO firewall rules. If your firewall policy comes from Group Policy, deploy there instead; local rules may be ignored.
  • Placeholders are obvious: C:\path\to\..., SERVER01, and the CSV values are examples you replace.

The building block: one rule

The modern cmdlet is New-NetFirewallRule. Here is a single inbound TCP allow, written safely with -WhatIf so it only prints what it would do:

New-NetFirewallRule `
  -DisplayName "Allow RDP from mgmt subnet" `
  -Direction Inbound `
  -Action Allow `
  -Protocol TCP `
  -LocalPort 3389 `
  -RemoteAddress 10.0.0.0/24 `   # restrict source; don't leave RDP open to the world
  -Profile Domain,Private `
  -Group "ShoreUp Managed Rules" `  # a Group tag makes bulk cleanup trivial later
  -Enabled True `
  -WhatIf

Drop -WhatIf when the output looks right. The -Group value is the single most useful habit here: it lets you list, disable, or remove everything you deployed in one command without touching Windows' own built-in rules.

-Profile takes any of Domain, Private, Public, Any. -Action is Allow or Block. -Direction is Inbound or Outbound. If you need parameters beyond these (ICMP types, program-scoped rules with -Program, service-scoped with -Service), check the exact syntax on Microsoft Learn under New-NetFirewallRule rather than guessing — the parameter set is large and some values are enums.

Deploy a rule set from a CSV

Keep the rules in a table so the intent is reviewable and version-controllable. Create firewall-rules.csv:

DisplayName,Direction,Action,Protocol,LocalPort,Profile,RemoteAddress
Allow RDP from mgmt,Inbound,Allow,TCP,3389,"Domain,Private",10.0.0.0/24
Allow HTTPS,Inbound,Allow,TCP,443,Any,Any
Allow WinRM from mgmt,Inbound,Allow,TCP,5985,Domain,10.0.0.0/24

Now a script that reads it, skips rules that already exist (so re-running is safe), and honours a dry-run switch:

#Requires -RunAsAdministrator
param(
    [string]$CsvPath = "C:\path\to\firewall-rules.csv",
    [switch]$Apply   # without -Apply, this only shows what WOULD happen
)

$rules = Import-Csv -Path $CsvPath

foreach ($r in $rules) {
    # Skip if a rule with this DisplayName already exists (idempotent re-runs)
    $existing = Get-NetFirewallRule -DisplayName $r.DisplayName -ErrorAction SilentlyContinue
    if ($existing) {
        Write-Host "SKIP  (exists): $($r.DisplayName)"
        continue
    }

    # -Profile and -RemoteAddress can be comma-separated lists in the CSV
    $profiles = $r.Profile -split ',' | ForEach-Object { $_.Trim() }
    $remote   = $r.RemoteAddress -split ',' | ForEach-Object { $_.Trim() }

    $params = @{
        DisplayName   = $r.DisplayName
        Direction     = $r.Direction
        Action        = $r.Action
        Protocol      = $r.Protocol
        LocalPort     = $r.LocalPort
        Profile       = $profiles
        RemoteAddress = $remote
        Group         = "ShoreUp Managed Rules"
        Enabled       = "True"
    }

    if ($Apply) {
        New-NetFirewallRule @params | Out-Null
        Write-Host "ADDED: $($r.DisplayName)"
    } else {
        Write-Host "WOULD ADD: $($r.DisplayName)"
    }
}

Run it dry first, then with -Apply:

.\Deploy-FirewallRules.ps1                 # dry run, prints WOULD ADD
.\Deploy-FirewallRules.ps1 -Apply          # actually creates the rules

The netsh equivalent

If you're on an older host or scripting in batch, netsh advfirewall firewall does the same job. One rule:

netsh advfirewall firewall add rule name="Allow RDP from mgmt" dir=in action=allow protocol=TCP localport=3389 remoteip=10.0.0.0/24 profile=domain,private

netsh is still supported, but Microsoft treats the NetSecurity cmdlets as the current path, and netsh lacks conveniences like the -Group tagging above. Use it where PowerShell isn't practical; otherwise prefer the cmdlets.

Pushing to remote machines

The NetSecurity cmdlets accept -CimSession, so you can target remote hosts without wrapping everything in Invoke-Command. WinRM must be reachable and you must have rights on the target:

$targets = "SERVER01","SERVER02"
$cs = New-CimSession -ComputerName $targets

New-NetFirewallRule -CimSession $cs `
  -DisplayName "Allow HTTPS" -Direction Inbound -Action Allow `
  -Protocol TCP -LocalPort 443 -Profile Any `
  -Group "ShoreUp Managed Rules" -Enabled True -WhatIf

Remove-CimSession $cs

Keep -WhatIf on until you've confirmed the session and syntax against one host.

Verify it worked

List exactly what you deployed by the group tag, and confirm the ports:

# The rules you created, with their state
Get-NetFirewallRule -Group "ShoreUp Managed Rules" |
    Select-Object DisplayName, Direction, Action, Enabled

# The port/protocol detail for those rules
Get-NetFirewallRule -Group "ShoreUp Managed Rules" |
    Get-NetFirewallPortFilter |
    Select-Object Protocol, LocalPort

netsh advfirewall firewall show rule name="Allow HTTPS" gives the same for a single rule.

Undo

Because everything carries the -Group tag, cleanup is one command. Preview with -WhatIf first — this is destructive:

# Preview
Get-NetFirewallRule -Group "ShoreUp Managed Rules" | Remove-NetFirewallRule -WhatIf

# Remove for real
Get-NetFirewallRule -Group "ShoreUp Managed Rules" | Remove-NetFirewallRule

To pull a single rule, use Remove-NetFirewallRule -DisplayName "Allow HTTPS". To disable rather than delete — useful when you want to test the effect of removing a rule without losing it — use Disable-NetFirewallRule -DisplayName "Allow HTTPS" and re-enable with Enable-NetFirewallRule.

If something went wider than the tagged rules — say a profile default changed — that's what the .wfw snapshot from the top is for: netsh advfirewall import restores the whole configuration to the point before you started.

For anything beyond the parameters shown here, the authoritative reference is Microsoft Learn for the NetSecurity module (New-NetFirewallRule, Get-NetFirewallRule, Set-NetFirewallRule, Remove-NetFirewallRule). Don't infer a parameter — look it up, because a wrong scope on a firewall rule is exactly the kind of mistake that's invisible until traffic stops.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.