Functions/Get-FirewallRule.ps1

# 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.
     
    In Carbon 2.4.0 and earlier, this data is parsed from the output of:
     
        netsh advfirewall firewall show rule name=all
 
    which only works on english-speaking computers.
 
    Beginning with Carbon 2.4.1, firewall rules are read using the Windows Firewall with Advanced Security API's `HNetCfg.FwPolicy2` object.
 
    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.
 
    This function requires administrative privileges.
 
    .OUTPUTS
    Carbon.Firewall.Rule.
 
    .LINK
    Assert-FirewallConfigurable
 
    .LINK
    Carbon_FirewallRule
 
    .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'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState
    
    if( -not (Assert-FirewallConfigurable) )
    {
        return
    }

    $fw = New-Object -ComObject 'HNetCfg.FwPolicy2'
    $fw.Rules |
        Where-Object { 
            if( $PSCmdlet.ParameterSetName -eq 'ByLiteralName' )
            {
                return $_.Name -eq $LiteralName
            }

            if( -not $Name )
            {
                return $true
            }

            return $_.Name -like $Name 
        } | ForEach-Object {
    
            $rule = $_

            Write-Debug -Message $rule.Name

            $profiles = [Carbon.Firewall.RuleProfile]::Any
            if( $rule.Profiles -eq 0x7FFFFFFF )
            {
                $profiles = [Carbon.Firewall.RuleProfile]::Domain -bor [Carbon.Firewall.RuleProfile]::Private -bor [Carbon.Firewall.RuleProfile]::Public
            }
            else
            {
                if( ($rule.Profiles -band 1) -eq 1 )
                {
                    $profiles = $profiles -bor [Carbon.Firewall.RuleProfile]::Domain
                }
                if( ($rule.Profiles -band 2) -eq 2 )
                {
                    $profiles = $profiles -bor [Carbon.Firewall.RuleProfile]::Private
                }
                if( ($rule.Profiles -band 4) -eq 4 )
                {
                    $profiles = $profiles -bor [Carbon.Firewall.RuleProfile]::Public
                }
            }
            Write-Debug -Message (' Profiles {0,25} -> {1}' -f $rule.Profiles,$profiles)
            $protocol = switch( $rule.Protocol ) 
            {
                6 { 'TCP' }
                17 { 'UDP' }
                1 { 'ICMPv4' }
                58 { 'ICMPv6' }
                256 { 'Any' }
                default { $_ }
            }

            if( ($rule | Get-Member -Name 'IcmpTypesAndCodes') -and $rule.IcmpTypesAndCodes )
            {
                $type,$code = $rule.IcmpTypesAndCodes -split ':' | ConvertTo-Any
                if( -not $code )
                {
                    $code = 'Any'
                }
                $protocol = '{0}:{1},{2}' -f $protocol,$type,$code
                Write-Debug -Message (' IcmpTypesAndCode {0,25} -> {1},{2}' -f $rule.IcmpTypesAndCodes,$type,$code)
            }
            Write-Debug -Message (' Protocol {0,25} -> {1}' -f $rule.Protocol,$protocol)

            $direction = switch( $rule.Direction )
            {
                1 { [Carbon.Firewall.RuleDirection]::In }
                2 { [Carbon.Firewall.RuleDirection]::Out }
            }

            $action = switch( $rule.Action )
            {
                0 { [Carbon.Firewall.RuleAction]::Block }
                1 { [Carbon.Firewall.RuleAction]::Allow }
                default { throw ('Unknown action ''{0}''.' -f $_) }
            }

            $interfaceType = [Carbon.Firewall.RuleInterfaceType]::Any
            $rule.InterfaceTypes -split ',' |
                Where-Object { $_ -ne 'All' } |
                ForEach-Object {
                    if( $_ -eq 'RemoteAccess' )
                    {
                        $_ = 'Ras'
                    }
                    $interfaceType = $interfaceType -bor [Carbon.Firewall.RuleInterfaceType]::$_
                }
            Write-Debug -Message (' InterfaceType {0,25} -> {1}' -f $rule.InterfaceTypes,$interfaceType)

            function ConvertTo-Any
            {
                param(
                    [Parameter(ValueFromPipeline=$true)]
                    $InputObject
                )

                process
                {
                    if( $InputObject -eq '*' )
                    {
                        return 'Any'
                    }

                    $InputObject = $InputObject -split ',' |
                                        ForEach-Object { 
                                            $ipAddress,$mask = $_ -split '/'
                                            [ipaddress]$maskAddress = $null
                                            if( $mask -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$' -and [ipaddress]::TryParse($mask, [ref]$maskAddress) )
                                            {
                                                $cidr = $maskAddress.GetAddressBytes() | 
                                                            ForEach-Object { [Convert]::ToString($_, 2) -replace '[s0]' } |
                                                            Select-Object -ExpandProperty 'Length' |
                                                            Measure-Object -Sum | 
                                                            Select-Object -ExpandProperty 'Sum'
                                                return '{0}/{1}' -f $ipAddress,$cidr
                                            }
                                            return $_
                                        }
                    return $InputObject -join ','
                }
            }

            $localAddresses = $rule.LocalAddresses | ConvertTo-Any
            Write-Debug -Message (' LocalAddresses {0,25} -> {1}' -f $rule.LocalAddresses,$localAddresses)
            $remoteAddresses = $rule.RemoteAddresses | ConvertTo-Any
            Write-Debug -Message (' RemoteAddresses {0,25} -> {1}' -f $rule.RemoteAddresses,$remoteAddresses)
            $localPorts = $rule.LocalPorts | ConvertTo-Any
            Write-Debug -Message (' LocalPorts {0,25} -> {1}' -f $rule.LocalPorts,$localPorts)
            $remotePorts = $rule.RemotePorts | ConvertTo-Any
            Write-Debug -Message (' RemotePorts {0,25} -> {1}' -f $rule.RemotePorts,$remotePorts)

            $edgeTraversal = switch( $rule.EdgeTraversalOptions ) 
            {
                0 { 'No' }
                1 { 'Yes' }
                2 { 'Defer to application' }
                3 { 'Defer to user' }
            }

            $security = [Carbon.Firewall.RuleSecurity]::NotRequired
            if( $rule | Get-Member -Name 'SecureFlags' )
            {
                $security = switch( $rule.SecureFlags )
                {
                    1 { [Carbon.Firewall.RuleSecurity]::AuthNoEncap }
                    2 { [Carbon.Firewall.RuleSecurity]::Authenticate }
                    3 { [Carbon.Firewall.RuleSecurity]::AuthDynEnc }
                    4 { [Carbon.Firewall.RuleSecurity]::AuthEnc }
                    default { [Carbon.Firewall.RuleSecurity]::NotRequired }
                }
                Write-Debug -Message (' Security {0,25} -> {1}' -f $rule.SecureFlags,$security)
            }

            $serviceName = $rule.ServiceName | ConvertTo-Any
            Write-Debug -Message (' Service {0,25} -> {1}' -f $rule.ServiceName,$serviceName)


            $constructorArgs = @(
                                    $rule.Name, 
                                    $rule.Enabled,
                                    $direction,
                                    $profiles,
                                    $rule.Grouping,
                                    $localAddresses,
                                    $localPorts,
                                    $remoteAddresses,
                                    $remotePorts,
                                    $protocol,
                                    $edgeTraversal,
                                    $action,
                                    $interfaceType,
                                    $security,
                                    'Local Setting', 
                                    $rule.Description,
                                    $rule.ApplicationName,
                                    $serviceName
                                )
            New-Object -TypeName 'Carbon.Firewall.Rule' -ArgumentList $constructorArgs
        } 
}

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