Show top network sources by traffic volume (process)

Capture and analyse network traffic received for the specified process in a specified time period, showing the IP addresses and DNS names of the remote systems, the port used, the amount of data received in the capture period.
Arguments:
Capture Time - the number of seconds to run a network capture for (default 15 seconds)
Show top - show only the top n sources (default is 10)

Credits: based on "Capture Network IO for process" SBA by Andrew Morgan
Version 1.5.20
Created on 2018-08-22
Modified on 2018-11-20
Created by Guy Leech
Downloads: 278

The Script Copy Script Copied to clipboard
#Logman capture for Network Diagnostics
#requires -version 3.0

## Last modified 28/09/18 1657 BST @guyrleech

## Change these to repurpose the script - currently only supports one or other of sent or receieved being true, not both
[bool]$received = $true
[bool]$sent = $false
[bool]$perSession = $false
[bool]$perComputer = $false

[int]$top = 10
[int]$caplength = 30

if( $received -and $sent )
{
    Write-Warning 'Cannot run with both $received and $sent set to true'
}

if( $perSession -and $perComputer )
{
    Write-Warning 'Cannot run with both $perSession and $perCOmputer set to true'
}

if( ! $received -and ! $sent )
{
    Write-Warning 'Cannot run with both $received and $sent set to false'
}

[int]$outputWidth = 400

$DebugPreference = "SilentlyContinue"

If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator"))
{
    Write-Warning “You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!”
    Break
}

Function Cleanup{
        $ret=start-process -FilePath "$env:windir\system32\logman.exe" -ArgumentList "stop wdc -ets" -wait -PassThru
        foreach($file in Get-ChildItem "$env:temp\output*.etl"){
            Remove-Item $file.FullName -Force -ea SilentlyContinue
        }
}

[int]$sessionId =-1
[int]$ppid = -1

if( $perSession )
{
    $sessionId = $args[0]
    $theSession = quser.exe $sessionId
    if( ! $theSession -or ! $theSession.Count )
    {
        Throw "Session id $sessionId not found"
    }
}
elseif( ! $perComputer )
{
    $ppid = $args[0]
    if( ! ( Get-Process -Id $ppid -ErrorAction SilentlyContinue ) )
    {
        Throw "Process id $ppid no longer exists"
    }
}

if( $perComputer )
{
    if ( $args.Count -ge 1 -and $args[0]){$caplength = $args[0]}
    if ( $args.Count -ge 2 -and $args[1]){$top = $args[1]}
}
else
{
    if ( $args.Count -ge 2 -and $args[1]){$caplength = $args[1]}
    if ( $args.Count -ge 3 -and $args[2]){$top = $args[2]}
}

#write-debug "Capturing Process id: $ppid, process name $(get-process -id $ppid).processname for length: $caplength"

$connectionlogs=@()
$break=$false
$breakreason=""
$tempfile="$env:temp\output%d.etl"

#check prereqs
if (!(test-path $env:windir\system32\logman.exe)){$break=true;$breakreason="logman.exe could not be found"}

