{"id":25688,"date":"2025-07-21T08:34:14","date_gmt":"2025-07-21T08:34:14","guid":{"rendered":"https:\/\/www.newsbeep.com\/us\/25688\/"},"modified":"2025-07-21T08:34:14","modified_gmt":"2025-07-21T08:34:14","slug":"managing-windows-firewall-rules-with-powershell-part-3-creating-a-baseline","status":"publish","type":"post","link":"https:\/\/www.newsbeep.com\/us\/25688\/","title":{"rendered":"Managing Windows Firewall Rules with PowerShell, Part 3: Creating a Baseline"},"content":{"rendered":"<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">[Editor&#8217;s Note: This is Part 3 of a three-part series on managing Windows firewall rules with PowerShell.]<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">So far in this article series, I have shown you how to use PowerShell to <a class=\"ContentText-BodyTextChunk ContentText-BodyTextChunk_link\" target=\"_self\" href=\"https:\/\/www.itprotoday.com\/powershell\/managing-windows-firewall-rules-with-powershell-part-1-beyond-the-gui\" rel=\"nofollow noopener\">manage Windows firewall rules<\/a>, and I have also explained how to <a class=\"ContentText-BodyTextChunk ContentText-BodyTextChunk_link\" target=\"_self\" href=\"https:\/\/www.itprotoday.com\/powershell\/managing-windows-firewall-rules-with-powershell-part-2-overcoming-cmdlet-limitations\" rel=\"nofollow noopener\">gather more information<\/a> than what the Get-NetRirewallRule cmdlet can provide by itself. As handy as those techniques might be, however, you can take things further. By building on the techniques that I have already shown you, you can create a baseline of your firewall settings, and then you can compare your existing firewall settings to the baseline to see if anything has changed.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Let me just say upfront that there are any number of tools and techniques that you could potentially use to document your baseline settings. For the purposes of this article, I am going to use a variation of the technique that I showed you in Part 2 to export my firewall rules to a CSV file. Here is my modified script:<\/p>\n<p>Get-NetFirewallRule | ForEach-Object {<br \/>\n$Rule = $_<br \/>\n$PortFilter = $Rule | Get-NetFirewallPortFilter<br \/>\n$AddressFilter = $Rule | Get-NetFirewallAddressFilter<br \/>\n[PSCustomObject]@{<br \/>\n\u00a0\u00a0\u00a0 Name\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = $Rule.DisplayName<br \/>\n\u00a0\u00a0\u00a0 Direction\u00a0\u00a0\u00a0\u00a0 = $Rule.Direction<br \/>\n\u00a0\u00a0\u00a0 Action\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = $Rule.Action<br \/>\n\u00a0\u00a0\u00a0 Protocol\u00a0\u00a0\u00a0\u00a0\u00a0 = $PortFilter.Protocol<br \/>\n\u00a0\u00a0\u00a0 LocalPort\u00a0\u00a0\u00a0\u00a0 = $PortFilter.LocalPort<br \/>\n\u00a0\u00a0\u00a0 RemotePort\u00a0\u00a0\u00a0 = $PortFilter.RemotePort<br \/>\n\u00a0\u00a0\u00a0 LocalAddress\u00a0 = $AddressFilter.LocalAddress<br \/>\n\u00a0\u00a0\u00a0 RemoteAddress = $AddressFilter.RemoteAddress<br \/>\n\u00a0\u00a0 }<br \/>\n} | Export-CSV \u201cC:\\Temp\\FirewallRules.csv\u201d -NoTypeInformation<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">As you can see, this script is very similar to the other one. However, instead of providing the script with the name of a firewall rule, we are using the Get-NetFirewallRule cmdlet to retrieve all of the firewall rules. The script then uses a ForEach loop to add each rule&#8217;s details to an array of custom objects, which is then exported to a CSV file after the loop ends.<\/p>\n<p data-component=\"related-article\" class=\"RelatedArticle\">Related:<a class=\"RelatedArticle-RelatedContent\" href=\"https:\/\/www.itprotoday.com\/powershell\/what-is-powershell-\" target=\"_self\" data-discover=\"true\" rel=\"nofollow noopener\">What Is PowerShell?<\/a><\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">You can see the script and the resulting CSV file in Figure 1.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\"><img decoding=\"async\" data-component=\"image\" class=\"ContentParagraph-Image\" src=\"https:\/\/www.newsbeep.com\/us\/wp-content\/uploads\/2025\/07\/Firewall_Rules_3-1.jpg\" loading=\"lazy\" alt=\"\" title=\"\"\/><\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Figure 1. The script has exported the firewall rules to a CSV file.<\/p>\n<p>Checking for Changes<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">The important thing to keep in mind is that the CSV file that I have created represents my firewall rules as they existed at a particular point in time. That being the case, we can go back later and compare the machine&#8217;s firewall rules to the CSV file to find out if anything has changed.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Here is the PowerShell script that I have created to compare the two CSV files:<\/p>\n<p>$Baseline = Import-Csv -Path &#8220;C:\\Temp\\FirewallRules.csv&#8221;<br \/>\n$Current\u00a0 = Import-Csv -Path &#8220;C:\\Temp\\CurrentFirewallRules.csv&#8221;<br \/>\n\u00a0<br \/>\nforeach ($BaseRule in $Baseline) {<br \/>\n\u00a0\u00a0\u00a0 $MatchExists = $False<br \/>\n\u00a0\u00a0\u00a0 $BaseName = $BaseRule.Name<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0 ForEach ($Row in $Current) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $CurrentRuleName = $Row.Name<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 If ($CurrentRuleName -eq $BaseName) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Write-Host &#8220;Checking rule: $CurrentRuleName&#8221; -ForegroundColor White<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $MatchExists = $True<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $FieldsToCompare = &#8220;Direction&#8221;, &#8220;Action&#8221;, &#8220;Protocol&#8221;, &#8220;LocalPort&#8221;, &#8220;RemotePort&#8221;, &#8220;LocalAddress&#8221;, &#8220;RemoteAddress&#8221;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $Differences = @()<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 ForEach ($Field in $FieldsToCompare) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $BaseValue = [string]$BaseRule.$Field<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $CurrentValue = [string]$Row.$Field<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($BaseValue -ne $CurrentValue) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $Differences += &#8220;Field &#8216;$Field&#8217; has changed from &#8216;$BaseValue&#8217; to &#8216;$CurrentValue'&#8221;<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if ($Differences.Count -gt 0) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Write-Host &#8220;Rule &#8216;$BaseName&#8217; has changed:&#8221; -ForegroundColor Magenta<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 $Differences | ForEach-Object { Write-Host &#8221;\u00a0\u00a0 $_&#8221; -ForegroundColor Cyan}<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }<br \/>\n\u00a0\u00a0\u00a0 }<br \/>\n\u00a0<br \/>\n\u00a0\u00a0\u00a0 If (-Not $MatchExists) {<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Write-Host &#8220;The rule &#8216;$BaseName&#8217; has been deleted from the current firewall configuration.&#8221; -ForegroundColor Yellow<br \/>\n\u00a0\u00a0\u00a0 }<br \/>\n}<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">As you can see, the script starts out by defining the path and filename for the baseline CSV file and for the current CSV file (the CSV file that was most recently created).<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Next, the script creates a ForEach loop that looks at all of the rules within the baseline CSV file. For each of these rules, the script initially sets the $MatchExists variable to False, indicating that the current rule has not yet been matched to any records within the CurrentFirewallRules.csv file. I have also created a variable called $BaseName that stores the name of the current rule.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Next, the script goes into a nested ForEach loop. This loop examines all of the records within the CurrentFirewallRules.csv file to see if any of those records have a name that matches the name of the current baseline record. Assuming that a match is found, the script displays a line of text conveying the name of the record that is currently being checked. It also sets the $MatchExists variable to True. A third ForEach loop compares all of the various columns of data associated with the current record and its match, as a way of checking whether or not any changes have been made.<\/p>\n<p data-component=\"related-article\" class=\"RelatedArticle\">Related:<a class=\"RelatedArticle-RelatedContent\" href=\"https:\/\/www.itprotoday.com\/powershell\/powershell-remoting-in-a-workgroup-environment-a-step-by-step-guide\" target=\"_self\" data-discover=\"true\" rel=\"nofollow noopener\">PowerShell Remoting in a Workgroup Environment: A Step-by-Step Guide<\/a><\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">If changes are detected, they are documented in an array called Differences. The script will display a message indicating the name of the firewall rule that has been modified, and then it will display the contents of the $Differences array. The reason why I am using an array is because a single firewall rule could potentially contain multiple modifications, and using an array makes it easy to display all of the modifications that are detected.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">The last thing that the script does for each of the baseline records is check to see if the $MatchExists variable is still set to False. If the variable does have a value of False, it means that no matching rule was found, and the script will display a message indicating that the rule has been deleted. You can see what the script looks like in Figure 2.<\/p>\n<p data-component=\"related-article\" class=\"RelatedArticle\">Related:<a class=\"RelatedArticle-RelatedContent\" href=\"https:\/\/www.itprotoday.com\/powershell\/untitled\" target=\"_self\" data-discover=\"true\" rel=\"nofollow noopener\">PowerShell Arrays: How To Build, Manipulate, and Manage Them<\/a><\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\"><img decoding=\"async\" data-component=\"image\" class=\"ContentParagraph-Image\" src=\"https:\/\/www.newsbeep.com\/us\/wp-content\/uploads\/2025\/07\/Firewall_Rules_3-2.jpg\" loading=\"lazy\" alt=\"\" title=\"\"\/><\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Figure 2. This is what it looks like when the script runs.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">Even though this script works, there is room for improvement. As it is written, the script does not check to see if any new rules have been created since the baseline was compiled. More importantly, the script assumes that none of the firewall rules has a duplicate name. If your firewall contains two or more rules with the same name, then those duplications have the potential to confuse the script, resulting in false modification alerts.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">If there is enough interest, I may eventually create a version of this script that can perform a two-way comparison between the files and handle duplicate rule names.<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">\u00a0<\/p>\n<p class=\"ContentParagraph ContentParagraph_align_left\" data-testid=\"content-paragraph\">\u00a0<\/p>\n","protected":false},"excerpt":{"rendered":"[Editor&#8217;s Note: This is Part 3 of a three-part series on managing Windows firewall rules with PowerShell.] So&hellip;\n","protected":false},"author":2,"featured_media":25689,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[46],"tags":[191,74],"class_list":{"0":"post-25688","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-computing","8":"tag-computing","9":"tag-technology"},"_links":{"self":[{"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/posts\/25688","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/comments?post=25688"}],"version-history":[{"count":0,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/posts\/25688\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/media\/25689"}],"wp:attachment":[{"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/media?parent=25688"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/categories?post=25688"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.newsbeep.com\/us\/wp-json\/wp\/v2\/tags?post=25688"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}