Skip to content

Instantly share code, notes, and snippets.

@SweetAsNZ
Created January 29, 2026 02:41
Show Gist options
  • Select an option

  • Save SweetAsNZ/87fb9e0a3921698a485b59ba2370a25e to your computer and use it in GitHub Desktop.

Select an option

Save SweetAsNZ/87fb9e0a3921698a485b59ba2370a25e to your computer and use it in GitHub Desktop.
Retrieves entries from the Windows Firewall log based on specified criteria e.g. Drops From an IP Address, Port, Exclude String
function Get-WindowsFirewallLog {
<#
.SYNOPSIS
Retrieves entries from the Windows Firewall log based on specified criteria.
.DESCRIPTION
This function reads the Windows Firewall log file and filters entries based on the provided string.
.PARAMETER Action
The firewall action to filter by. Valid values are 'Drop', 'Allow', or 'All'.
.PARAMETER Direction
The traffic direction to filter by. Valid values are 'Inbound', 'Outbound', or 'All'.
Inbound (RECEIVE) = Traffic coming TO this machine (destination IP is this machine)
Outbound (SEND) = Traffic going FROM this machine (source IP is this machine)
.PARAMETER FWLog
The specific firewall log file to read. Options are 'Domainfw.log', 'Firewall.log'
'Privatefw.log', and 'Publicfw.log'. Default is 'All'.
.PARAMETER Ports
An array of specific ports to filter and check in the firewall log entries. Remove Parameter for all ports.
.PARAMETER SourceIP
Filter by specific source IP address(es). Supports quoted single IP or quoted array of IPs.
.PARAMETER DestinationIP
Filter by specific destination IP address(es). Supports quoted single IP or quoted array of IPs.
.PARAMETER ForLastHours
An integer specifying the number of hours to look back. Ignored if ForLastMins is specified.
.PARAMETER ForLastMins
An integer specifying the number of minutes to look back. Takes precedence over ForLastHours if specified.
.PARAMETER String
The string to search for in the firewall log entries. Default is 'Drop'.
.PARAMETER ExcludeString
A string or array of strings to exclude from the search results.
.PARAMETER ExcludeLocalHost
When specified, excludes all entries with source or destination IP of 127.0.0.1 or ::1.
.PARAMETER IgnoreIPv6
When specified, excludes all IPv6 entries from the results.
.EXAMPLE
Get-WindowsFirewallLog -FWLog All -Action Drop -Direction Inbound -ForLastMins 5 -ExcludeLocalHost -ExcludeString "3389"
.EXAMPLE
Get-WindowsFirewallLog -FWLog All -Action Drop -Direction Inbound -ForLastMins 10 -ExcludeLocalHost -ExcludeString "3389"
.EXAMPLE
Get-WindowsFirewallLog -FWLog All -Action Drop -Direction Inbound -ForLastHours 1 -ExcludeLocalHost -ExcludeString "3389"
.EXAMPLE
Get-WindowsFirewallLog -FWLog All -Action Drop -Direction Inbound -ForLastHours 6 -ExcludeLocalHost -ExcludeString "3389" -SourceIP ( ((Resolve-DNSName Server3 -Type A).IpAddress),`
((Resolve-DNSName Server1 -Type A).IpAddress),'10.1.2.4')
.EXAMPLE
Get-WindowsFirewallLog -FWLog All -Action All -Direction All -ForLastHours 6 -ExcludeLocalHost -ExcludeString "3389"
.EXAMPLE
Get-WindowsFirewallLog -Action Drop -Direction Inbound -ForLastHours 1 -ExcludeLocalHost -ExcludeString "3389" #,"ICMP"
.EXAMPLE
Get-WindowsFirewallLog -Action All -Direction Inbound -ForLastHours 1 -ExcludeLocalHost
.EXAMPLE
Get-WindowsFirewallLog -Action Allow -Direction All
.EXAMPLE
Get-WindowsFirewallLog -Action Allow -Direction Outbound -ForLastHours 1
.EXAMPLE
Get-WindowsFirewallLog -Action Allow -String '2025-10-20 14:30:15' -ExcludeString '192.168.1.1'
.EXAMPLE
Get-WindowsFirewallLog -Action Drop -ExcludeString '192.168.1.1','10.0.0.5' -ForLastHours 2
.EXAMPLE
Get-WindowsFirewallLog -Action Drop -Ports 445,3389 -Direction Inbound -ForLastHours 24
.EXAMPLE
Get-WindowsFirewallLog -Action Drop -SourceIP '192.168.1.100' -ForLastHours 12
.EXAMPLE
Get-WindowsFirewallLog -DestinationIP '10.0.0.1','10.0.0.2' -Direction Inbound -ForLastHours 6
.EXAMPLE
Get-WindowsFirewallLog -Action Allow -SourceIP '172.16.1.50','192.168.1.1'
.EXAMPLE
Get-FirewallLog -Action All -Direction All -ForLastHours 24 -FWLog 'PublicFW.log' # This is like an Alias function for Get-WindowsFirewallLog
.EXAMPLE
Get-WindowsFirewallLog -Action Drop -Direction Inbound -ForLastHours 1 -IgnoreIPv6
.NOTES
Author: Tim West
Created: 2024-06-15
Updated: 2025-12-19
Version: 3.4.2
.CHANGELOG
2024-06-15: Initial version created by Tim West
2025-10-20: Major update by Tim West
- Implemented time-based filtering with ForLastHours parameter (default: 24 hours)
- Added Ports parameter to filter by specific port numbers (source or destination)
- Added structured output with parsed log entries in formatted table
- Added port deduplication summary with occurrence counts
- Enhanced output with color-coded formatting for better readability
- Improved date parsing with ParseExact and regex validation
- Added empty file validation and error handling
- Changed port sorting from alphabetic to numeric
- Added summary statistics (total entries and unique ports)
- Fixed variable naming consistency
- Replaced PortsForLastHours with separate Ports and ForLastHours parameters
2025-10-22: Enhanced output by Tim West (v2.1)
- Added deduplicated source IP addresses grouped by destination port
- Enhanced reporting with unique source IP counts per port
- Improved output formatting with color-coded sections
2025-10-23: Enhanced filtering capabilities by Tim West (v2.2)
- Added Action parameter with ValidateSet for 'Drop', 'Allow', or 'All'
- Added Direction parameter with ValidateSet for 'Inbound', 'Outbound', or 'All'
- Renamed string parameter to String for consistency
- Enhanced filtering logic to support action and direction filtering
- Updated examples to demonstrate new filtering capabilities
2025-10-23: Added IP filtering capabilities by Tim West (v2.3)
- Added SourceIP parameter to filter by source IP address(es)
- Added DestinationIP parameter to filter by destination IP address(es)
- Both IP parameters support single IP or array of IPs
- Enhanced filtering logic to support IP-based filtering
- Updated examples to demonstrate IP filtering capabilities
2025-10-23: Fixed direction filtering validation by Tim West (v2.4)
- Corrected Inbound/Outbound direction logic interpretation
- Inbound (RECEIVE) properly validates destination is this machine
- Outbound (SEND) properly validates source is this machine
- Added clarifying documentation for direction parameter
2025-10-23: Added minute-based filtering by Tim West (v2.5)
- Added ForLastMins parameter for minute-based time filtering
- ForLastMins takes precedence over ForLastHours when specified
- Updated output messages to display correct time range
- Fixed parameter syntax error (missing comma)
- Updated alias function to include ForLastMins parameter
2025-11-07
- Added fix for using a string and All directions
2025-11-17
- Improved date parsing speed to parse only valid log lines
2025-11-19
- Fixed Action and Direction parameter filtering when parameters are null or empty
- Added default values 'All' to Action and Direction parameters
- Fixed filtering logic to check for null/empty before comparison
- Fixed ExcludeString bug that was always excluding 127.0.0.1 entries
- Fixed array reverse operation to handle single-entry logs correctly
- Added verbose logging for cutoff time debugging
- Fixed direction field extraction to use second-to-last field for variable length logs
- Added ExcludeLocalHost switch parameter to filter out localhost traffic
- Added detailed verbose output for debugging
- Fixed Source IP filtering when using ExcludeLocalHost and port summary output showing non-requested Source IP's
- Fixed variable width $fields
- Performance optimization: Replaced array += with ArrayList for O(n) complexity instead of O(n²)
- Code cleanup: Removed redundant Test-Path check, consolidated duplicate filter string building
- Refactoring: Added field position constants for better maintainability
- Enhanced error handling: Added try/catch around Get-Content and array operations
- Fixed Select-String object handling for consistent field access
2025-11-26
- Added robust direction detection when 'direction' field is missing by using local IPs
- Improved local IP retrieval with error handling for DNS resolution failures
- Updated documentation to reflect new direction detection logic
2025-12-01
- Added support for IPv6 loopback addresses (::1) in ExcludeLocalHost filtering
- Updated documentation to clarify IPv6 support
- Fixed minor formatting issues in documentation
- Changed output format to raw lines with heading on first line instead of PSCustomObject
- Removed object building and property assignment code for performance improvement
2025-12-02
- Fixed bug where String parameter defaulted to 'Drop', causing Action parameter to be ineffective
- Removed default value from String parameter to allow Action parameter to work correctly
- Updated version to 3.1.1
2025-12-05
- Added IgnoreIPv6 parameter to filter out IPv6 addresses
- Enhanced IPv6 detection to check for colon character in IP addresses
- Added example demonstrating IgnoreIPv6 parameter usage
- Updated version to 3.2.0
- Enhanced ExcludeString parameter to accept array of strings
- Updated filtering logic to check against multiple exclude strings
- Added example demonstrating multiple ExcludeString usage
- Updated version to 3.3.0
2025-12-19
- Implemented support for FWLog 'All' parameter value
- When FWLog is 'All', function now processes all firewall logs (Domainfw.log, Firewall.log, Privatefw.log, Publicfw.log)
- Added log file separators in output when processing multiple logs
- Changed missing log file behavior from throw to Write-Warning with continue
- Updated version to 3.4.0
- Fixed null reference exception when log file has no data entries
- Added check for empty $dataLines before calling [array]::Reverse()
- Updated version to 3.4.1
2026-01-29
- Fixed direction filtering logic to correctly handle 'All' option without excluding entries
- Updated version to 3.4.2
#>
[CmdletBinding()]
param(
[ValidateSet('Drop','Allow','All')]
[string]$Action = 'All',
[ValidateSet('Inbound','Outbound','All')]
[string]$Direction = 'All',
[ValidateSet('All','Domainfw.log','pfirewall.log','Privatefw.log','Publicfw.log')]
[string]$FWLog = 'All',
[int[]]$Ports,
[string[]]$SourceIP,
[string[]]$DestinationIP,
[int]$ForLastHours,
[int]$ForLastMins,
[string]$String,
[string[]]$ExcludeString,
[switch]$ExcludeLocalHost,
[switch]$IgnoreIPv6
)
Write-Host -f Green "Retrieving Windows Firewall log entries for $ENV:COMPUTERNAME"
# Resolve log path(s)
$basePath = Join-Path $env:SystemRoot 'System32\LogFiles\Firewall'
if ($FWLog -eq 'All') {
$logFiles = @('Domainfw.log', 'Firewall.log', 'Privatefw.log', 'Publicfw.log')
}
else {
$logFiles = @($FWLog)
}
foreach ($logFile in $logFiles) {
$logPath = Join-Path $basePath $logFile
if (-not (Test-Path $logPath)) {
Write-Warning "Firewall log not found: $logPath"
continue
}
# Output log file separator when processing multiple logs
if ($logFiles.Count -gt 1) {
Write-Output ""
Write-Output "=== Processing: $logFile ==="
Write-Output ""
}
$lines = Get-Content -Path $logPath
# Get #Fields line and build index map
$fieldsLine = $lines | Where-Object { $_ -match '^#Fields:' } | Select-Object -First 1
if (-not $fieldsLine) {
throw "No '#Fields:' line found in log: $logPath"
}
$fields = $fieldsLine -replace '^#Fields:\s*', '' -split '\s+'
$fieldIndex = @{}
for ($i = 0; $i -lt $fields.Count; $i++) {
$fieldIndex[$fields[$i]] = $i
}
# Precompute local IPs for direction heuristics when 'direction' field is missing
$localIPs = New-Object 'System.Collections.Generic.HashSet[string]'
try {
[System.Net.Dns]::GetHostAddresses($env:COMPUTERNAME) |
Where-Object {
$_.AddressFamily -in @(
[System.Net.Sockets.AddressFamily]::InterNetwork,
[System.Net.Sockets.AddressFamily]::InterNetworkV6
)
} |
ForEach-Object { [void]$localIPs.Add($_.ToString()) }
} catch {
# If DNS lookup fails, we just won't do IP-based direction detection
}
# Compute time cutoff
$cutoff = $null
$hasTimeCutoff = $false
if ($ForLastMins) {
$cutoff = (Get-Date).AddMinutes(-[double]$ForLastMins)
$hasTimeCutoff = $true
} elseif ($ForLastHours) {
$cutoff = (Get-Date).AddHours(-[double]$ForLastHours)
$hasTimeCutoff = $true
}
$actionFilter = $null
if ($Action -ne 'All') {
$actionFilter = $Action.ToUpper()
}
$inboundTag = 'RECEIVE'
$outboundTag = 'SEND'
$loopbackIPs = @('127.0.0.1','::1')
# Output heading as first line
Write-Output $fieldsLine
# Data lines only (reverse to process newest first)
$dataLines = $lines | Where-Object { $_ -notmatch '^#' -and $_.Trim() -ne '' }
# Check if there are any data lines
if (-not $dataLines -or $dataLines.Count -eq 0) {
Write-Verbose "No data entries found in log: $logPath"
continue
}
[array]::Reverse($dataLines)
foreach ($line in $dataLines) {
# Include string filter
if ($String) {
if ($line -notlike "*$String*") {
continue
}
}
# Exclude string filter
if ($ExcludeString -and $ExcludeString.Count -gt 0) {
$shouldExclude = $false
foreach ($excludePattern in $ExcludeString) {
if ($line -like "*$excludePattern*") {
$shouldExclude = $true
break
}
}
if ($shouldExclude) {
continue
}
}
$parts = $line -split '\s+'
if ($parts.Count -lt $fields.Count) {
continue
}
# Timestamp (use Get-Date for compatibility)
$timestamp = $null
if ($fieldIndex.ContainsKey('date') -and $fieldIndex.ContainsKey('time')) {
$dateStr = $parts[$fieldIndex['date']]
$timeStr = $parts[$fieldIndex['time']]
try {
# Firewall logs generally: yyyy-MM-dd HH:mm:ss
$timestamp = Get-Date ("$dateStr $timeStr") -ErrorAction Stop
}
catch {
$timestamp = $null
}
}
# Time filter - if we have a cutoff and timestamp is before it, stop parsing (log is chronological)
if ($hasTimeCutoff -and $timestamp) {
if ($timestamp -lt $cutoff) {
# Since we're reading newest-first, once we hit older entries, we can stop
break
}
}
# Action filter
if ($actionFilter -and $fieldIndex.ContainsKey('action')) {
$logAction = $parts[$fieldIndex['action']]
if ($logAction -ne $actionFilter) {
continue
}
}
# Ports filter (check src-port / dst-port if present)
if ($Ports -and $Ports.Count -gt 0) {
$srcPort = $null
$dstPort = $null
if ($fieldIndex.ContainsKey('src-port')) {
$srcPort = $parts[$fieldIndex['src-port']]
}
if ($fieldIndex.ContainsKey('dst-port')) {
$dstPort = $parts[$fieldIndex['dst-port']]
}
$matchPort = $false
foreach ($p in $Ports) {
$pStr = $p.ToString()
if ($srcPort -eq $pStr -or $dstPort -eq $pStr) {
$matchPort = $true
break
}
}
if (-not $matchPort) {
continue
}
}
# SourceIP filter
if ($SourceIP -and $SourceIP.Count -gt 0 -and $fieldIndex.ContainsKey('src-ip')) {
$srcIP = $parts[$fieldIndex['src-ip']]
if ($SourceIP -notcontains $srcIP) {
continue
}
}
# DestinationIP filter
if ($DestinationIP -and $DestinationIP.Count -gt 0 -and $fieldIndex.ContainsKey('dst-ip')) {
$dstIP = $parts[$fieldIndex['dst-ip']]
if ($DestinationIP -notcontains $dstIP) {
continue
}
}
# ExcludeLocalHost filter
if ($ExcludeLocalHost) {
$srcIP = $null
$dstIP = $null
if ($fieldIndex.ContainsKey('src-ip')) {
$srcIP = $parts[$fieldIndex['src-ip']]
}
if ($fieldIndex.ContainsKey('dst-ip')) {
$dstIP = $parts[$fieldIndex['dst-ip']]
}
if ($loopbackIPs -contains $srcIP -or $loopbackIPs -contains $dstIP) {
continue
}
}
# IgnoreIPv6 filter
if ($IgnoreIPv6) {
$srcIP = $null
$dstIP = $null
if ($fieldIndex.ContainsKey('src-ip')) {
$srcIP = $parts[$fieldIndex['src-ip']]
}
if ($fieldIndex.ContainsKey('dst-ip')) {
$dstIP = $parts[$fieldIndex['dst-ip']]
}
# IPv6 addresses contain colons
if (($srcIP -and $srcIP.Contains(':')) -or ($dstIP -and $dstIP.Contains(':'))) {
continue
}
}
# Direction filter
if ($Direction -ne 'All') {
$directionMatches = $false
# Prefer 'direction' field if present
if ($fieldIndex.ContainsKey('direction')) {
$dirVal = $parts[$fieldIndex['direction']]
switch ($Direction) {
'Inbound' {
if ($dirVal -eq $inboundTag) { $directionMatches = $true }
}
'Outbound' {
if ($dirVal -eq $outboundTag) { $directionMatches = $true }
}
}
}
else {
# Fallback heuristic using local IPs and src/dst
$srcIP = $null
$dstIP = $null
if ($fieldIndex.ContainsKey('src-ip')) {
$srcIP = $parts[$fieldIndex['src-ip']]
}
if ($fieldIndex.ContainsKey('dst-ip')) {
$dstIP = $parts[$fieldIndex['dst-ip']]
}
if ($localIPs.Count -gt 0 -and $srcIP -and $dstIP) {
switch ($Direction) {
'Inbound' {
if ($localIPs.Contains($dstIP)) { $directionMatches = $true }
}
'Outbound' {
if ($localIPs.Contains($srcIP)) { $directionMatches = $true }
}
}
}
}
if (-not $directionMatches) {
continue
}
}
# Output raw line
Write-Output $line
}
} # End foreach logFile
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment