Powershell : Visualise the Windows Firewall Log (if enabled)

If you have Windows Firewall enabled and your have it logging all the packets that get either dropped or allowed, first you firewall has to be enabled as below:



Then you need to ensure that you have packet logging enabled, I would not recommend you log "Allowed" packets but that is your decision, here you can see I only log dropped packets to the file pfirewall.log in the Windows directory.



So, what if I want to parse that log file the originally looks like this, which is readable:

2024-12-12 13:32:25 DROP TCP 10.80.44.264 10.80.44.259 10054 80 52 S 2359260937 0 64240 - - - RECEIVE
2024-12-12 13:32:28 DROP TCP 10.80.44.264 10.80.44.259 10054 80 52 S 2359260937 0 64240 - - - RECEIVE
2024-12-12 13:32:34 DROP TCP 10.80.44.264 10.80.44.259 10054 443 52 S 2359260937 0 64240 - - - RECEIVE

Then the script would turn that into a website that is nicely presented in a html document as below:

Ensure that the pfirewall.log is in the same directory as where your script is run from and the script will process this log file into a html file.

Script : FirewallLogVisuals.ps1

# Create cache directory if it doesn't exist
$cacheDir = Join-Path $PSScriptRoot "port_cache"
$cachePath = Join-Path $cacheDir "port_descriptions.json"
$ianaPortsPath = Join-Path $cacheDir "iana_ports.json"
if (-not (Test-Path $cacheDir)) {
    New-Item -ItemType Directory -Path $cacheDir | Out-Null
}
function Update-IANAPortDatabase {
    Write-Host "Downloading IANA port database..."
    try {
        # Download IANA service names and port numbers
        $url = "https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml"
        $webRequest = Invoke-WebRequest -Uri $url -UseBasicParsing       
        if ($webRequest.StatusCode -eq 200) {
            [xml]$portData = $webRequest.Content
            $ports = @{}           
            Write-Host "Processing port database..."
            # Get the correct namespace
            $ns = New-Object Xml.XmlNamespaceManager($portData.NameTable)
            $ns.AddNamespace("iana", "http://www.iana.org/assignments")           
            # Process each record with proper namespace
            $records = $portData.SelectNodes("//iana:record", $ns)
            $count = 0           
            foreach ($record in $records) {
                $numberNode = $record.SelectSingleNode(".//iana:number", $ns)
                $serviceNode = $record.SelectSingleNode(".//iana:name", $ns)
                $descriptionNode = $record.SelectSingleNode(".//iana:description", $ns)               
                if ($numberNode -and $serviceNode) {
                    # Some port records have multiple ports separated by '-' or ','
                    $numbers = $numberNode.InnerText -split '[-,]' | ForEach-Object { $_.Trim() }                   
                    foreach ($num in $numbers) {
                        if ($num -match '^\d+$') {
                            $portNum = [int]$num
                            $service = $serviceNode.InnerText.Trim()
                            $description = if ($descriptionNode) { $descriptionNode.InnerText.Trim() } else { ""
}                           
                            # Create meaningful description
                            if ($description) {
                                $portDescription = "$service - $description"
                            } else {
                                $portDescription = $service
                            }                           
                            if (-not $ports.ContainsKey($portNum)) {
                                $ports[$portNum] = $portDescription
                                $count++
                            }
                        }
                    }
                }
            }           
            Write-Host "Processed $count port entries"           
            # Save to cache
            $ports | ConvertTo-Json -Depth 10 | Set-Content $ianaPortsPath
            Write-Host "Port database updated successfully."
            return $ports
        }
    }
    catch {
        Write-Host "Failed to download IANA port database: $_"
        return $null
    }
}
# Test function to verify database content
function Test-PortDatabase {
    Write-Host "`nTesting port database entries:"
    Write-Host "Total ports in database: $($global:portDatabase.Count)"
# Check if port database exists and ask user about updating
     if (Test-Path $ianaPortsPath) {
    $lastUpdate = (Get-Item $ianaPortsPath).LastWriteTime
    Write-Host "`nIANA port database last updated: $lastUpdate"
    $updateChoice = Read-Host "Would you like to download a fresh copy of the port database? (y/n)"
    if ($updateChoice.ToLower() -eq 'y') {
        $global:portDatabase = Update-IANAPortDatabase
    } else {
        Write-Host "Using existing port database..."
        $global:portDatabase = Get-Content $ianaPortsPath | ConvertFrom-Json -AsHashtable
    }
} else {
    Write-Host "`nNo port database found. Downloading for the first time..."
    $global:portDatabase = Update-IANAPortDatabase
}
# Fallback common ports if IANA download fails
$commonPorts = @{
    20 = "FTP Data Transfer"
    21 = "FTP Control"
    22 = "SSH"
    23 = "Telnet"
    25 = "SMTP"
    53 = "DNS"
    80 = "HTTP"
    110 = "POP3"
    123 = "NTP"
    137 = "NetBIOS Name Service"
    139 = "NetBIOS Session Service"
    143 = "IMAP"
    443 = "HTTPS"
    445 = "Microsoft-DS"
    3389 = "RDP"
    49665 = "Windows RPC"
    59701 = "Windows RPC Dynamic Port"
    61948 = "DNS Zone Transfer"
    61949 = "DNS Zone Transfer"
}
if (-not $global:portDatabase) {
    Write-Host "Using fallback port database due to download failure..."
    $global:portDatabase = $commonPorts
}
function Get-PortDescription {
    param (
        [string]$port
    )   
    if ($port -eq "-") { return "N/A" }   
    try {
        [int]$portNum = $port
        if ($global:portDatabase.ContainsKey($portNum)) {
            # Return both port number and description
            return "$portNum - $($global:portDatabase[$portNum])"
        }
        return "$portNum - Unregistered Port"
    }
    catch {
        return "Invalid Port"
    }
}
function Convert-LogToHTML {
    param (
        [string]$LogPath
    )
    Write-Host "Processing log file..."
    $progressCount = 0
    $batchSize = 1000
    $logContent = @(Get-Content $LogPath | Select-Object -Skip 4)
    $totalLines = $logContent.Count
    $processedData = @()
    for ($i = 0; $i -lt $totalLines; $i += $batchSize) {
        $batch = $logContent | Select-Object -Skip $i -First $batchSize       
        $batchData = $batch | ForEach-Object {
            $progressCount++
            if ($progressCount % 100 -eq 0) {
                Write-Progress -Activity "Processing Log Entries" `
                    -Status "$progressCount of $totalLines entries" `
                    -PercentComplete (($progressCount / $totalLines) * 100)
            }
            $fields = $_ -split '\s+'
            if ($fields.Count -ge 9) {
                $srcPort = $fields[6]
                $dstPort = $fields[7]               
                @{
                    Timestamp = "$($fields[0]) $($fields[1])"
                    Protocol = $fields[3]
                    SourceIP = $fields[4]
                    SourcePort = $srcPort
                    SourcePortDesc = (Get-PortDescription $srcPort)
                    DestinationIP = $fields[5]
                    DestinationPort = $dstPort
                    DestinationPortDesc = (Get-PortDescription $dstPort)
                    Action = $fields[2]
                }
            }
        }       
        $processedData += $batchData
    }
    Write-Progress -Activity "Processing Log Entries" -Completed
    $htmlHeader = @"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Firewall Log Analysis Report</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css"
rel="stylesheet">
    <style>
        .table-container { 
            overflow-x: auto; 
            max-width: 100%;
        }
        .custom-table { 
            min-width: 100%; 
            border-collapse: collapse; 
            table-layout: fixed;
        }
        .custom-table th, .custom-table td { 
            padding: 8px 16px;
            text-align: left;
            border-bottom: 1px solid #e2e8f0;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .custom-table th { 
            background-color: #f7fafc;
            font-weight: 600;
            position: sticky;
            top: 0;
            z-index: 10;
        }
        .custom-table tr:hover { background-color: #f8fafc; }
        .drop { color: #e53e3e; font-weight: 600; }
        .allow { color: #38a169; font-weight: 600; }
        @media (max-width: 768px) {
            .custom-table {
                font-size: 14px;
            }
        }
    </style>
</head>

<body class="bg-gray-100">
    <div class="container mx-auto px-4 py-8">
        <div class="bg-white rounded-lg shadow-lg p-6">
            <div class="flex flex-col md:flex-row justify-between mb-6">
                <div>
                    <h1 class="text-2xl font-bold text-gray-800">Firewall Log Analysis Report</h1>
                    <p class="text-sm text-gray-600 mt-1">Processed Entries: $($processedData.Count)</p>
                </div>
                    <p class="text-gray-600">Generated: $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p>
                    <p class="text-sm text-gray-500">Log File: $([System.IO.Path]::GetFileName($LogPath)
                 </p>
                </div>
            </div>
            <div class="table-container">
                <table class="custom-table">
                    <thead>
                        <tr>
                            <th>Timestamp</th>
                            <th>Protocol</th>
                            <th>Source IP</th>
                            <th>Source Port</th>
                            <th>Source Service</th>
                            <th>Destination IP</th>
                            <th>Destination Port</th>
                            <th>Destination Service</th>
                            <th>Action</th>
                        </tr>
                    </thead>
                    <tbody>
"@
    $htmlRows = $processedData | ForEach-Object {
        $actionClass = if ($_.Action -eq "DROP") { "drop" } else { "allow" }
        @"
                        <tr>
                            <td>$($_.Timestamp)</td>
                            <td>$($_.Protocol)</td>
                            <td>$($_.SourceIP)</td>
                            <td>$($_.SourcePort)</td>
                            <td>$($_.SourcePortDesc)</td>
                            <td>$($_.DestinationIP)</td>
                            <td>$($_.DestinationPort)</td>
                            <td>$($_.DestinationPortDesc)</td>
                            <td class="$actionClass">$($_.Action)</td>
                        </tr>
"@
    }
    $htmlFooter = @"
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>
"@
   return $htmlHeader + $htmlRows + $htmlFooter
}
# Main script
$logFiles = Get-ChildItem -Filter "*.log"
if ($logFiles.Count -eq 0) {
    Write-Host "No log files found in the current directory."
    exit
}
Write-Host "`nAvailable log files:"
for ($i = 0; $i -lt $logFiles.Count; $i++) {
    Write-Host "$($i + 1). $($logFiles[$i].Name)"
}
do {
    $selection = Read-Host "`nEnter the number of the log file to analyze (1-$($logFiles.Count))"
    $index = [int]$selection - 1
} while ($index -lt 0 -or $index -ge $logFiles.Count)
$selectedFile = $logFiles[$index].FullName
$outputFile = "firewall_report_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"

# Generate and save the report
Write-Host "`nGenerating report..."
Convert-LogToHTML -LogPath $selectedFile | Out-File -FilePath $outputFile -Encoding UTF8
Write-Host "`nReport generated: $outputFile"

Previous Post Next Post

نموذج الاتصال