PowerShell Script to Log Network Connections

General Description

The Log-Connections.ps1 file is a PowerShell Script that Logs active TCP connections and includes the process ID (PID) and process name for each connection on a Microsoft Windows computer.  The log file name is a parameter that is passed to the script at run time.  A log entry is created every time that the list of processes with open connections or listening ports changes.  If the ports or remote addresses are not yet established, they are shown as an asterisk (*).

The Log-Connections script is based on the netstat command, “netstat –nao,” which can be run at the Windows command prompt to display a snapshot of all connections and listening ports.  The –o switch tells netstat to display the owning process ID that is associated with each process.  A limitation of the netstat command is that it cannot report the associated process name, just the PID.   To achieve this, the Log-Connections PowerShell script calls the Get-NetworkStatistics function.   This function was written by Shay Levy and is available at http://poshcode.org/2701 .

The Log-Connections script calls the Get-NetworkStatistics function repeatedly in an infinite loop, comparing the current snapshot with the previous.  If there is a change, the current snapshot is time stamped, logged to the file, and optionally passed through to the PowerShell pipeline.

Passing the connections snapshot object through to the PowerShell pipeline allows the data to be manipulated or displayed in real-time by other PowerShell cmdlets.  This will be illustrated in the examples that follow.

Quick Intro to PowerShell

Windows PowerShell is a command-line shell and a scripting language that is built upon the .NET Framework. PowerShell has been around since 2006, but is included in the base OS with Windows 7 and Windows Server 2008 R2.

 PowerShell can be invoked by typing “powershell” in the search box above the Windows Start Button, or by typing “powershell” at the Windows command prompt.

A great website to learn more about PowerShell is at http://www.powershellpro.com/powershell-tutorial-introduction/.

PowerShell Security Features

PowerShell has an script execution policy that by default will prevent scripts from being executed unintentionally.  To learn more about execution policies, type the following command in PowerShell:

Get-Help about_Execution_Policies

To be able to run the following examples, it is suggested that you change the execution policy to “RemoteSigned” so that local scripts can run unsigned and remote scripts can only run if signed by a trusted entity.  To do this run the following PowerShell command:

Set-ExecutionPolicy RemoteSigned –scope CurrentUser

To revert back to the default, just type:

Set-ExecutionPolicy Restricted

Another feature of PowerShell is that scripts cannot be executed in the current directory by typing just the script name.  Instead they must be prefaced with a dot and a backslash, ( “.\”).  This is illustrated in the examples below.

EXAMPLE 1

powershell .\Log-Connections.ps1 c:\workspace\mylog.csv

Example 1 shows the Log-Connections.ps1 script being invoked from the Windows command prompt. The ps1 script and the complete path to the log file are passed in as arguments the command “powershell.”  Also note the use the file extension of “CSV.”  This is convenient because on many systems when a CSV file is double clicked it will launch Microsoft Excel.  Any other extension, including “TXT” is also acceptable.

EXAMPLE 2

powershell c:\Temp\Log-Connections.ps1 mylog.csv svchost

Example 2 is very similar to the first one except a process name has been passed in as an argument and just the file name (without the path) has been provided, so the log file will be saved in the current directory, c:\workspace.  The full path to the script is provided because it is in c:\temp.

Note: if the log file exists already, the new observations will be appended to the bottom

EXAMPLE 3

powershell .\Log-Connections.ps1 mylog.csv svchost -PassThru

Using the “-PassThru” switch will cause the script display the results to the screen in a raw format in addition to logging them in the log file.

EXAMPLE 4

.\Log-Connections.ps1 -ProcName svchost -Filepath mylog.csv

In Example 4 the user typed “powershell” at the command prompt to invoke PowerShell. Then the name of the script and its arguments were typed at the PowerShell prompt.

This example also demonstrated the use of the named parameters convention.  Passing in the parameter value (e.g. “mylog.csv”)  after the parameter name (“-Filepath”) allows the parameters to be passed in out of order.

EXAMPLE 5

.\Log-Connections.ps1

Example 6 shows that PowerShell will gracefully request any missing parameters that are mandatory. In this case it is the FilePath value.

EXAMPLE 6

.\Log-Connections.ps1 mylog.csv svchost -PassThru | Format-Table

Example 6 illustrates piping the output of the Log-Connections script to the Format-Table cmdlet.  The Format-Table cmdlet produces a nice table of the results that will grow in real time.

EXAMPLE 7

.\Log-Connections.ps1 mylog.csv iexplore -PassThru | Out-GridView

Using the Out-Gridview cmdlet, as shown in Example 7, will produce a grid of the results.  The grid grows in real time and can be filtered and sorted.  The columns can also be re-arranged.

The grid can also be filtered using the “Add Criteria” button as shown below:

#* FileName: Log-Connections.ps1
 #*=============================================================================
 #* Script Name: Log-Connections
 #* Created:     [12/1/2012]
 #* Author:      Kenneth G. Hartman
 #* Email:       KGH@kennethGHartman.com
 #* Web:         http://www.KennethGHartman.com
 #*
 #* CREDITS:    Author of the Get-NetworkStatistics Function is
 #*             Shay Levy   http://PowerShay.com  http://poshcode.org/2701
 #*        http://blogs.microsoft.co.il/blogs/scriptfanatic/archive/2011/02/10/
 #*        How-to-find-running-processes-and-their-port-number.aspx
 #*=============================================================================

