Eric's Technical Outlet

Learning the hard way so you don't have to

PowerShell: Find Local Applications Blocked By a Remote Firewall

I’m sure we’ve all been there. You get an application that a vendor wrote and tested on a single, unfirewalled subnet. They sell it to you and you put it in your higher-security, multi-subnetted, firewalled environment, and it all falls down and goes boom. The vendor swears they’ve given you all the firewall information and then you go around-and-around for a few days, pulling network traces, etc.

So, I have thrown together a little script suite that should help you get to the bottom of it a little more quickly. Of course, a lot of times, you open one port only to discover that there is another port that will be needed that you couldn’t detect until the first one was open. Not much I can do about that in a PowerShell script, but watch the Altaro blog because I plan to demonstrate how to set up a test environment in Hyper-V to do this whole thing in a few minutes as opposed to the hours, or even weeks, it can sometimes take otherwise.

This script should be considered a first-draft work-in-progress. It doesn’t have any help text and it’s not nearly as robust as it could be or as I’d like it to be. It does what it’s supposed to do, though.

Note

This will only work with firewalls that are actively dropping ports. If the firewall is accepting packets and silently shipping them nowhere, Netstat can’t see that because its state will be ESTABLISHED.

Usage

There are four scripts in this module (to date).

Get-NetstatByState

This is the work-horse of the group. It pulls a Netstat list and filters it by one or more connection states, which you can specify. The default is “Any”, so that’s basically just the same list you normally get. It has switch parameters:

  • CloseWait
  • Established
  • FinWaitEither
  • FinWait1
  • FinWait2
  • LastAck
  • Listening
  • SynSent
  • TimeWait

I know there are other states, but these are the only ones I could verify in the 10 minutes I worked up this script. Post others in the comments and I’ll add switches for those.

If you specify one or more of these switches, the output of the script will be filtered only to those items.

Get-ParsedInfoFromNetstat

This function is intended to only accept input from Get-NetstatByState. You can supply any array of strings that you want, but try not to be surprised if you get a lot of errors. It emits an array of hash tables which I’m using as a quick-and-dirty custom data type. The “properties” of these items are:

  • Protocol
  • LocalAddress
  • LocalPort
  • RemoteAddress
  • RemotePort
  • State
  • PID
  • ProcessName
  • ProcessPath

Note that if you don’t run the script with administrative privileges, the “Path” parameter will often be empty.

So, if you want to see the process names of everything that’s in a LISTENING state:


Get-NetstatByState -Listening | Get-ParsedInfoFromNetstat | foreach {
$_['ProcessName']
}

To see active connections to 192.168.20.27:


Get-NetStatByState -Established | Get-ParsedInfoFromNetstat | where { $_['RemoteAddress'] -eq '192.168.0.27' }

You can use “where” to filter against things like “$_[‘RemoteAddress’] -eq ‘192.168.20.27’ ” to look for specific entries, etc.

Get-BlockedConnections

This calls Get-NetstatByState with the -SynSent parameter and ships it through Get-ParsedInfoFromNetstat.

Start-BlockConnectionWatch

This is the function that I wrote the whole thing to accomplish. It only has a single parameter, and that’s “CheckIntervalInSeconds”. It’s optional, so if you don’t enter anything it will check every second. It’s positional so you can just type a number, and it’s got two aliases (“Delay” and “Interval”).

All this does is call Get-BlockedConnections at each delay interval. If it finds an entry in SYN_SENT state, it adds it to the list to check on the next interval. Anything that’s found in two successive checks is assumed to be blocked.

Right now, it just emits to the screen like so:

Blocking address: 192.168.0.27. Blocked port: 443. Blocked process name: svchost

I intend to make that a lot better someday, but for now it solves my problem.

When you get tired of waiting or have found what you need, just press [CTRL]+C to interrupt it.

Installation

Copy the text in the following script block. Either save it into your profile file ($PROFILE) or as a .psm1 in your Modules directory (C:\Users\You\Documents\WindowsPowerShell\Modules\[NewFolderNameWithSameNameAsThePSM1File] or C:\Windows\System32\WindowsPowerShell\v1.0\Modules\\[NewFolderNameWithSameNameAsThePSM1File]) or save it as a .ps1 and dot-source it when you need it (ex: “. C\Scripts\NetstatInfo.ps1”).


