Firewall/Get-FirewallRule.ps1

# Copyright 2012 Aaron Jensen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

function Get-FirewallRule
{
    <#
    .SYNOPSIS
    Gets the local computer's firewall rules.
     
    .DESCRIPTION
    Returns a `Carbon.Firewall.Rule` object for each firewall rule on the local computer.
     
    This data is parsed from the output of:
     
        netsh advfirewall firewall show rule name=all.
 
    You can return specific rule(s) using the `Name` or `LiteralName` parameters. The `Name` parameter accepts wildcards; `LiteralName` does not. There can be multiple firewall rules with the same name.
 
    If the firewall isn't configurable/running, writes an error and returns without returning any objects.
 
    .OUTPUTS
    Carbon.Firewall.Rule.
 
    .LINK
    Assert-FirewallConfigurable
 
    .EXAMPLE
    Get-FirewallRule
 
    Demonstrates how to get the firewall rules running on the current computer.
 
    .EXAMPLE
    Get-FirewallRule -Name 'World Wide Web Services (HTTP Traffic-In)'
 
    Demonstrates how to get a specific rule.
 
    .EXAMPLE
    Get-FirewallRule -Name '*HTTP*'
 
    Demonstrates how to use wildcards to find rules whose names match a wildcard pattern, in this case any rule whose name contains the text 'HTTP' is returned.
 
    .EXAMPLE
    Get-FirewallRule -LiteralName 'Custom Rule **CREATED BY AUTOMATED PROCES'
 
    Demonstrates how to find a specific firewall rule by name if that name has wildcard characters in it.
    #>

    [CmdletBinding(DefaultParameterSetName='All')]
    [OutputType([Carbon.Firewall.Rule])]
    param(
        [Parameter(Mandatory=$true,ParameterSetName='ByName')]
        [string]
        # The name of the rule. Wildcards supported. Names aren't unique, so you may still get back multiple rules
        $Name,

        [Parameter(Mandatory=$true,ParameterSetName='ByLiteralName')]
        [string]
        # The literal name of the rule. Wildcards not supported.
        $LiteralName
    )

    Set-StrictMode -Version 'Latest'
    
    if( -not (Assert-FirewallConfigurable) )
    {
        return
    }

    $containsWildcards = $false
    $nameArgValue = 'all'
    if( $PSCmdlet.ParameterSetName -eq 'ByName' )
    {
        $containsWildcards = [Management.Automation.WildcardPattern]::ContainsWildcardCharacters($Name) 
        if( -not $containsWildcards )
        {
            $nameArgValue = $Name
        }
    }
    elseif( $PSCmdlet.ParameterSetName -eq 'ByLiteralName' )
    {
        $nameArgValue = $LiteralName
    }

    # Don't change/move this. It's so we can detect if we've parsed a rule.
    $rule = $null

    $fieldMap = @{
                    'Rule name' = 'Name';
                    'Enabled' = 'Enabled';
                    'Direction' = 'Direction';
                    'Profiles' = 'Profiles';
                    'Grouping' = 'Grouping';
                    'LocalIP' = 'LocalIPAddress';
                    'RemoteIP' = 'RemoteIPAddress';
                    'Protocol' = 'Protocol';
                    'LocalPort' = 'LocalPort';
                    'RemotePort' = 'RemotePort';
                    'Edge traversal' = 'EdgeTraversal';
                    'InterfaceTypes' = 'InterfaceType';
                    'Security' = 'Security';
                    'Rule source' = 'Source';
                    'Action' = 'Action';
                    'Description' = 'Description';
                    'Program' = 'Program';
                    'Service' = 'Service';
                }

    $parsingProtocolTypeCode = $false
    netsh advfirewall firewall show rule name=$nameArgValue verbose | ForEach-Object {
        $line = $_
        
        Write-Verbose $line

        if( -not $line -and $rule )
        {
            $profiles = [Carbon.Firewall.RuleProfile]::Any
            $rule.Profiles -split ',' | ForEach-Object { $profiles = $profiles -bor ([Carbon.Firewall.RuleProfile]$_) }
            $constructorArgs = @(
                                    $rule.Name,
                                    $rule.Enabled,
                                    $rule.Direction,
                                    $profiles,
                                    $rule.Grouping,
                                    $rule.LocalIPAddress,
                                    $rule.LocalPort,
                                    $rule.RemoteIPAddress,
                                    $rule.RemotePort,
                                    $rule.Protocol,
                                    $rule.EdgeTraversal,
                                    $rule.Action,
                                    $rule.InterfaceType,
                                    $rule.Security,
                                    $rule.Source,
                                    $rule.Description,
                                    $rule.Program,
                                    $rule.Service
                                )
            New-Object -TypeName 'Carbon.Firewall.Rule' -ArgumentList $constructorArgs
            return
        }

        if( $line -match '^ +Type +Code *$' )
        {
            $parsingProtocolTypeCode = $true
            return
        }

        if( $parsingProtocolTypeCode )
        {
            $parsingProtocolTypeCode = $false
            if( $line -notmatch '^ +?([^ ]+) +?([^ ]+) *$' )
            {
                Write-Warning ('Failed to parse protocol type/code for rule {0}' -f $rule.Name)
                return
            }
            $rule.Protocol = '{0}:{1},{2}' -f $rule.Protocol,$Matches[1],$Matches[2]
        }
        
        if( $line -notmatch '^([^:]+): +(.*)$' )
        {
            return
        }
        
        $propName = $matches[1]
        $value = $matches[2]
        if( -not $fieldMap.ContainsKey( $propName ) )
        {
            Write-Warning ('Unknown field ''{0}'' for rule ''{1}'' in `netsh advfirewall firewall show rule` output.' -f $propName,$rule.Name)
            return
        }
        
        $propName = $fieldMap[$propName]
        if( $propName -eq 'Name' )
        {
            $rule = New-Object 'PsObject'
            foreach( $item in $fieldMap.Values )
            {
                Add-Member -InputObject $rule -MemberType NoteProperty -Name $item -Value $null
            }
            $rule.InterfaceType = [Carbon.Firewall.RuleInterfaceType]::Any
            $rule.Security = [Carbon.Firewall.RuleSecurity]::NotRequired
        }

        if( $propName -eq 'Enabled' )
        {
            $value = if( $value -eq 'No' ) { $false } else { $value }
            $value = if( $value -eq 'Yes' ) { $true } else { $value }
        }
        
        $rule.$propName = $value
    } |
    Where-Object { 
        -not $containsWildcards -or $_.Name -like $Name 
    }
}

Set-Alias -Name 'Get-FirewallRules' -Value 'Get-FirewallRule'