#*=============================================================================
 #* REVISION HISTORY
 #*=============================================================================
 #* Date: [12/16/2012]
 #* Description: Initial Version
 #*
 #*=============================================================================

[CmdletBinding()]
Param(
   [Parameter(Mandatory=$True,Position=0)]
   [string]$FilePath,

   [Parameter(Mandatory=$False,Position=1)]
   [string]$ProcName='*',

   [switch]$PassThru
) 

#*******************************************
#* Get-NetworkStatistics Function          *
#*******************************************

function Get-NetworkStatistics
{
    [OutputType('System.Management.Automation.PSObject')]
    [CmdletBinding(DefaultParameterSetName='name')]

    param(
        [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName='port')]
        [System.Int32]$Port='*',

        [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName='name')]
        [System.String]$ProcessName='*',

        [Parameter(Position=0,ValueFromPipeline=$true,ParameterSetName='address')]
        [System.String]$Address='*',        

        [Parameter()]
        [ValidateSet('*','tcp','udp')]
        [System.String]$Protocol='*',

        [Parameter()]
        [ValidateSet('*','Closed','CloseWait','Closing','DeleteTcb','Established','FinWait1',`
        'FinWait2','LastAck','Listen','SynReceived','SynSent','TimeWait','Unknown')]
        [System.String]$State='*'

    )

    begin
    {
        $properties = 'Protocol','LocalAddress','LocalPort'
            $properties += 'RemoteAddress','RemotePort','State','ProcessName','PID'
    }

    process
    {
        netstat -ano | Select-String -Pattern '\s+(TCP|UDP)' | ForEach-Object {

            $item = $_.line.split(' ',[System.StringSplitOptions]::RemoveEmptyEntries)

            if($item[1] -notmatch '^\[::')
            {           
                if (($la = $item[1] -as [ipaddress]).AddressFamily -eq 'InterNetworkV6')
                {
                   $localAddress = $la.IPAddressToString
                   $localPort = $item[1].split('\]:')[-1]
                }
                else
                {
                    $localAddress = $item[1].split(':')[0]
                    $localPort = $item[1].split(':')[-1]
                } 

                if (($ra = $item[2] -as [ipaddress]).AddressFamily -eq 'InterNetworkV6')
                {
                   $remoteAddress = $ra.IPAddressToString
                   $remotePort = $item[2].split('\]:')[-1]
                }
                else
                {
                   $remoteAddress = $item[2].split(':')[0]
                   $remotePort = $item[2].split(':')[-1]
                } 

                $procId = $item[-1]
                $ProcName = (Get-Process -Id $item[-1] -ErrorAction SilentlyContinue).Name
                $proto = $item[0]
                $status = if($item[0] -eq 'tcp') {$item[3]} else {$null}                

                $pso = New-Object -TypeName PSObject -Property @{
                    PID = $procId
                    ProcessName = $ProcName
                    Protocol = $proto
                    LocalAddress = $localAddress
                    LocalPort = $localPort
                    RemoteAddress =$remoteAddress
                    RemotePort = $remotePort
                    State = $status
                } | Select-Object -Property $properties                                

                if($PSCmdlet.ParameterSetName -eq 'port')
                {
                    if($pso.RemotePort -like $Port -or $pso.LocalPort -like $Port)
                    {
                        if($pso.Protocol -like $Protocol -and $pso.State -like $State)
                        {
                            $pso
                        }
                    }
                }

                if($PSCmdlet.ParameterSetName -eq 'address')
                {
                    if($pso.RemoteAddress -like $Address -or $pso.LocalAddress -like $Address)
                    {
                        if($pso.Protocol -like $Protocol -and $pso.State -like $State)
                        {
                            $pso
                        }
                    }
                }

                if($PSCmdlet.ParameterSetName -eq 'name')
                {        
                    if($pso.ProcessName -like $ProcessName)
                    {
                        if($pso.Protocol -like $Protocol -and $pso.State -like $State)
                        {
                               $pso
                        }
                    }
                }
            }
        }
    }
<#
.SYNOPSIS
    Displays the current TCP/IP connections.

.DESCRIPTION
    Displays active TCP connections and includes the process ID (PID) and Name for each connection.
    If the port is not yet established, the port number is shown as an asterisk (*).    

.PARAMETER ProcessName
    Gets connections by the name of the process. The default value is '*'.

.PARAMETER Port
    The port number of the local computer or remote computer. The default value is '*'.

.PARAMETER Address
    Gets connections by the IP address of the connection, local or remote. Wildcard is supported. 
    The default value is '*'.

.PARAMETER Protocol
    The name of the protocol (TCP or UDP). The default value is '*' (all)

.PARAMETER State
    Indicates the state of a TCP connection. The possible states are as follows:

    Closed      - The TCP connection is closed. 
    CloseWait   - The local endpoint of the TCP connection is waiting for a connection termination 
                  request from the local user. 
    Closing     - The local endpoint of the TCP connection is waiting for an acknowledgement of the 
                  connection termination request sent previously. 
    DeleteTcb   - The transmission control buffer (TCB) for the TCP connection is being deleted. 
    Established - The TCP handshake is complete. The connection has been established and data 
                  can be sent. 
    FinWait1    - The local endpoint of the TCP connection is waiting for a connection termination 
                  request from the remote endpoint or for an acknowledgement of the connection 
                  termination request sent previously. 
    FinWait2     - The local endpoint of the TCP connection is waiting for a connection termination 
                  request from the remote endpoint. 
    LastAck     - The local endpoint of the TCP connection is waiting for the final acknowledgement 
                  of the connection termination request sent previously. 
    Listen      - The local endpoint of the TCP connection is listening for a connection request 
                  from any remote endpoint. 
    SynReceived - The local endpoint of the TCP connection has sent and received a connection request 
                  and is waiting for an acknowledgment. 
    SynSent     - The local endpoint of the TCP connection has sent the remote endpoint a segment 
                  header with the synchronize (SYN) control bit set and is waiting for a matching 
                  connection request. 
    TimeWait    - The local endpoint of the TCP connection is waiting for enough time to pass to 
                  ensure that the remote endpoint received the acknowledgement of its connection 
                  termination request. 
    Unknown     - The TCP connection state is unknown.

    Values are based on the TcpState Enumeration:
    http://msdn.microsoft.com/en-us/library/system.net.networkinformation.tcpstate%28VS.85%29.aspx

.EXAMPLE
    Get-NetworkStatistics

.EXAMPLE
    Get-NetworkStatistics iexplore

.EXAMPLE
    Get-NetworkStatistics -ProcessName md* -Protocol tcp 

.EXAMPLE
    Get-NetworkStatistics -Address 192* -State LISTENING 

.EXAMPLE
    Get-NetworkStatistics -State LISTENING -Protocol tcp

.OUTPUTS
    System.Management.Automation.PSObject

.NOTES
    Author: Shay Levy
    Blog  : http://PowerShay.com
#>    
}

#*******************************************
#*                MAIN PROGRAM             *
#*******************************************

#Add Header to the CSV File
[string]$Previous = "TimeStamp,Protocol,LocalAddress,LocalPort,RemoteAddress,"
$Previous += "RemotePort,State,ProcessName,PID"
Add-Content $FilePath $Previous

#Initiate an infinite loop that calls the Get-NetworkStatistics Function repeatedly
#and formats the output as appropriate
while ($true) {
    $Observation = Get-NetworkStatistics $ProcName
    [string]$Current = $Observation | Out-String
    if ($Previous -ne $Current) {
        [string]$TimeStamp = Get-Date -Format o
        $Previous = $Current
        ForEach ($Socket in $Observation) {
            $Record = $TimeStamp + "," + $Socket.Protocol + "," + $Socket.LocalAddress + "," `
            + $Socket.LocalPort + "," + $Socket.RemoteAddress + "," + $Socket.RemotePort + "," `
            + $Socket.State + "," + $Socket.ProcessName  + "," + $Socket.PID
            Add-Content $FilePath $Record
            if ($PassThru) {
                $pso2 = New-Object -TypeName PSObject -Property @{
                    PID = $Socket.PID
                    ProcessName = $Socket.ProcessName
                    Protocol = $Socket.Protocol
                    LocalAddress = $Socket.LocalAddress
                    LocalPort = $Socket.LocalPort
                    RemoteAddress = $Socket.RemoteAddress
                    RemotePort = $Socket.RemotePort
                    State = $Socket.State
                    TimeStamp = $TimeStamp
                } | Select-Object -Property $properties    

                Write-Output $pso2
                }
            }
        }
    }

<#
.SYNOPSIS
    Creates a log of current TCP/IP connections and optionally passes them through to the pipeline.

.DESCRIPTION
    Logs active TCP connections and includes the process ID (PID) and Name for each connection.
    If the port is not yet established, the port number is shown as an asterisk (*).    

.PARAMETER FilePath
    The path and file name of the log file. Mandatory.

.PARAMETER ProcName
    Log only connections with the name of the process provided. The default value is '*'.

.SWITCH PassThru
    Return a process object to the screen or the pipeline. 

.EXAMPLE
    Log-Connections mylog.csv

.EXAMPLE
    Log-Connections mylog.csv svchost

.EXAMPLE
    Log-Connections mylog.csv svchost -PassThru

.EXAMPLE
    Log-Connections -Filepath mylog.csv -ProcName svchost 

.EXAMPLE
    Log-Connections mylog.csv svchost -PassThru | Format-Table

.EXAMPLE
    Log-Connections mylog.csv svchost -PassThru | Out-GridView

.OUTPUTS
    System.Management.Automation.PSObject

.NOTES
    Author : Kenneth G. Hartman
    Blog   : http://www.KennethGHartman.com
    Credits: Author of the Get-NetworkStatistics Function is
             Shay Levy   http://PowerShay.com  http://poshcode.org/2701
#>
Tagged . Bookmark the permalink.