if (!$break){

    cleanup

    $loop=0
    #loop for $caplength seconds
    #Start capture here with logman
    write-host "Capturing ETL logs for $caplength seconds" -nonewline
    $ret=start-process -FilePath "$env:windir\system32\logman.exe" -ArgumentList "start wdc -p Microsoft-Windows-Kernel-Network 0x10 0xff -bs 64 -nb 16 38 -max 16 -mode newfile -o ""$tempfile"" -ets" -Wait -PassThru
    Start-Sleep 1
    if ( ! $ret -or $ret.ExitCode -ne 0){write-warning "could not start log capture process! $($ret.exitcode)";exit}
    Do{
        write-host "." -NoNewline
        $loop++
        start-sleep 1
    } while($loop -lt $caplength)

    write-host "Capturing complete"
    $ret=start-process -FilePath "$env:windir\system32\logman.exe" -ArgumentList "stop wdc -ets" -wait -PassThru
    if (!($ret.ExitCode -eq 0)){write-warning "could not stop log capture process! $($ret.exitcode)";exit}
    $logs=@()

    [hashtable]$connectionTable = @{}
    [long]$lastKey = $null
    $netobject = $null
    [hashtable]$sessionProcesses = @{}

    [string]$xpath = '*[System['
    if( $sent )
    {
        $xpath += 'EventID=10 or EventID=42'
    }
    if( $received )
    {
        if( $sent )
        {
            $xpath += ' or '
        }
        $xpath += 'EventID=11 or EventID=43'
    }
    $xpath += ']'
    if( $ppid -gt 0 )
    {
        $xpath += " and EventData[Data[@Name='PID']='$ppid']" ## if session then make an "or" list
    }
    ## FilterXPath string has a maximum length of around 512 so we can't filter on all session processes
    elseif( $sessionId -ge 0 )
    {
        Get-Process | Where-Object { $_.SessionId -eq $sessionId } | ForEach-Object {
            $sessionProcesses.Add( $_.Id , $true )
        }
        if( ! $sessionProcesses -or ! $sessionProcesses.Count )
        {
            Throw "Found no processes for session $sessionId"
        }
    }
    ## else computer scope so no process filtering
    $xpath += ']'

    $startProcessing = [datetime]::Now
    #loop for all logs
    Write-host "Anaylysing captured traffic"
    @( foreach($file in Get-ChildItem "$env:temp\output*.etl"){
        Get-WinEvent -Path $file.fullname -Oldest -FilterXPath $xpath -ErrorAction SilentlyContinue
    } ) | ForEach-Object {

        if( $sessionId -lt 0 -or $sessionProcesses[ $_.Properties[0].Value -as [int] ] )
        {
            $log = $_
            $udp=$false
            $TCP=$false
            $send=$false
            $receive=$false
            #define type
            Switch ($log.id)
            {
                10 {$tcp=$true;$send=$true}
                42 {$udp=$true;$send=$true}
                11 {$tcp=$true;$receive=$true}
                43 {$UDP=$true;$receive=$true}
            }

            $size=$log.Properties[1].Value

            [string]$mainkey = $null
            if( $sent )
            {
                $saddr = $null
                $sport = $null
                $daddr = [long]$log.Properties[2].Value
                $dport=(($log.Properties[4].Value -band 0xff ) -shl 8 ) -bor ( ( $log.Properties[4].Value -band 0xff00 ) -shr 8)
                $mainkey = "$daddr$dport"
            }
            if( $received )
            {
                $daddr = $null
                $dport = $null 
                $saddr = [long]$log.Properties[2].Value 
                $sport=(($log.Properties[4].Value -band 0xff ) -shl 8 ) -bor ( ( $log.Properties[4].Value -band 0xff00 ) -shr 8)
                $mainkey = "$saddr$sport"
            }
        
            ## if we already have object from previous iteration and it's the same key then use that object rather than looking up again
            if( ! $netobject -or $lastKey -ne $mainkey )
            {
                $netobject = $connectionTable[ $mainkey ]
            }
            
            if (!$netobject){
                $netObject = [pscustomobject]@{            
                    Source           = $saddr
                    Destination      = $daddr                 
                    sourceport       = $sport             
                    destinationport  = $dport           
                    UDPReceived      = 0            
                    UDPSent          = 0            
                    TCPReceived      = 0            
                    TCPSent          = 0            
                    TCPTotal         = 0            
                    UDPTotal         = 0            
                    TotalSent        = 0            
                    TotalReceived    = 0
                    PIDs             = New-Object System.Collections.ArrayList
                }
                $connectionTable.Add( $mainkey , $netobject )
            }
      
            if( $netobject.PIDs -notcontains $log.Properties[0].Value ) ## shortish list so lookup not too bad
            {
                $null = $netobject.PIDs.Add( $log.Properties[0].Value )
            }
            if ($tcp){
                if ($send){
                    $netobject.tcpsent+=$size
                    $netobject.totalsent+=$size
                }
                elseif ($receive){
                    $netobject.tcpreceived+=$size
                    $netobject.totalreceived+=$size
                }
                $netobject.TCPtotal+=$size
            }
            elseif ($udp){
            if ($send){
                    $netobject.udpsent+=$size
                    $netobject.totalsent+=$size
                }
                elseif ($receive){
                    $netobject.udpreceived+=$size
                    $netobject.totalreceived+=$size
                }
                $netobject.udptotal+=$size                
            }
            $lastKey = $mainkey
        }
    }
}
Else{
    write-warning "Script execution has been halted: $breakreason"
}

Write-Debug ( "$(Get-Date) : Data processing took {0} seconds" -f ([datetime]::Now - $startProcessing).TotalSeconds )

