Skip to content

Instantly share code, notes, and snippets.

@SweetAsNZ
Created December 2, 2025 21:46
Show Gist options
  • Select an option

  • Save SweetAsNZ/738965ad651eb2bcf9951c2a6c3c44bd to your computer and use it in GitHub Desktop.

Select an option

Save SweetAsNZ/738965ad651eb2bcf9951c2a6c3c44bd to your computer and use it in GitHub Desktop.
Analyzes Windows Firewall logs of the local machine and another computer(s) to identify network traffic that is sent by a Windows Computer with no matching inbound traffic on the destination computer.
function Compare-WindowsFirewallLogs {
<#
.SYNOPSIS
Analyzes Windows Firewall logs of the local machine and another computer(s) to identify network traffic that is sent by a
Computer with no matching inbound traffic on the destination computer.
.DESCRIPTION
This function analyzes outbound traffic from the local computer and checks if the destination
computer(s) received that traffic. It identifies network communication failures, blocked connections,
or dropped packets by finding outbound SEND traffic that has no corresponding inbound RECEIVE traffic
on the destination server.
The function is useful for troubleshooting network connectivity issues, firewall rule problems,
and identifying traffic that may be blocked between servers. It matches based on source IP,
destination IP, destination port, and protocol.
.PARAMETER FWLog
The specific firewall log file to read. Options are 'Domainfw.log', 'Firewall.log',
'Privatefw.log', and 'Publicfw.log'. Default is 'Domainfw.log'.
.PARAMETER OtherComputers
An array of remote computer names to retrieve firewall logs from for comparison with the local log.
.PARAMETER ForLastHours
Number of hours to look back from the current time for log entries. Ignored if ForLastMins is specified.
.PARAMETER ForLastMins
Number of minutes to look back from the current time for log entries. Takes precedence over ForLastHours.
.PARAMETER Ports
Optional array of specific ports to filter. Only connections using these ports will be compared.
.PARAMETER CheckIPv6
When specified, includes IPv6 traffic in the analysis. By default, only IPv4 traffic is analyzed.
.PARAMETER ExcludeLocalHost
When specified, excludes all entries with source or destination IP of 127.0.0.1 or ::1.
.PARAMETER ShowUnmatched
When specified, also displays successful connections that were received on destination computers.
.EXAMPLE
Compare-WindowsFirewallLogs -OtherComputers 'APSE2AEX812','AKL0EX804' -ForLastHours 1
Finds outbound traffic from this computer that was not received by the remote servers in the last hour.
.EXAMPLE
Compare-WindowsFirewallLogs -OtherComputers 'Server1' -ForLastMins 30 -Ports 445
Checks if SMB traffic (port 445) sent to Server1 in the last 30 minutes was received.
.EXAMPLE
Compare-WindowsFirewallLogs -ForLastMins 30 -Action Allow -Ports 443,445
Compares allowed traffic on ports 443 and 445 for the last 30 minutes.
.EXAMPLE
Compare-WindowsFirewallLogs -FWLog 'Publicfw.log' -ForLastHours 2 -ShowUnmatched
Analyzes public firewall log and shows both matched and unmatched connections.
.EXAMPLE
Compare-WindowsFirewallLogs -ForLastMins 15 -Action Drop -ExcludeLocalHost
Compares dropped traffic excluding localhost connections for the last 15 minutes.
.NOTES
Author: Tim West
Company: Air New Zealand/Sweet As Chocolate Ltd
Created: 2025-12-02
Updated: 2025-12-02
Status: WIP
Version: 2.0.0
.CHANGELOG
2025-12-02: Major rewrite (v2.0.0) by Tim West
- Refocused script to identify blocked/unreceived traffic from local computer to remote computers
- Removed local inbound traffic analysis (not needed for troubleshooting blocked traffic)
- Now only analyzes remote computer inbound logs to find missing traffic
- Inverted display logic: primary focus on blocked/unreceived connections (previously "unmatched")
- Added local IP detection to match outbound source with remote inbound destination
- Updated all output messages and statistics to reflect blocked/dropped traffic focus
2025-12-02: Initial version created by Tim West
- Implemented comparison logic for outbound/inbound traffic matching
- Added time-based filtering (hours and minutes)
- Added action filtering (Drop, Allow, All)
- Added port filtering capability
- Added ExcludeLocalHost option
- Added ShowUnmatched option for unmatched outbound connections
- Implemented color-coded output for matched/unmatched connections
- Added summary statistics for matched and unmatched connections
- Added OtherComputers parameter to retrieve remote firewall logs via Get-ChildItem
- Added CheckIPv6 switch parameter to include IPv6 traffic (excluded by default)
- Always excludes localhost traffic (127.0.0.1, ::1) regardless of other parameters
- Performance optimization: Pre-filter log lines by date before detailed parsing when time range specified
#>
[CmdletBinding()]
param(
[ValidateSet('Domainfw.log','Firewall.log','Privatefw.log','Publicfw.log')]
[string]$FWLog = 'Domainfw.log',
[string[]]$OtherComputers,
[int]$ForLastHours,
[int]$ForLastMins,
[ValidateSet('Drop','Allow','All')]
[string]$Action = 'All',
[int[]]$Ports,
[switch]$CheckIPv6,
[switch]$ExcludeLocalHost,
[switch]$ShowUnmatched
)
# Resolve local log path
$basePath = Join-Path $env:SystemRoot 'System32\LogFiles\Firewall'
$logPath = Join-Path $basePath $FWLog
# Validate local log file exists
if (-not (Test-Path $logPath)) {
Write-Host "ERROR: Local firewall log not found: $logPath" -ForegroundColor Red
return
}
# Collect remote logs if OtherComputers specified
$remoteLogs = @()
if ($OtherComputers -and $OtherComputers.Count -gt 0) {
Write-Host "`n=== Collecting Remote Firewall Logs ===" -ForegroundColor Cyan
foreach ($computer in $OtherComputers) {
try {
$remotePath = "\\$computer\c$\Windows\System32\LogFiles\Firewall\$FWLog"
Write-Host "Retrieving log from $computer..." -ForegroundColor Yellow
if (Test-Path $remotePath) {
$remoteLog = Get-ChildItem -Path $remotePath -ErrorAction Stop
$remoteLogs += [PSCustomObject]@{
ComputerName = $computer
Path = $remotePath
LogObject = $remoteLog
}
Write-Host " SUCCESS: Retrieved log from $computer" -ForegroundColor Green
}
else {
Write-Host " WARNING: Log not found at $remotePath" -ForegroundColor Yellow
}
}
catch {
Write-Host " ERROR: Failed to retrieve log from $computer - $($_.Exception.Message)" -ForegroundColor Red
}
}
if ($remoteLogs.Count -eq 0) {
Write-Host "WARNING: No remote logs could be retrieved" -ForegroundColor Yellow
}
}
# Display comparison parameters
Write-Host "`n=== Firewall Log Analysis ===" -ForegroundColor Cyan
Write-Host "Local Log: $logPath" -ForegroundColor White
if ($remoteLogs.Count -gt 0) {
Write-Host "Remote Logs: $($remoteLogs.Count) server(s) - $($remoteLogs.ComputerName -join ', ')" -ForegroundColor White
}
$timeRange = if ($ForLastMins) { "$ForLastMins minutes" }
elseif ($ForLastHours) { "$ForLastHours hours" }
else { "All time" }
Write-Host "Time Range: $timeRange" -ForegroundColor White
Write-Host "Action Filter: $Action" -ForegroundColor White
if ($Ports) {
Write-Host "Port Filter: $($Ports -join ', ')" -ForegroundColor White
}
Write-Host "`nProcessing logs..." -ForegroundColor Yellow
# Read and parse log file
$logLines = Get-Content -Path $logPath
$fieldsLine = $logLines | Where-Object { $_ -match '^#Fields:' } | Select-Object -First 1
if (-not $fieldsLine) {
Write-Host "ERROR: No '#Fields:' line found in log file" -ForegroundColor Red
return
}
$fields = $fieldsLine -replace '^#Fields:\s*', '' -split '\s+'
$fieldIndex = @{}
for ($i = 0; $i -lt $fields.Count; $i++) {
$fieldIndex[$fields[$i]] = $i
}
# 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()
}
$loopbackIPs = @('127.0.0.1','::1')
# Helper function to check if IP is IPv6
function Test-IsIPv6 {
param([string]$IP)
return $IP -match ':'
}
# Parse outbound traffic (SEND)
Write-Host "Parsing outbound traffic..." -ForegroundColor Yellow
Write-Host "Note: Excluding localhost traffic (127.0.0.1, ::1)" -ForegroundColor White
if (-not $CheckIPv6) {
Write-Host "Note: Excluding IPv6 traffic (use -CheckIPv6 to include)" -ForegroundColor White
}
$outboundConnections = New-Object System.Collections.ArrayList
$dataLines = $logLines | Where-Object { $_ -notmatch '^#' -and $_.Trim() -ne '' }
# Pre-filter by time if cutoff specified for performance
if ($hasTimeCutoff) {
$cutoffStr = $cutoff.ToString('yyyy-MM-dd HH:mm:ss')
$dataLines = $dataLines | Where-Object {
$_ -ge $cutoffStr.Substring(0, 10) # Quick date string comparison
}
}
[array]::Reverse($dataLines)
foreach ($line in $dataLines) {
$parts = $line -split '\s+'
if ($parts.Count -lt $fields.Count) {
continue
}
# Timestamp check
$timestamp = $null
if ($fieldIndex.ContainsKey('date') -and $fieldIndex.ContainsKey('time')) {
$dateStr = $parts[$fieldIndex['date']]
$timeStr = $parts[$fieldIndex['time']]
try {
$timestamp = Get-Date ("$dateStr $timeStr") -ErrorAction Stop
}
catch {
$timestamp = $null
}
}
if ($hasTimeCutoff -and $timestamp -and $timestamp -lt $cutoff) {
break
}
# Action filter
if ($actionFilter -and $fieldIndex.ContainsKey('action')) {
$logAction = $parts[$fieldIndex['action']]
if ($logAction -ne $actionFilter) {
continue
}
}
# Direction filter - only outbound (SEND)
if ($fieldIndex.ContainsKey('direction')) {
$dirVal = $parts[$fieldIndex['direction']]
if ($dirVal -ne 'SEND') {
continue
}
}
# Extract connection details
$srcIP = if ($fieldIndex.ContainsKey('src-ip')) { $parts[$fieldIndex['src-ip']] } else { $null }
$dstIP = if ($fieldIndex.ContainsKey('dst-ip')) { $parts[$fieldIndex['dst-ip']] } else { $null }
$srcPort = if ($fieldIndex.ContainsKey('src-port')) { $parts[$fieldIndex['src-port']] } else { $null }
$dstPort = if ($fieldIndex.ContainsKey('dst-port')) { $parts[$fieldIndex['dst-port']] } else { $null }
$protocol = if ($fieldIndex.ContainsKey('protocol')) { $parts[$fieldIndex['protocol']] } else { $null }
$logAction = if ($fieldIndex.ContainsKey('action')) { $parts[$fieldIndex['action']] } else { $null }
# Always exclude localhost traffic
if ($loopbackIPs -contains $srcIP -or $loopbackIPs -contains $dstIP) {
continue
}
# Port filter
if ($Ports -and $Ports.Count -gt 0) {
$matchPort = $false
foreach ($p in $Ports) {
$pStr = $p.ToString()
if ($srcPort -eq $pStr -or $dstPort -eq $pStr) {
$matchPort = $true
break
}
}
if (-not $matchPort) {
continue
}
}
# Create connection object
$connection = [PSCustomObject]@{
Timestamp = $timestamp
SourceIP = $srcIP
SourcePort = $srcPort
DestinationIP = $dstIP
DestinationPort = $dstPort
Protocol = $protocol
Action = $logAction
Matched = $false
}
[void]$outboundConnections.Add($connection)
}
Write-Host "Found $($outboundConnections.Count) outbound connections" -ForegroundColor Green
# Parse inbound traffic (RECEIVE) from remote destination computers only
Write-Host "Parsing inbound traffic on destination computers..." -ForegroundColor Yellow
# Build hashtable of inbound connections for fast lookup
$inboundConnections = @{}
# Get local computer's IP addresses for matching
$localIPs = New-Object 'System.Collections.Generic.HashSet[string]'
try {
[System.Net.Dns]::GetHostAddresses($env:COMPUTERNAME) |
Where-Object { $_.AddressFamily -eq [System.Net.Sockets.AddressFamily]::InterNetwork } |
ForEach-Object { [void]$localIPs.Add($_.ToString()) }
} catch {
Write-Host "WARNING: Could not resolve local IP addresses" -ForegroundColor Yellow
}
# Process remote logs for inbound traffic
foreach ($remoteLog in $remoteLogs) {
Write-Host "Processing inbound traffic from $($remoteLog.ComputerName)..." -ForegroundColor Yellow
try {
$remoteLines = Get-Content -Path $remoteLog.Path -ErrorAction Stop
$remoteDataLines = $remoteLines | Where-Object { $_ -notmatch '^#' -and $_.Trim() -ne '' }
# Pre-filter by time if cutoff specified for performance
if ($hasTimeCutoff) {
$cutoffStr = $cutoff.ToString('yyyy-MM-dd HH:mm:ss')
$remoteDataLines = $remoteDataLines | Where-Object {
$_ -ge $cutoffStr.Substring(0, 10) # Quick date string comparison
}
}
[array]::Reverse($remoteDataLines)
foreach ($line in $remoteDataLines) {
$parts = $line -split '\s+'
if ($parts.Count -lt $fields.Count) {
continue
}
# Timestamp check
$timestamp = $null
if ($fieldIndex.ContainsKey('date') -and $fieldIndex.ContainsKey('time')) {
$dateStr = $parts[$fieldIndex['date']]
$timeStr = $parts[$fieldIndex['time']]
try {
$timestamp = Get-Date ("$dateStr $timeStr") -ErrorAction Stop
}
catch {
$timestamp = $null
}
}
if ($hasTimeCutoff -and $timestamp -and $timestamp -lt $cutoff) {
break
}
# Action filter
if ($actionFilter -and $fieldIndex.ContainsKey('action')) {
$logAction = $parts[$fieldIndex['action']]
if ($logAction -ne $actionFilter) {
continue
}
}
# Direction filter - only inbound (RECEIVE)
if ($fieldIndex.ContainsKey('direction')) {
$dirVal = $parts[$fieldIndex['direction']]
if ($dirVal -ne 'RECEIVE') {
continue
}
}
# Extract connection details
$srcIP = if ($fieldIndex.ContainsKey('src-ip')) { $parts[$fieldIndex['src-ip']] } else { $null }
$dstIP = if ($fieldIndex.ContainsKey('dst-ip')) { $parts[$fieldIndex['dst-ip']] } else { $null }
$dstPort = if ($fieldIndex.ContainsKey('dst-port')) { $parts[$fieldIndex['dst-port']] } else { $null }
$protocol = if ($fieldIndex.ContainsKey('protocol')) { $parts[$fieldIndex['protocol']] } else { $null }
# Only consider inbound traffic where source is from our local computer
if ($localIPs.Count -gt 0 -and -not $localIPs.Contains($srcIP)) {
continue
}
# Always exclude localhost traffic
if ($loopbackIPs -contains $srcIP -or $loopbackIPs -contains $dstIP) {
continue
}
# Exclude IPv6 unless CheckIPv6 specified
if (-not $CheckIPv6 -and ((Test-IsIPv6 $srcIP) -or (Test-IsIPv6 $dstIP))) {
continue
}
# Port filter
if ($Ports -and $Ports.Count -gt 0) {
$matchPort = $false
foreach ($p in $Ports) {
$pStr = $p.ToString()
if ($dstPort -eq $pStr) {
$matchPort = $true
break
}
}
if (-not $matchPort) {
continue
}
}
# Create lookup key
$key = "$srcIP-$dstIP-$dstPort-$protocol"
if (-not $inboundConnections.ContainsKey($key)) {
$inboundConnections[$key] = New-Object System.Collections.ArrayList
}
[void]$inboundConnections[$key].Add($timestamp)
}
Write-Host " Processed $($remoteLog.ComputerName)" -ForegroundColor Green
}
catch {
Write-Host " ERROR processing $($remoteLog.ComputerName): $($_.Exception.Message)" -ForegroundColor Red
}
}
Write-Host "Found $($inboundConnections.Count) unique inbound connection patterns" -ForegroundColor Green
# Compare and find matches
Write-Host "`nComparing connections..." -ForegroundColor Yellow
$matchedConnections = New-Object System.Collections.ArrayList
$unmatchedConnections = New-Object System.Collections.ArrayList
foreach ($outbound in $outboundConnections) {
# Create lookup key for this outbound connection
$key = "$($outbound.SourceIP)-$($outbound.DestinationIP)-$($outbound.DestinationPort)-$($outbound.Protocol)"
if ($inboundConnections.ContainsKey($key)) {
$outbound.Matched = $true
[void]$matchedConnections.Add($outbound)
}
else {
[void]$unmatchedConnections.Add($outbound)
}
}
# Display results - focus on UNMATCHED (blocked/dropped) connections
Write-Host "`n=== BLOCKED/UNRECEIVED TRAFFIC ===" -ForegroundColor Red
Write-Host "Outbound traffic (SEND) that was NOT received on destination computer(s)" -ForegroundColor White
Write-Host "This traffic may be blocked by firewall rules or dropped in transit`n" -ForegroundColor Yellow
if ($unmatchedConnections.Count -gt 0) {
$unmatchedConnections |
Sort-Object Timestamp -Descending |
Format-Table -AutoSize Timestamp, SourceIP, SourcePort, DestinationIP, DestinationPort, Protocol, Action
}
else {
Write-Host "No blocked/unreceived connections found - all outbound traffic was received!" -ForegroundColor Green
}
# Display matched connections if requested
if ($ShowUnmatched) {
Write-Host "`n=== SUCCESSFUL CONNECTIONS ===" -ForegroundColor Green
Write-Host "Outbound traffic (SEND) that was successfully received on destination computer(s)`n" -ForegroundColor White
if ($unmatchedConnections.Count -gt 0) {
$unmatchedConnections |
Sort-Object Timestamp -Descending |
Format-Table -AutoSize Timestamp, SourceIP, SourcePort, DestinationIP, DestinationPort, Protocol, Action
}
else {
Write-Host "No unmatched connections found" -ForegroundColor Yellow
}
}
# Summary
Write-Host "`n=== SUMMARY ===" -ForegroundColor Cyan
Write-Host "Total Outbound Connections Analyzed: $($outboundConnections.Count)" -ForegroundColor White
Write-Host "Successfully Received: $($matchedConnections.Count)" -ForegroundColor Green
Write-Host "Blocked/Unreceived: $($unmatchedConnections.Count)" -ForegroundColor $(if ($unmatchedConnections.Count -gt 0) { 'Red' } else { 'Green' })
if ($outboundConnections.Count -gt 0) {
$successPercent = [math]::Round(($matchedConnections.Count / $outboundConnections.Count) * 100, 2)
$blockedPercent = [math]::Round(($unmatchedConnections.Count / $outboundConnections.Count) * 100, 2)
Write-Host "Success Rate: $successPercent%" -ForegroundColor $(if ($successPercent -ge 80) { 'Green' } elseif ($successPercent -ge 50) { 'Yellow' } else { 'Red' })
Write-Host "Blocked/Dropped Rate: $blockedPercent%" -ForegroundColor $(if ($blockedPercent -eq 0) { 'Green' } elseif ($blockedPercent -le 20) { 'Yellow' } else { 'Red' })
}
Write-Host ""
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment