PowerShell Version 2 Compatible Function to Determine Windows Firewall State

I recently had a need to perform some security auditing on an environment that still has some servers running PowerShell version 2 (PowerShell version 2 is deprecated). One of the things I needed to determine was whether or not the Windows firewall was enabled on each of the servers in the environment. Luckily, all of the servers at least had PowerShell remoting enabled.

 1#Requires -Version 2.0
 2function Get-MrNetFirewallState {
 3
 4<#
 5.SYNOPSIS
 6    Displays the per-profile state of the Windows Firewall.
 7
 8.DESCRIPTION
 9    The Get-MrNetFirewallState function displays the current firewall state for the Domain, Private, and Public profiles.
10
11.PARAMETER ComputerName
12    The name of the computer(s) to retrieve the firewall state for.
13
14.PARAMETER Credential
15    Specifies a user account that has permission to perform this action. The default is the current user.
16
17.EXAMPLE
18     Get-MrNetFirewallState
19
20.EXAMPLE
21     Get-MrNetFirewallState -ComputerName Server01, Server02
22
23.EXAMPLE
24     Get-MrNetFirewallState -ComputerName Server01, Server02 -Credential (Get-Credential)
25
26.INPUTS
27    None
28
29.OUTPUTS
30    PSCustomObject
31
32.NOTES
33    Author:  Mike F Robbins
34    Website: http://mikefrobbins.com
35    Twitter: @mikefrobbins
36#>
37
38    [CmdletBinding()]
39    param (
40        [ValidateNotNullOrEmpty()]
41        [string[]]$ComputerName = $env:COMPUTERNAME,
42
43        [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
44    )
45
46    $ScriptBlock =
47@'
48    $Results = netsh.exe advfirewall show allprofiles | Select-String -SimpleMatch Profile, State
49
50    for ($i = 0; $i -lt 6; $i += 2) {
51        New-Object PSObject -Property @{
52            ComputerName = $env:COMPUTERNAME
53            Name = ($Results[$i] | Select-String -SimpleMatch 'Profile Settings') -replace '^*.Profile Settings:'
54            Enabled = if ($Results[$i+1] -match 'ON') {
55                          $true
56                      }
57                      else {
58                          $false
59                      }
60        }
61    }
62'@
63
64    $Params = @{
65        Scriptblock = [Scriptblock]::Create($ScriptBlock)
66    }
67
68    if ($ComputerName -ne $env:COMPUTERNAME) {
69
70        $Params.ComputerName = $ComputerName
71        $Params.ErrorAction = 'SilentlyContinue'
72        $Params.ErrorVariable = 'Problem'
73
74        if ($PSBoundParameters.Credential) {
75            $Params.Credential = $Credential
76        }
77
78        Invoke-Command @Params | Select-Object -Property ComputerName, Name, Enabled
79
80        foreach ($p in $Problem) {
81            if ($p.FullyQualifiedErrorId -match 'AccessDenied|LogonFailure') {
82                Write-Warning -Message "Access Denied when trying to connect to $($p.TargetObject)"
83            }
84            elseif ($p.FullyQualifiedErrorId -match 'NetworkPathNotFound') {
85                Write-Warning -Message "Unable to connect to $($p.targetobject)"
86            }
87            else {
88                Write-Warning -Message "An unexpected error has occurred when trying to connect to $($p.targetobject)"
89            }
90        }
91
92    }
93    else {
94        $Params.ScriptBlock.Invoke()
95    }
96
97}

The PowerShell function shown in the previous code example is a wrapper for the Netsh.exe command. The commands that are going to be executed are stored in a here-string and then converted to a script block within the parameters hash table. It uses Invoke-Command to execute Netsh on the remote servers and them uses Select-String to parse down the data to only the lines that are necessary. The results are turned into a usable custom object in a For loop that uses Select-String again to parse out some of the unnecessary information from each of the remaining lines. If the credential parameter is specified, it's also added to the parameters hash table. Splatting is used with Invoke-Command.

Custom error handling has been added to allow the function to take advantage of running against multiple computers at the same time via Invoke-Command while providing the user with feedback when errors occur such as being unable to connect to the remote computer.

netsh-firewall-wrapper4a.jpg

I found that two different errors (access denied and logon failure) can be returned which both mean access denied. During the last few months, I've performed a technical technical review on the "Windows Server 2016 Automation with PowerShell Cookbook". Based on reviewing Thomas Lee's PowerShell code, I learned that the Match operator can be used similarly to the Like operator except without the need to specify wildcard characters. I had previously only used it when I needed to specify a regular expression.

netsh-firewall-wrapper5a.jpg

If you're going to use the GroupBy parameter of Format-Table, you have to sort by that property first.

1Get-MrNetFirewallState -ComputerName FS1, FS2, SRV1, SRV2 |
2Sort-Object -Property ComputerName |
3Format-Table -GroupBy ComputerName

netsh-firewall-wrapper1a.jpg

If the function is run against the local computer, the script block is simply invoked. While this doesn't cover all scenarios where the local computer could be specified, it's a great option for testing against a local computer where PowerShell remoting isn't enabled.

1Get-MrNetFirewallState

netsh-firewall-wrapper2a.jpg

This task is much easier to accomplish with newer versions of PowerShell using the Get-NetFirewallProfile cmdlet which was introduced in PowerShell version 3.0.

1Get-NetFirewallProfile -CimSession fs1, fs2, srv1, srv2 |
2Sort-Object -Property PSComputerName |
3Format-Table -Property PSComputerName, Name, Enabled -GroupBy PSComputerName

netsh-firewall-wrapper3a.jpg

Here's a bonus tip: Even though commands such as Get-NetFirewallProfile don't have a ComputerName parameter, a computer name can be specified with the CimSession parameter without needing to first create a CIM session. A CIM session will automatically be created behind the scenes using the WSMan protocol and then it will be automatically removed once the command completes (a special thanks to Richard Siddaway for clarifying this for me a few weeks ago).

The Get-MrNetFirewallState function shown in this blog article can be downloaded from my PowerShell repository on GitHub.

µ