Automated Firewall Rule Migration
Note: This page is a companion to Firewall Configuration. Read that guide first, particularly Preparing for the firewall migration and Baseline design decisions. This page covers a scripted alternative to the manual rule creation steps in Creating rules in the GPO, for when you already have your existing firewall product's rules mapped out and want to load them into a Group Policy Object in one pass rather than working through the rule wizard by hand.
This route uses Active Directory Group Policy only. If your management plane is Intune or Configuration Manager, use the Intune or Configuration Manager sections of the main guide instead.
Download the files
- Deploy-FirewallMigrationGpo.ps1 — the migration script.
- firewall-rules-template.csv — a template for your rules. Replace the sample rows with your own, mapped from whichever firewall product you are retiring.
Both files are provided as-is. Read this page in full, including the pre-rollout checklist, before running either against anything other than a disposable test machine.
What the script does
Given a GPO name and a CSV of rules, the script:
- Creates the GPO if it does not already exist.
- Imports every row of the CSV as a firewall rule written directly into that GPO's policy store. Re-running the script with the same CSV recreates any rule whose name already exists. This does not purge rules: removing a row from the CSV does not remove that rule from the GPO, remove it yourself if you no longer want it.
The remaining steps only run when you pass -TargetOU. Omit it for a rule-import-only pass that touches nothing else, see Step 1: review only, no changes yet.
- Hardens the firewall profile settings themselves, not just the rules. A rule only ever defines what to do with matching traffic. It does nothing if the profile it sits on is switched off, or if the profile's own default action leaves inbound traffic wide open regardless of your rules. The script forces the profile settings that make the imported rules actually mean something. See What the hardening step changes below.
- Links the GPO to whichever organisational unit (OU) you give it (re-enabling the link if it already exists but is disabled), and sets its scope to
Authenticated Userson that OU. - Forces a policy refresh (
gpupdate /force) on every computer resolved as in scope. - Verifies the rollout: confirms the GPO shows as applied (not just filtered or denied), checks that every rule the CSV created is present and, if the CSV enabled it, enabled, by name and this GPO specifically (not just a matching count from any GPO), and runs a real network reachability test from wherever the script is run, rather than trusting the affected computer's own report of its firewall state. See Verifying the rollout for why this distinction matters.
The script is safe to re-run. It is designed to be run repeatedly as you refine the CSV, add more machines to a pilot, or re-assert the baseline.
Prerequisites
- A PowerShell session on a domain controller, or a management host with RSAT installed (the
GroupPolicy,NetSecurity, andActiveDirectoryPowerShell modules). - An account with rights to create and edit the target GPO, and to run commands remotely on the target computers (WinRM/
Invoke-Commandreachability is needed for the push and verify steps, only until the GPO takes effect on a given machine, see WinRM and the lockout risk). - Your CSV file, completed from the template. See CSV schema below.
- Out-of-band access to every machine you plan to run this against, confirmed before you run it against anything for the first time. This means something that does not depend on the machine's own network path, for example a hypervisor console, a hardware management interface such as iLO or iDRAC, or physical access. If something is misconfigured, this script can close off your normal remote access (RDP, PowerShell remoting) to that machine, and out-of-band access is how you recover.
CSV schema
One row per firewall rule. Only DisplayName is required. Every other column can be left blank, and the script fills in a sensible default.
| Column | Required | Default if blank | Notes |
|---|---|---|---|
DisplayName |
Yes | A unique name for the rule. A row is skipped if this is blank, or starts with # (use that for comment rows). |
|
Direction |
No | Inbound |
Inbound or Outbound. Outbound is rarely needed, since Defender Firewall allows outbound by default. |
Action |
No | Allow |
Allow or Block. |
Protocol |
No | Any |
For example TCP or UDP. Required if LocalPort or RemotePort is set. |
LocalPort |
No | (unset) | A single port, a range (27000-27009), or a comma or semicolon separated list. Requires Protocol to be TCP or UDP. |
RemotePort |
No | (unset) | Same format as LocalPort. Note: if set, this restricts the connecting client's source port too, not just the destination. This is unusual, but faithfully carries forward whatever your source firewall product specified. Confirm with whoever owns the migration data whether a narrow RemotePort like this is genuinely intended, since a normal port scan will show a rule like this as blocked even when it is working correctly. |
Program |
No | (unset) | Full path to the executable. Environment variables (%ProgramFiles%) are expanded. |
RemoteAddress |
No | (unset) | A single IP, a CIDR range, or a comma or semicolon separated list. |
LocalAddress |
No | (unset) | Same format as RemoteAddress. |
Profile |
No | Any |
Domain, Private, Public, or a comma or semicolon separated combination. |
Group |
No | (unset) | Groups the rule under a named category in the Firewall UI. |
Enabled |
No | True |
Set to False, No, or 0 to import the rule disabled. |
Notes |
No | (unset) | Written to the rule's Description field, giving a durable record of why the rule exists without a separate tracking sheet. |
A cell containing <...> (an unfilled template placeholder) causes that row to be skipped with a warning, rather than creating a broken rule.
A source rule scoped to
Anyprotocol. If a rule from your source firewall product allows both TCP and UDP on the same ports, do not narrow it to one protocol on assumption. If the real protocol in use is not confirmed, split it into a TCP row and a UDP row with the same ports, rather than guessing which one was meant. Narrowing anAnyprotocol rule to a guess can silently drop real traffic.
What the hardening step changes
After importing the rules, the script also:
- Forces
Enabled = Trueon the Domain, Private and Public profiles, overriding any locally disabled state. Several endpoint provisioning tools disable Windows Firewall locally, and an imported rule sitting on a firewall that is switched off does nothing. - Sets
DefaultInboundAction = Block,DefaultOutboundAction = Allow, matching the recommended baseline in the main guide: block inbound by default, and leave outbound open until your application inventory is mature enough to lock that down too. -
Enables the "Core Networking" predefined rule group, needed for basic Windows networking (DHCP, IPv6 neighbour discovery, and similar) once inbound defaults to Block.
Known gap: the machine you run this script from may not have this predefined group's rule templates available in its own local rule catalogue, in which case this step prints a warning rather than succeeding. That is visible and honest, not silently broken, but it means Core Networking may not actually be enforced by this run. No breakage from this has been observed in testing, since DHCP and DNS traffic is outbound-initiated and gets its return traffic allowed regardless of the inbound default, but it has not been proven safe for every kind of traffic that genuinely depends on Core Networking's rules. If you see this warning, investigate before assuming it is harmless in your environment.
-
Enables ICMP Echo Request (ping) explicitly. This is not covered by Core Networking, despite what some vendor migration notes claim.
- Does not enable Remote Desktop on your behalf. Unlike Core Networking and ICMP, RDP is never added unconditionally. While importing your CSV, the script recognises a row as covering RDP only if it is genuinely an enabled, Allow, Inbound rule, on TCP or every protocol (a blank/
AnyProtocol column includes TCP), whose port (a blank/AnyLocalPort column, a single port, a range like3388-3390, or a comma/semicolon-separated list) includes3389, driven entirely by the rows this run actually creates, never by checking what's already sitting in the GPO's policy store, so a stale rule left over from an earlier run is never mistaken for CSV coverage. If a matching row exists, nothing further happens, that rule is already live like any other row you migrated, scoped however you wrote it in the CSV. If it does not (including a disabled orBlockrow, which don't count), the script prints a warning and leaves RDP closed onceDefaultInboundActionflips to Block. If you want RDP to keep working, add it to your CSV as an explicit row, the same way as any other rule, so you control its scoping (RemoteAddressrestricted to your management subnets, for example) rather than inheriting a script default. - Disables local firewall and IPsec rule merging by default, making this GPO the sole source of truth rather than merging with whatever local administrator, installer, or
netshchanges already exist on a given machine. Pass-AllowLocalRuleMergingto leave the Windows default (merging allowed) instead. - Leaves logging off unless
-EnableLoggingis passed.
WinRM and the lockout risk
Read this before your first run. WinRM is deliberately not enabled by this script. The moment
DefaultInboundAction=Blocktakes effect on a machine, WinRM stops being reachable unless something is explicitly allow-listing it, and nothing in a typical third-party firewall export does, since the prior product was never blocking it either. That is not a bug in the script. Opening WinRM unconditionally on your behalf would be an assumption the script has no basis to make about your environment.
Consequences of this:
- Once the GPO applies to a machine,
Invoke-Commandand PowerShell remoting to it will stop working, including the script's own "Forcinggpupdate" and "Verify" steps on that same run. Expect those to fail or time out on any run that changes profile state on a machine that was only reachable over WinRM beforehand. This is expected, not a fault. - After that,
gpupdate /forcehas to be run locally on the target (console, RDP, or a scheduled task), not remotely, since outbound traffic (the machine pulling policy from a domain controller) still works even though inbound to it does not. - Whether RDP is available as a fallback here depends entirely on your CSV, since the script does not add it either (see What the hardening step changes). If your CSV does not define an RDP rule, you have no network path back into the machine at all once WinRM is blocked, only genuine out-of-band access recovers it. Confirm that access exists before your first run against anything, regardless of what your CSV does or does not contain.
If WinRM or PowerShell remoting is part of how you manage an environment day to day, add it back deliberately, with its own -TargetOU-scoped pilot and -EnableLogging turned on first, the same way any other previously unrestricted traffic your CSV does not cover should be discovered and added, not assumed.
Iterating quickly with -TempWinRmForDeploy
Repeatedly using console access for every gpupdate while you pilot this is slow. The -TempWinRmForDeploy switch adds a temporary WinRM-allow rule to the GPO before DefaultInboundAction flips to Block, so the script's own push and verify steps, and any further Invoke-Command work while you keep iterating, stay reachable over WinRM without needing console access each time.
This only ever touches the firewall port (TCP 5985), not the WinRM service. It restores connectivity if WinRM was already configured and running on the target, which is the normal case for a domain-joined machine you could already reach with Invoke-Command before this GPO applied. It does not start the service, configure a listener, or do anything that Enable-PSRemoting or winrm quickconfig would do. If WinRM genuinely is not configured on a target at all, opening the port alone will not restore remote management.
This is a deployment convenience only. It is never part of the migrated firewall state, and the script does not remove it automatically. When you are done iterating, retire it:
Remove-NetFirewallRule -DisplayName "TEMP - WinRM for deployment (remove before production)" -PolicyStore "<yourdomain>\<GPO display name>"
Then let it go. Do not force a gpupdate on every machine to apply that removal immediately. Forcing it remotely cuts off the session doing it, which is the exact problem this switch exists to avoid, and forcing it locally one machine at a time does not scale to a real fleet. Each machine's normal background Group Policy refresh (the Windows default, roughly every 90 to 120 minutes) picks up the removal on its own. WinRM being reachable for up to that long after you have removed the rule is an acceptable, bounded window. If a specific machine needs it closed immediately, a local gpupdate /force on that one machine works.
Parameters
| Parameter | Default | Purpose |
|---|---|---|
-GpoName (required) |
Display name of the GPO to write into. Created if it does not exist. | |
-Csv (required) |
Path to your rules CSV. | |
-TargetOU |
(none) | OU or OUs to link the GPO to. Omit for an import-only dry run, which creates and populates the GPO without hardening the profile, linking, scoping, pushing, or verifying. |
-SkipGpUpdate |
off | Skip forcing gpupdate on target computers, leaving the change to land on the next normal refresh cycle instead. |
-AllowLocalRuleMerging |
off (merging disabled) | By default this GPO disables local firewall rule merging, making it the sole source of truth on affected machines. Pass this to leave the Windows default (merging allowed) instead. |
-EnableLogging |
off | Turn on LogAllowed/LogBlocked for all three profiles. Useful during a pilot to see what is actually being allowed or blocked before widening rollout. |
-TempWinRmForDeploy |
off | Adds a temporary WinRM-allow rule before the profile hardening applies, so the script's own push and verify steps keep working over WinRM instead of needing console or RDP access for every gpupdate. Deployment convenience only, see Iterating quickly with -TempWinRmForDeploy. |
Running a pilot
Do not point this at your whole estate on the first run. The steps below take a single pilot machine through review, deployment and verification before you expand.
Before you start
- Confirm you have out-of-band access to whichever machine you pilot this on.
- Decide whether WinRM or PowerShell remoting is part of how you manage this machine day to day. The script can temporarily open the WinRM port during deployment (
-TempWinRmForDeploy), but that only restores connectivity if the WinRM service is already running and configured, and it is not left open in the final state either way. If WinRM needs to keep working permanently, that is a decision to make deliberately, covered in WinRM and the lockout risk. - Have a PowerShell session open on a domain controller, or a management host with RSAT installed, with rights to create and edit Group Policy Objects and to manage the target computer remotely.
Step 1: review only, no changes yet
Run the script without -TargetOU. This creates the GPO and imports the rules, but does not link, scope, push, or change anything on any machine:
.\Deploy-FirewallMigrationGpo.ps1 -GpoName "SEC - Windows Firewall - Migration" -Csv .\firewall-rules-template.csv
Review what it created, and check it against your CSV before continuing:
Get-NetFirewallRule -PolicyStore "<yourdomain>\SEC - Windows Firewall - Migration" |
Sort-Object DisplayName | Select DisplayName, Direction, Action, Enabled, Profile | Format-Table -AutoSize
Step 2: set up a pilot OU
Create a pilot OU nested under wherever your real target machines live. Nesting it this way means piloted machines keep every other policy already applied to them, and it also avoids the caching problem in Why a nested OU, not security group filtering below.
New-ADOrganizationalUnit -Name "FirewallMigrationPilot" -Path "OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
Move one machine into it, ideally one that is non-critical and where a mistake is recoverable:
Get-ADComputer <pilot-machine-name> | Move-ADObject -TargetPath "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
Step 3: deploy to the pilot machine
Recommended, the frictionless option:
.\Deploy-FirewallMigrationGpo.ps1 `
-GpoName "SEC - Windows Firewall - Migration" `
-Csv .\firewall-rules-template.csv `
-TargetOU "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>" `
-EnableLogging -TempWinRmForDeploy
-TempWinRmForDeploy temporarily allows WinRM during this deployment so the push and verification steps run without interrupting your session, and so you can keep iterating from the same PowerShell window. It is not part of the migrated firewall rules, see Retiring the temporary WinRM rule once you are done piloting.
-EnableLogging turns on Windows Firewall logging for the pilot so you can see what is actually being allowed or blocked. Worth keeping on until you are confident, then it can be dropped for the final rollout if you do not want it long term.
If you would rather not open WinRM even temporarily, omit -TempWinRmForDeploy. This is a valid choice, but be aware that the moment this policy applies, PowerShell remoting to the pilot machine stops working by design, and the script's own push and verify steps will visibly hang with a connection-interrupted message. Cancel it and complete the remaining steps locally on the pilot machine (console, RDP, or a scheduled task):
# Run this directly on the pilot machine, not remotely, once you have cancelled the script
gpupdate /force
Watch the output as the script runs. Each hardening step reports either a green confirmation or a yellow warning. A warning means that specific protection did not actually apply. Do not treat a warning as something to ignore.
Step 4: verify with an independent reachability check
The script includes its own verification step, but confirm independently too. This is the single most important habit in this whole procedure. Use a second PowerShell window, or a second person, to test actual reachability to the pilot machine, rather than relying solely on output from the machine that just made the change:
# Test whatever your CSV actually defines as an inbound allow. RDP (3389) is
# only expected open here if your CSV contains an RDP rule, this script does
# not open it by default, see "What the hardening step changes" above.
Test-NetConnection -ComputerName <pilot-machine-name> -Port <port from your CSV>
If you have a network scanning tool available, an actual scan from outside is the strongest check:
nmap -sT -p 3389,445,135,139,5985 -Pn <pilot-machine-ip>
Expect each port to match what your CSV says should be open, and everything else filtered or closed. 3389 (RDP) is only expected open if your CSV includes a rule for it, 5985 (WinRM) should always be filtered unless you ran with -TempWinRmForDeploy. If your environment needs something else open that isn't in the CSV yet, that needs its own row rather than an assumption.
Also confirm your actual applications still work. The rules in the CSV allow specific programs or ports through the firewall, but that is necessary, not sufficient. Confirm the real software using them still functions correctly on the pilot machine before calling this step done.
Step 5: expand gradually
Once the pilot machine is confirmed working, both firewall-correct and application-functional, for a reasonable period rather than a few minutes, add more machines the same way:
Get-ADComputer <next-machine> | Move-ADObject -TargetPath "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
.\Deploy-FirewallMigrationGpo.ps1 `
-GpoName "SEC - Windows Firewall - Migration" `
-Csv .\firewall-rules-template.csv `
-TargetOU "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>" `
-TempWinRmForDeploy
Re-running is safe. Already-correct rules and settings are simply reasserted, not duplicated. Keep using -TempWinRmForDeploy for each wave if you used it initially, the script re-asserts it each run the same as everything else.
Step 6: cut over once fully confident
Move all machines back to their normal OU and re-link the GPO there directly, then delete the pilot OU rather than just unlinking it, since an empty leftover OU is easy to miss later:
Get-ADComputer -SearchBase "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>" -Filter * |
Move-ADObject -TargetPath "OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
Remove-GPLink -Name "SEC - Windows Firewall - Migration" -Target "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
New-GPLink -Name "SEC - Windows Firewall - Migration" -Target "OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
Set-ADOrganizationalUnit -Identity "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>" -ProtectedFromAccidentalDeletion $false
Remove-ADOrganizationalUnit -Identity "OU=FirewallMigrationPilot,OU=<YourServersOU>,DC=<yourdomain>,DC=<com>" -Confirm:$false
Do not assume the relink worked. Confirm with three quick checks:
# The GPO should now be linked directly to your real OU
Get-GPInheritance -Target "OU=<YourServersOU>,DC=<yourdomain>,DC=<com>"
# Every migrated machine should show your real OU in its DN, not the pilot OU
Get-ADComputer <machine-name> | Select DistinguishedName
# The pilot OU should be gone entirely
Get-ADOrganizationalUnit -Filter 'Name -eq "FirewallMigrationPilot"'
Then repeat Step 4's reachability check against each machine one more time. The relink is a real structural change, so confirm it did not introduce a gap rather than assuming it was seamless.
Why a nested OU, not security group filtering
Restricting a GPO's rollout by adding a pilot AD security group to its security filtering is a reasonable-sounding alternative, but it does not behave the way you would expect for a staged rollout. GPO applicability through security groups is evaluated against the group SIDs cached in a computer's Kerberos ticket, which is only refreshed when a new ticket is issued, normally at boot. A plain gpupdate /force does not request a new ticket, so a computer added to a pilot group keeps showing the GPO as not applied until it reboots, or its ticket naturally renews, which can be several days later by default. That does not scale to a large fleet.
GPO applicability through OU membership, by contrast, is resolved live against Active Directory on every gpupdate, with no caching involved. Moving a computer into a linked OU takes effect on the next policy refresh, with no reboot required, which is why the pilot procedure above uses a nested OU instead.
Verifying the rollout
The script's own verification step covers the common case, but for a deeper check on any single machine (over RDP or console if WinRM is blocked):
gpupdate /force
gpresult /r /scope:computer
Get-NetFirewallProfile | Select Name, Enabled, DefaultInboundAction, DefaultOutboundAction
Get-NetFirewallRule -PolicyStore ActiveStore | Where-Object PolicyStoreSourceType -eq GroupPolicy |
Sort-Object DisplayName | Select DisplayName, Direction, Action, Enabled, Profile
Compare against the source of truth in the GPO itself:
Get-NetFirewallRule -PolicyStore "<yourdomain>\<GPO display name>" |
Sort-Object DisplayName | Select DisplayName, Direction, Action, Enabled, Profile
Do not trust
Get-NetFirewallProfilealone on a GPO-managed profile. On a machine whose firewall profile is managed by this GPO,Get-NetFirewallProfile | Select Enabled, DefaultInboundAction, DefaultOutboundActioncan reportFalse/NotConfigured/NotConfiguredeven while the firewall is genuinely and correctly enforcing, confirmed both by a live network scan and by the Windows Security interface showing all three profiles On, "managed by your system administrator". Rule-level queries (Get-NetFirewallRule -PolicyStore ActiveStore) are reliable. The profile-levelEnabled/DefaultInboundAction/DefaultOutboundActionproperties specifically are not, on a GPO-managed profile. Never conclude a machine's firewall is not enforcing based on this cmdlet alone. Confirm with an actual reachability test, a real network scan, or the Security interface instead. This is exactly why the script's own verify step tests reachability rather than querying profile state directly.
Treat the console output as a per-step status, not the final gate. Each hardening step prints a green confirmation or a yellow warning depending on whether Windows actually applied the underlying rule, which is a stronger signal than rule presence alone. Even so, before treating a rollout wave as validated, follow up with an independent reachability check (
Test-NetConnection, a real RDP attempt, a network scan) from a separate machine. That is the same discipline worth applying to any firewall change, scripted or manual, and it is what actually confirms a machine is reachable the way you expect, rather than just that a rule with the right name exists.
To confirm a specific rule's port or program filter survived the import intact:
Get-NetFirewallRule -DisplayName "<rule name>" -PolicyStore ActiveStore | Get-NetFirewallPortFilter
Get-NetFirewallRule -DisplayName "<rule name>" -PolicyStore ActiveStore | Get-NetFirewallApplicationFilter
Validating with a network scan
A scan against the target from outside, using a tool such as nmap, is a good independent check. Run it and compare against what should be open: open for anything with an inbound allow rule bound to a listening service, filtered (no response) for anything correctly blocked. Be aware that filtered is also what you will see for a correctly configured rule whose RemotePort is scoped narrowly (see the CSV schema note above), which is not necessarily a fault.
nmap -sT -p <ports of interest, comma-separated or ranges> -Pn -T4 <target-ip>
(-sT if you do not have raw-socket privileges for -sS. -Pn skips host discovery if you already know it is up.)
Removing the migration later
If the GPO is deleted or unlinked, Windows Defender Firewall's Group Policy client-side extension cleans up correctly on the next gpupdate. Rules do not persist locally ("tattoo") after their source GPO is gone.
Get-NetFirewallRule -PolicyStore ActiveStore | Where-Object PolicyStoreSourceType -eq GroupPolicy | Measure-Object
# ... delete or unlink the GPO, then gpupdate /force ...
# Count comes back 0, and profiles revert to whatever local (non-GPO) state existed before
If you are mid-incident, having lost access to a machine after this GPO applied, this is also the recovery path: delete the GPO centrally, then run gpupdate /force locally on the affected machine (console or out-of-band access, since remote management is exactly what is broken) to pull the removal and restore whatever local state existed before.
Pre-rollout checklist
Before trusting this script against a real environment:
- [ ] Confirm out-of-band recovery access exists for every target machine class, before running this against anything for the first time. A lockout without it means a physical visit.
- [ ] Run a genuine pilot, not just a clean-lab test. One or two representative machines,
-EnableLoggingon, watched over a real business cycle rather than a few minutes, confirmed both rule-present and independently reachable before expanding. - [ ] Confirm whether WinRM or PowerShell remoting is part of how the environment is actually managed. If it is, add it deliberately with its own pilot-tested rule. This script does not include it.
- [ ] Decide whether RDP belongs in your CSV at all, and if so how it should be scoped. This script never adds it for you. If you do add an RDP row, confirm its
ProfileandRemoteAddressscoping is correct for every machine it will apply to, roaming laptops or remote-site machines that are not reliably on the Domain profile will lose RDP if it is scoped toDomainonly. - [ ] Confirm whether anything else your CSV opens should be scoped by
RemoteAddressto actual management subnets, rather than left open to an entire profile. - [ ] Investigate the Core Networking warning if it appears in your environment, rather than assuming that nothing breaking in a lab test transfers directly. Different traffic patterns may depend on it.
- [ ] Test across the actual variety of Windows builds in your fleet, not just one. The predefined-rule-template gap that affects Core Networking here is itself an environment-specific quirk, and there is no guarantee something similar does not recur differently on a different build.
- [ ] Verify every wave independently, rule presence and actual reachability, plus a real network scan if practical, as the final check before expanding rollout.
Troubleshooting
| Symptom | Likely cause and check |
|---|---|
| Rule count is 0, or fewer than expected, on a target machine | Check Get-GPInheritance -Target <OU> to confirm the GPO is actually linked where you expect, and Get-GPPermission -Name <GpoName> -All to confirm Authenticated Users (or whichever trustee) has GpoApply. |
A specific rule shows Enabled: False |
The script warns about this after import. Check the CSV's Enabled column for that row, since this is sometimes an intentional placeholder rather than a mistake. |
| A predefined-group step (Core Networking, ICMP) prints a warning instead of a green success line | The rule genuinely is not enabled in the GPO, not a false alarm. See What the hardening step changes. |
| RDP is not reachable after rollout | Expected if your CSV does not define an inbound TCP/3389 rule, this script never opens RDP on its own. Add an explicit row to your CSV if RDP needs to work. |
| RDP is reachable even though your CSV has no RDP rule | Something else is granting it, a local rule, another GPO, or local rule merging still being allowed. This script did not add it. |
| Lost RDP, ping, or WinRM to a machine after this GPO applied | Expected risk if the compensating rules did not take, see WinRM and the lockout risk. Recover with out-of-band console access, then run gpupdate /force locally after fixing the GPO centrally. |
The script's own "Forcing gpupdate" or "Verify" step hangs with a connection-interrupted message |
Expected if WinRM just got blocked by the policy this same run applied. Cancel it, then run gpupdate /force locally on the target instead. |
A network scan shows a rule's port as filtered even though the rule looks correctly configured |
Check whether RemotePort is set, which narrows to the connecting client's source port too. A normal scanner will not satisfy that and will show filtered even for a working rule. |
Invoke-Command fails against a target |
Confirm WinRM is enabled and reachable (Test-WSMan <computer>) and has not already been blocked by this GPO, and that the account running the script has remote management rights on that machine. |
Get-ADComputer or Move-ADObject errors on multiple names at once |
-Identity takes one object, not an array. Pipe through ForEach-Object, or use -Filter with -or conditions instead. |
Related pages
- Firewall Configuration for the full manual configuration guide across Intune, Group Policy and Configuration Manager, and the recommended baseline this script implements.
- Browser Security Policy for the browser half of a third-party suite replacement.