Show packets dropped by Windows Firewall

Version: 1.4.16
Creator Name: Ton de Vreede
Date Created: 2018-11-22
Date Modified: 2019-02-17
Scripting language: PS
Download Count: 99

Creates a report of the packets dropped by Windows firewall during the specified interval.
Tags: Windows Firewall, dropped packets, security

The Script

#requires -version 3
$ErrorActionPreference = 'Stop'
<#
    .SYNOPSIS
    This script will report on packets dropped by the firewall

    .DESCRIPTION
    This script will enable firewall logging for dropped packets if not enabled, log for the specified time in minutes,
    revert to old settings if settings were changed and output the firewall log of the packets that were dropped during the logging interval.
    -   Logging is enabled for each profile (Domain, Private and Public)
    -   If any logging was already enabled for a profile, dropped packet logging is also enabled if not yet so
    -   If logging was not enabled at all, dropped packet logging is enabled with a custom file log
    -   After logging, firewall settings are reverted to old settings and custom log file is removed

    The returned output is a grouping of the IP addresses,ports and actions with the count of each combination 

    .PARAMETER intSleepMinutes
    The amount of time in minutes the script will sleep, waiting for results to be logged in firewall log

    .PARAMETER bolReceive
    Include received packets in the report

    .PARAMETER bolSend
    Include sent packets in the report

    .PARAMETER intMaxGroups
    The maximum amount of results to be returned

    .EXAMPLE
    Example is not relevant as this script will be called through ControlUp Console

    .NOTES
    This script requires the NetSecurity module, which is only available on Server 2012 (or later) Windows 8 or later.

#>

# Logging duration
[int]$intSleepMinutes = $args[0]

# Flags for RECEIVE or SEND
If ($args[1] -eq 'True') {[bool]$bolReceive = $true} Else {[bool]$bolReceive = $false}
If ($args[2] -eq 'True') {[bool]$bolSend = $true} Else {[bool]$bolSend = $false}

# Maximum amount of lines (groups) to be displayed
[int]$intMaxGroups = $args[3]

# Array for the log files to be parsed
[array]$arrLogFileNames = @()

# String for Log file location if custom file is created
[string]$strCustomLogFileName = '%systemroot%\system32\LogFiles\Firewall\_TempControlUpFirewall.log'

# Output array
[array]$ParsedLogContents = @()

# Test if at least Receive or Send have been chosen to log
If (!$bolReceive -and !$bolSend){
    Write-Host 'You have chosen to report neither sent or received packets, so there will be no output. Please enable reporting for at least one of these options.'
    Exit 0
}
Function Feedback ($strFeedbackString)
{
  # This function provides feedback in the console on errors or progress, and aborts if error has occured.
  If ($error.count -eq 0)
  {
    # Write content of feedback string
    Write-Host -Object $strFeedbackString -ForegroundColor 'Green'
  }
  
  # If an error occured report it, and exit the script with ErrorLevel 1
  Else
  {
    # Write content of feedback string but in red
    Write-Host -Object $strFeedbackString -ForegroundColor 'Red'
    
    # Display error details
    Write-Host 'Details: ' $error[0].Exception.Message -ForegroundColor 'Red'

    Exit 1
  }
}

# Try to import the NetSecurityModel module
Try {
    Import-Module NetSecurity
  }
  Catch {
    Feedback "There was an error loading the NetSecurity module. This module is only available on Server 2012 or later and in some versions of Windows 8 or later."
  }


# Get firewall settings if firewall is enabled for that profile
$objStartWindowsFirewallSettings = Get-NetFirewallProfile | Where-Object Enabled

# Check the settings, if necesary change
If ($objStartWindowsFirewallSettings)
{
    ForEach($prf in $objStartWindowsFirewallSettings)
    {
        If (($prf.LogAllowed -eq 'True') -or ($prf.LogBlocked -eq 'True') -or ($prf.LogIgnored -eq 'True'))
        {
            # Firewall log has been (partially) enabled, turn on Dropped packet logging if necessary but leave log location intact
            If ($prf.LogBlocked -ne 'True')
            {
                Set-NetFirewallProfile -Name $prf.Name -LogBlocked True
            }    
        }
        else
        {
            # Firewall log not enabled for this profile, enable Dropped packet logging and use custom log location
            # Set size to 4092 just in case the size has been set too small, will also be reverted at the end of the script
            Set-NetFirewallProfile -Name $prf.Name -LogBlocked True -LogFileName $strCustomLogFileName -LogMaxSizeKilobytes 4092
        }
    }
}
# If none of the firewalls have been enabled, write output and exit
Else
{
    Write-Host 'Firewall logging can not be enabled as the firewall is disabled entirely'
    Exit 0
}

# Record logging start time
[datetime]$dtStartTime = Get-Date

# Wait specified amount of time for logging
Start-Sleep -Seconds ($intSleepMinutes * 60)

