Public/Get-LocalFirewallPolicy.ps1

function Get-LocalFirewallPolicy {
    <#
    .SYNOPSIS
        Reads local firewall rules from a server. Never modifies local policy.
 
    .DESCRIPTION
        Connects to one or more servers and reads their locally configured Windows
        Firewall rules. Rules delivered by Group Policy are automatically excluded
        so only manually configured local rules are returned.
 
        This function is READ-ONLY. It never creates, modifies, or deletes any
        firewall rule on the source server.
 
        For each rule the function also retrieves address filters, port filters,
        and application filters to provide full rule detail.
 
    .PARAMETER ComputerName
        One or more server names to read firewall rules from. Defaults to localhost.
        Accepts pipeline input.
 
    .PARAMETER ProfileFilter
        Filter rules by firewall profile. Valid values: Domain, Private, Public, Any.
        Defaults to Any (all profiles).
 
    .PARAMETER EnabledOnly
        When specified (default), only returns rules that are currently enabled.
        Use -EnabledOnly:$false to include disabled rules.
 
    .PARAMETER ExportPath
        Optional file path to save the results as JSON for review before migration.
 
    .EXAMPLE
        Get-LocalFirewallPolicy -ComputerName "SVR-WEB-01" -ExportPath .\firewall-export.json
 
        Reads all enabled local firewall rules from SVR-WEB-01 and saves them to JSON.
 
    .EXAMPLE
        Get-LocalFirewallPolicy -ComputerName "SVR-WEB-01","SVR-WEB-02" -ProfileFilter Domain
 
        Reads only Domain-profile firewall rules from two servers.
 
    .EXAMPLE
        "SVR-WEB-01","SVR-WEB-02" | Get-LocalFirewallPolicy -EnabledOnly:$false
 
        Reads all local firewall rules (including disabled) from servers via pipeline.
 
    .OUTPUTS
        PSCustomObject with properties: ComputerName, DisplayName, Direction, Action,
        Protocol, LocalPort, RemotePort, LocalAddress, RemoteAddress, Program, Profile,
        Enabled, Description, PolicySource
    #>

    [CmdletBinding()]
    param(
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [string[]]$ComputerName = @('localhost'),

        [Parameter()]
        [ValidateSet('Domain', 'Private', 'Public', 'Any')]
        [string]$ProfileFilter = 'Any',

        [Parameter()]
        [switch]$EnabledOnly = $true,

        [Parameter()]
        [string]$ExportPath
    )

    begin {
        $allResults = [System.Collections.Generic.List[PSObject]]::new()
        Write-Verbose "Get-LocalFirewallPolicy: READ-ONLY operation -- no local policy will be modified."
    }

    process {
        foreach ($computer in $ComputerName) {
            Write-Verbose "Reading local firewall rules from '$computer'..."

            $scriptBlock = {
                param($ProfileFilter, $EnabledOnly)

                # Get all local firewall rules -- exclude GPO-delivered rules
                $rules = Get-NetFirewallRule | Where-Object {
                    $_.PolicyStoreSourceType -ne 'GroupPolicy'
                }

                # Filter by enabled state
                if ($EnabledOnly) {
                    $rules = $rules | Where-Object { $_.Enabled -eq 'True' }
                }

                # Filter by profile if not Any
                if ($ProfileFilter -ne 'Any') {
                    $rules = $rules | Where-Object {
                        $_.Profile -match $ProfileFilter -or $_.Profile -eq 'Any'
                    }
                }

                foreach ($rule in $rules) {
                    # Retrieve associated filters for full detail
                    $addressFilter = $rule | Get-NetFirewallAddressFilter -ErrorAction SilentlyContinue
                    $portFilter    = $rule | Get-NetFirewallPortFilter -ErrorAction SilentlyContinue
                    $appFilter     = $rule | Get-NetFirewallApplicationFilter -ErrorAction SilentlyContinue

                    [PSCustomObject]@{
                        DisplayName  = $rule.DisplayName
                        Direction    = $rule.Direction.ToString()
                        Action       = $rule.Action.ToString()
                        Protocol     = if ($portFilter) { $portFilter.Protocol } else { 'Any' }
                        LocalPort    = if ($portFilter) { $portFilter.LocalPort } else { 'Any' }
                        RemotePort   = if ($portFilter) { $portFilter.RemotePort } else { 'Any' }
                        LocalAddress = if ($addressFilter) { $addressFilter.LocalAddress } else { 'Any' }
                        RemoteAddress= if ($addressFilter) { $addressFilter.RemoteAddress } else { 'Any' }
                        Program      = if ($appFilter -and $appFilter.Program -ne 'Any') { $appFilter.Program } else { $null }
                        Profile      = $rule.Profile.ToString()
                        Enabled      = ($rule.Enabled -eq 'True')
                        Description  = $rule.Description
                        PolicySource = 'Local'
                    }
                }
            }

            try {
                if ($computer -eq 'localhost' -or $computer -eq $env:COMPUTERNAME -or $computer -eq '.') {
                    Write-Verbose "Executing locally on '$computer'."
                    $results = & $scriptBlock -ProfileFilter $ProfileFilter -EnabledOnly $EnabledOnly
                }
                else {
                    Write-Verbose "Executing remotely on '$computer' via Invoke-Command."
                    $results = Invoke-Command -ComputerName $computer -ScriptBlock $scriptBlock `
                        -ArgumentList $ProfileFilter, $EnabledOnly -ErrorAction Stop
                }

                if ($results) {
                    foreach ($result in $results) {
                        # Add the source computer name
                        $obj = [PSCustomObject]@{
                            ComputerName  = $computer
                            DisplayName   = $result.DisplayName
                            Direction     = $result.Direction
                            Action        = $result.Action
                            Protocol      = $result.Protocol
                            LocalPort     = $result.LocalPort
                            RemotePort    = $result.RemotePort
                            LocalAddress  = $result.LocalAddress
                            RemoteAddress = $result.RemoteAddress
                            Program       = $result.Program
                            Profile       = $result.Profile
                            Enabled       = $result.Enabled
                            Description   = $result.Description
                            PolicySource  = $result.PolicySource
                        }
                        $allResults.Add($obj)
                        $obj
                    }
                    Write-Verbose "Retrieved $($results.Count) local firewall rule(s) from '$computer'."
                }
                else {
                    Write-Verbose "No local firewall rules found on '$computer' matching the specified criteria."
                }
            }
            catch {
                Write-Error "Failed to read firewall rules from '$computer': $_"
            }
        }
    }

    end {
        if ($ExportPath -and $allResults.Count -gt 0) {
            try {
                $exportDir = Split-Path -Path $ExportPath -Parent
                if ($exportDir -and -not (Test-Path $exportDir)) {
                    New-Item -ItemType Directory -Path $exportDir -Force | Out-Null
                }
                $allResults | ConvertTo-Json -Depth 10 | Set-Content -Path $ExportPath -Encoding UTF8
                Write-Verbose "Exported $($allResults.Count) rule(s) to '$ExportPath'."
                Write-Output "Export saved to: $ExportPath"
            }
            catch {
                Write-Error "Failed to export results to '$ExportPath': $_"
            }
        }
    }
}