function Get-NetstatByState
{
    [CmdletBinding(DefaultParameterSetName="Any")]
    param(
        [Parameter(ParameterSetName="Any")]
        [Switch]$Any,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$CloseWait,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$Established,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$FinWaitEither,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$FinWait1,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$FinWait2,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$LastAck,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$Listening,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$SynSent,
        
        [Parameter(ParameterSetName="Specific")]
        [Switch]$TimeWait
    )
    BEGIN {
        switch ($PSCmdlet.ParameterSetName)
        {
            "Any" {
                $Any = $true
            }
            default {
                $Any = $false
            }
        }
    }
    
    PROCESS {
        $InterestingStates = @()
        if($Any -or $CloseWait)            { $InterestingStates += @(,"CLOSE_WAIT") }
        if($Any -or $Established)        { $InterestingStates += @(,"ESTABLISHED") }
        if($Any -or $FinWaitEither)    { $InterestingStates += @(,"FIN_WAIT") }
        if($Any -or $FinWait1)            { $InterestingStates += @(,"FIN_WAIT_1") }
        if($Any -or $FinWait2)            { $InterestingStates += @(,"FIN_WAIT_2") }
        if($Any -or $LastAck)            { $InterestingStates += @(,"LAST_ACK") }
        if($Any -or $Listening)         { $InterestingStates += @(,"LISTENING") }
        if($Any -or $SynSent)            { $InterestingStates += @(,"SYN_SENT") }
        if($Any -or $TimeWait)            { $InterestingStates += @(,"TIME_WAIT") }
        
        $CurrentConnectionsRaw = netstat -aon | Select-String $InterestingStates
        $CurrentConnectionsRaw
    }
}

function Get-ParsedInfoFromNetstat
{
    param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [String[]]$FilteredNetstatAONOutput
    )
    
    PROCESS {
        $ParsedInfo = @()    # array to hold all the output
        [String]$CurrentProcessName = ""
        
        foreach ($NetstatEntry in $FilteredNetstatAONOutput)
        {
            $ParsedLine = @{} # hash table that will essentially be a new, unnamed custom data type to hold the "parsed" information
            
            $NetstatEntry = ($NetstatEntry -replace ('\s{2,}', ' ')).Trim()
            $NetstatEntry = $NetstatEntry.Split(' ')
            $CurrentProcess = Get-Process -Id ($NetstatEntry[($NetstatEntry.Count - 1)])    # more efficient to pull it once; safer to do it inline
            $ParsedLine.Add('Protocol', $NetstatEntry[0])
            $ParsedLine.Add('LocalAddress', $NetstatEntry[1].Substring(0, $NetstatEntry[1].LastIndexOf(":")))
            $ParsedLine.Add('LocalPort', $NetstatEntry[1].Substring($NetstatEntry[1].LastIndexOf(":") + 1))
            $ParsedLine.Add('RemoteAddress', $NetstatEntry[2].SubString(0, $NetstatEntry[2].LastIndexOf(":")))
            $ParsedLine.Add('RemotePort', $NetstatEntry[2].SubString($NetstatEntry[2].LastIndexOf(":") + 1))
            $ParsedLine.Add('State', $NetstatEntry[3])
            $ParsedLine.Add('PID', $NetstatEntry[4])
            $ParsedLine.Add('ProcessName', $CurrentProcess.ProcessName)
            $ParsedLine.Add('ProcessPath', $CurrentProcess.Path)
            $ParsedInfo += $ParsedLine
        }
        $ParsedInfo
    }    
}

function Get-BlockedConnections
{
    Get-NetstatByState -SynSent | Get-ParsedInfoFromNetstat
}

function Start-BlockConnectionWatch
{
    [CmdletBinding()]
    param(
        [Parameter(Position=1)]
        [Alias("Delay", "Interval")]
        [UInt32]$CheckIntervalInSeconds = 1
    )
    
    BEGIN {
        $PendingConnectionsAtLastCheck = @()
        $CurrentPendingConnections = @()
    }
    
    PROCESS {
        while($true)
        {
            $CurrentPendingConnections = @()
            $BlockList = Get-BlockedConnections
            foreach ($BlockedItem in $BlockList)
            {
                [Boolean]$Found = $false
                foreach ($ItemInLastCheck in $PendingConnectionsAtLastCheck)
                {
                    if(!$Found)
                    {
                        if($ItemInLastCheck['RemoteAddress'] -eq $BlockedItem['RemoteAddress'] -and $ItemInLastCheck['RemotePort'] -eq $BlockedItem['RemotePort'] -and $ItemInLastCheck['PID'] -eq $BlockedItem['PID'])
                        {
                            $Found = $true
                            Write-Host ("Blocking address: {0}. Blocked port: {1}. Blocked process name: {2}" -f $BlockedItem['RemoteAddress'], $BlockedItem['RemotePort'], $BlockedItem['ProcessName'])
                        }
                    }
                }
                $CurrentPendingConnections += $BlockedItem
                Write-Verbose ("Adding {0} to the watch list" -f $BlockedItem['ProcessName'])
            }
            $PendingConnectionsAtLastCheck = $CurrentPendingConnections
            Write-Verbose "Waiting for the next check interval..."
            Start-Sleep -Seconds $CheckIntervalInSeconds
        }
    }
}

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.