# Revert firewall settings if they have changed, and create list of filenames to be parsed
Foreach ($prf in $objStartWindowsFirewallSettings)
{   
    # Get current firewall settings to see what needs to be reverted
    $objCurrentProfileSettings = Get-NetFirewallProfile -Name $prf.Name

    # If the log file name is different, this means that logging has been enabled for dropped packets with custom log file name
    If ($prf.LogFileName -ne $objCurrentProfileSettings.LogFileName)
    {
        Set-NetFirewallProfile -Name $prf.Name -LogBlocked $prf.LogBlocked -LogFileName $prf.LogFileName -LogMaxSizeKilobytes $prf.LogMaxSizeKilobytes
        # Add log name to list of files to be parsed
        $arrLogFileNames += $strCustomLogFileName
    }
    # If only the LogBlocked setting has been changed, only that needs to be reverted
    ElseIf ($prf.LogBlocked -ne $objCurrentProfileSettings.LogBLocked)
    {
        Set-NetFirewallProfile -Name $prf.Name -LogBlocked $prf.LogBlocked
        # Add log name to list of file to be parsed
        $arrLogFileNames += $prf.LogFileName
    }
    # No settings have changed, only add log file name to the list
    Else
    {
        $arrLogFileNames += $prf.LogFileName
    }
}

# Read contents of logfile(s) and add to $ParsedLogContents
ForEach($filLogFile in ($arrLogFileNames | Get-Unique))
{
    # Declare csv headers
    [array]$arrCsvHeader = 'Date','Time','Action','Protocol','SourceIP','DestinationIP','SourcePort','DestinationPort','size','tcpflags','tcpsyn','tcpack','tcpwin','icmptype','icmpcode','info','Direction'
    
    # Create the array with contents
    $arrLogContents += Get-Content -Path ([System.Environment]::ExpandEnvironmentVariables("$filLogFile")) | Select-Object -Skip 6 | ConvertFrom-Csv -Header $arrCsvHeader -Delimiter ' '
    
    # If custom log file was used, no need to check dates
    If ($filLogFile -eq $strCustomLogFileName)
    {
        $ParsedLogContents += $arrLogContents | Where-Object {$_.Action -eq 'DROP'} 
    }
    # Standard log file, could contain old entries so parse date and time
    Else 
    {
        Foreach ($line in $arrLogContents)
        {
            If (($line.Action -eq 'DROP') -and ([datetime]::parseexact("$($line.date) $($line.time)", 'yyyy-MM-dd HH:mm:ss', $null) -gt $dtStartTime))
            {
                $ParsedLogContents += $line 
            } 
        }
    }
}

<# Clean up custom log files
- If logging was already enabled in any way, do not clean up that file
- If logging was turned on for this script, the custom log file was created (and a version with extension .old) so only custom log file and .old version need to be removed
Simply test if these exist and remove
#>
[string]$strFileToBeRemoved = [System.Environment]::ExpandEnvironmentVariables("$strCustomLogFileName")
If (Test-Path $strFileToBeRemoved) {Remove-Item $strFileToBeRemoved}
If (Test-Path "$strFileToBeRemoved.old") {Remove-Item "$strFileToBeRemoved.old"}

# Select Send, Receive or both and sort the output by datetime and output
# Grouped by source and destination
    # Some logic to work with the flags
    If ($bolReceive -and $bolSend) {
        $tmpReport = $ParsedLogContents | Select-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort | Group-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort | Sort-Object Count -Descending
        }
    elseif ($bolReceive) {
        $tmpReport = $ParsedLogContents | Where-Object {($_.Direction -eq 'RECEIVE')} | Select-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort | Group-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort  | Sort-Object Count -Descending
        }
    else {
        $tmpReport = $ParsedLogContents | Where-Object {($_.Direction -eq 'SEND')} | Select-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort | Group-Object -Property Action,Direction,Protocol,SourceIP,SourcePort,DestinationIP,DestinationPort  | Sort-Object Count -Descending
        }


# Put it all together in a PSCustomObject
If ($tmpReport.Count -ne 0){
    $FinalReport = @( Foreach ($obj in $tmpReport | Select-Object -First $intMaxGroups) {
        $objEx = $obj | Select-object -expandproperty Group
        [pscustomobject][ordered]@{
            Count                   = $obj.Count
            Action                  = $objEX[0].Action
            Direction               = $objEX[0].Direction
            Protocol                = $objEX[0].Protocol
            SourceIP                = $objEX[0].SourceIP
            SourcePort              = $objEX[0].SourcePort
            DestinationIP           = $objEX[0].DestinationIP
            DestinationPort         = $objEX[0].DestinationPort
        }
    })
    # Output the report
    $FinalReport | Sort-Object Count,SourceIP -Descending | Format-Table -AutoSize
}
Else {
    Feedback 'No dropped packets were logged during the logging interval.'
}