# Altering the size of the PS Buffer
$PSWindow = (Get-Host).UI.RawUI
$WideDimensions = $PSWindow.BufferSize
$WideDimensions.Width = $outputWidth
$PSWindow.BufferSize = $WideDimensions

[string]$heading = "`nShowing top $top network"
if( $received )
{
    $heading += ' sources for '
}
else
{
    $heading += ' destinations for '
}

[string]$scope = $null
if( $sessionId -ge 0 )
{
    $scope = "session $sessionId"
}
elseif( $ppid -gt 0 )
{
    $scope = "process $ppid ($(Get-Process -Id $ppid -ErrorAction SilentlyContinue|Select -ExpandProperty Name))"
}
else
{
    $scope = 'whole computer'
}
$heading += "$scope :"

$SummaryObject = [pscustomobject][ordered]@{ "Total Connections" = ($connectionTable.count) }

if( $connectionTable.count )
{
    if( $sent )
    {
        Add-Member -InputObject $SummaryObject -MemberType NoteProperty -Name "Total Sent (KB)" -Value ( [math]::Round(($connectionTable.GetEnumerator()|Select -ExpandProperty Value|Measure-Object -Property 'totalsent' -Sum).Sum /  1KB,1))
    }
    if( $received )
    {
        Add-Member -InputObject $SummaryObject -MemberType NoteProperty -Name "Total Received (KB)" -Value  ([math]::Round(($connectionTable.GetEnumerator()|Select -ExpandProperty Value|Measure-Object -Property 'totalreceived' -Sum).Sum /  1KB,1))
    }
}
else
{
    "No network traffic captured during $caplength seconds for $scope"
    return
}

$SummaryObject | Format-Table -AutoSize

$heading

[string[]]$rawSorter = $null 
$selector = $null

if( $received )
{
    $rawSorter = @( 'TotalReceived' )
    $selector = [System.Collections.ArrayList]@( 'Source' , @{n='Source DNS Name';e={[system.net.dns]::GetHostByAddress($_.source)|Select -ExpandProperty HostName}} , @{n='Source port';e={$_.SourcePort}} , @{n="Total Received (KB)";e={[math]::Round($_.TotalReceived / 1KB,1)}} )
}
if( $sent )
{
    $rawSorter = @( 'TotalSent' )
    $selector = [System.Collections.ArrayList]@( 'Destination' , @{n='Destination DNS Name';e={[system.net.dns]::GetHostByAddress($_.destination)|Select -ExpandProperty HostName}} , @{n='Destination port';e={$_.DestinationPort}} , @{n="Total Sent (KB)";e={[math]::Round($_.TotalSent / 1KB,1)}} )
}

if( $ppid -lt 0 )
{
    $null = $selector.Add( 'Process Name(s)' )
}

$connectionTable.GetEnumerator()|Select -ExpandProperty Value | Where-Object { $rawSorter } | Sort-Object -Property $rawSorter -descending | Select -First $top | ForEach-Object `
{
    ## reconstruct IP addresses
    if( $_.source )
    {
        $_.source = [ipaddress]$_.source
    }
    if( $_.destination )
    {
        $_.destination = [ipaddress]$_.destination
    }
    $result = $_
    if( $ppid -lt 0 ) ## computer or session context so have pids we need to convert to process names (and counts)
    {
        [hashtable]$processes = @{}
        $_.Pids | ForEach-Object `
        {
            [string]$thisProcess = Get-Process -Id $_ -ErrorAction SilentlyContinue | Select -ExpandProperty Name -ErrorAction SilentlyContinue
            if( [string]::IsNullOrEmpty( $thisProcess ) )
            {
                $thisProcess = $_ ## can't get process, probably as terminated
            }
            $existingProcess = $processes[ $thisProcess ]
            if( $existingProcess )
            {
                $processes.Set_Item( $thisProcess , [int]$existingProcess + 1 )
            }
            else
            {
                $processes.Add( $thisProcess , [int]1 )
            }
        }
        Add-Member -InputObject $result -MemberType NoteProperty -Name 'Process Name(s)' -Value (( $processes.GetEnumerator() | ForEach-Object { if( $_.Value -gt 1 ) { "$($_.Name)($($_.Value))" } else { "$($_.Name)" } } ) -join ',')
    }
    $result
} | select -Property $selector | Format-Table -AutoSize

cleanup