windowspowershellfirewall

How to get a list of recent firewall changes and the details using powershell?


I would like to have a list of recent firewall changes (as is often made by installing software), and see the details of the firewall change that was made.

I managed to get a list within a certain time frame, from EventLogs, where the message contains the interesting info, but I am not sure how to best get that detail without resorting to long RegEx.

Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{logname="Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"; id=2097; StartTime=(Get-Date).AddHours(-2); EndTime=Get-Date}

   ProviderName: Microsoft-Windows-Windows Firewall With Advanced Security

TimeCreated                     Id LevelDisplayName Message
-----------                     -- ---------------- -------
2024-11-22 18:18:24           2097 Information      A rule has been added to the Windows Defender Firewall exception list.…
2024-11-22 18:18:24           2097 Information      A rule has been added to the Windows Defender Firewall exception list.…
2024-11-22 18:18:21           2097 Information      A rule has been added to the Windows Defender Firewall exception list.…
2024-11-22 18:18:21           2097 Information      A rule has been added to the Windows Defender Firewall exception list.…

Piping the previous command to Format-List (fl) gives the details:

TimeCreated  : 2024-11-22 18:18:24
ProviderName : Microsoft-Windows-Windows Firewall With Advanced Security
Id           : 2097
Message      : A rule has been added to the Windows Defender Firewall exception list.

               Added Rule:
                Rule ID:        UDP Query User{D0EBCDC7-8C31-463A-ADB9-A72A2FE0EFA9}C:\bin\arduino\arduino ide.exe
                Rule Name:      Arduino IDE
                Origin: Local
                Active: Yes
                Direction:      Inbound
                Profiles:       Public
                Action: Block
                Application Path:       C:\bin\arduino\arduino ide.exe
                Service Name:
                Protocol:       UDP
                Security Options:       None
                Edge Traversal: None
                Modifying User: S-1-5-80-3088073201-1464728630-1879813800-1107566885-823218052
                Modifying Application:  C:\Windows\System32\svchost.exe
                PolicyAppId:
                Error Code:     0

How can I selectively get the table with these columns?
TimeCreated, Protocol, Direction, 'Rule Name', 'Application Path'

Extra Credit would be to add the port number.


UPDATE:

The final function:

function get_firewall_changes {
    $nArgs = $($args.Count)
    if ( $nArgs -eq 0 ) { 
        $tHours = -1        # [hours] 1 hour  
    } else {
        $tHours = -[int] $args[0]
    }

    $selector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new(
    [string[]]@(
        "Event/EventData/Data[@Name='RuleName']"
        "Event/EventData/Data[@Name='ApplicationPath']"))
    $REX = '(?s)Direction:\s*(?<dir>[^\n]+).+Protocol:\s*(?<prot>[^\n]+)'

    Get-WinEvent -ErrorAction SilentlyContinue -FilterHashtable @{logname="Microsoft-Windows-Windows Firewall With Advanced Security/Firewall"; id=2097; StartTime=(Get-Date).AddHours($tHours); EndTime=Get-Date} | ForEach-Object {
        $RuleName, $ApplicationPath = $_.GetPropertyValues($selector)
        $Direction, $Protocol = [regex]::Match( $_.Message, $REX).Groups['dir', 'prot'].Value
        if ($RuleName.Length -gt 47 ) { $RuleName = ($RuleName[0..47] -join '') + '...'; }      # Truncate long names to 50 characters
        [pscustomobject]@{
            TimeCreated     = $_.TimeCreated
            Protocol        = $Protocol
            Direction       = $Direction
            RuleName        = $RuleName
            ApplicationPath = $ApplicationPath
        }
    } | Format-Table -Autosize -Wrap
}


Solution

  • You can use the same logic as the one shown in this answer however this comes with a caveat, the obtained Protocol and Direction will be uint and ushort and there isn't an available API that handles this translation from those values to human readable data, i.e. 6 -> Inbound, 1 -> TCP, etc. The event message is created by the EvtFormatMessage function.

    If that doesn't matter for you then use this approach:

    $selector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new(
        [string[]]@(
            "Event/EventData/Data[@Name='Protocol']"
            "Event/EventData/Data[@Name='Direction']"
            "Event/EventData/Data[@Name='RuleName']"
            "Event/EventData/Data[@Name='ApplicationPath']"))
    
    Get-WinEvent -FilterHashtable ..... | ForEach-Object {
        $Protocol, $Direction, $RuleName, $ApplicationPath = $_.GetPropertyValues($selector)
        [pscustomobject]@{
            TimeCreated     = $_.TimeCreated
            Protocol        = $Protocol
            Direction       = $Direction
            RuleName        = $RuleName
            ApplicationPath = $ApplicationPath
        }
    }
    

    Otherwise, you can extract those values from the .Message via regex, this approach seem to work:

    $selector = [System.Diagnostics.Eventing.Reader.EventLogPropertySelector]::new(
        [string[]]@(
            "Event/EventData/Data[@Name='RuleName']"
            "Event/EventData/Data[@Name='ApplicationPath']"))
    
    Get-WinEvent -FilterHashtable ..... | ForEach-Object {
        $RuleName, $ApplicationPath = $_.GetPropertyValues($selector)
        $Direction, $Protocol = [regex]::Match(
            $_.Message,
            '(?s)Direction:\s*(?<dir>[^\n]+).+Protocol:\s*(?<prot>[^\n]+)').
            Groups['dir', 'prot'].Value
    
        [pscustomobject]@{
            TimeCreated     = $_.TimeCreated
            Protocol        = $Protocol
            Direction       = $Direction
            RuleName        = $RuleName
            ApplicationPath = $ApplicationPath
        }
    }