Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell: Get-NetTCPConnection script that also shows Username & Process Name

I created a script to output Get-NetTCPConnection data but additionally show Process Name and Username. The script does work, but I would love any tips to simplify or make this more canonical.

I am wondering if there is a more efficient way to add the ProcessName and Username to the output without having to preload the values into the custom PSObject ($obj array). I am concerned that the custom e={($obj |? PID -eq $_.OwningProcess | select -ExpandProperty UserName)}} expression is overly complex.

$obj=@()

Foreach($p In (Get-Process -IncludeUserName | where {$_.UserName} | `
  select Id, ProcessName, UserName)) {
      $properties = @{ 'PID'=$p.Id;
                       'ProcessName'=$p.ProcessName;
                       'UserName'=$p.UserName;
                     }
      $psobj = New-Object -TypeName psobject -Property $properties
      $obj+=$psobj
  }

Get-NetTCPConnection | where {$_.State -eq "Established"} | select `
  RemoteAddress, `
  RemotePort, `
  @{n="PID";e={$_.OwningProcess}}, @{n="ProcessName";e={($obj |? PID -eq $_.OwningProcess | select -ExpandProperty ProcessName)}}, `
  @{n="UserName";e={($obj |? PID -eq $_.OwningProcess | select -ExpandProperty UserName)}} |
  sort -Property ProcessName, UserName |
  ft -auto

Here's a screenshot with some sample output:

screenshot

like image 232
luckman212 Avatar asked Jun 12 '17 21:06

luckman212


3 Answers

get-nettcpconnection | select local*,remote*,state,@{Name="Process";Expression={(Get-Process -Id $_.OwningProcess).ProcessName}}

I can't take credit for this solution, I found it here: https://superuser.com/questions/1215093/powershell-one-liner-to-show-process-on-same-line-as-port-using-netstat-issue/1215237

like image 109
MattZ Avatar answered Oct 09 '22 22:10

MattZ


The pattern:

$a = @()
foreach ($x in Get-Thing|..) {
    $z = ..
    $a += $z
}

is not a great one, because += on an array does a lot of in-memory copying of everything to a new array, so it can be surprisingly slow. foreach() {} can sometimes run faster than | ForEach-Object {} but if you have a pipeline inside the () part and array addition going on, it's possibly not going to make much difference there. Typical alternatives:

$a = foreach ($x in Get-Thing|..) {
    $z = $x...
    $z
}

or

$a = Get-Thing | .. | ForEach-Object {
    $_..
}

The first half of your script does approximately nothing:

  • starts with an array of process objects
  • filters out ones with no username, even though the empty usernames wouldn't change anything later on in your script
  • selects three properties, copying them from one object into a new custom object, even though the script wasn't going to be affected by the other properties being there.
  • takes those same three properties out into a hashtable ..
  • converts that hashtable back into a custom object, putting you back where you were after the select.
  • puts it back into another array, but slowly.

Original: process objects in an array.

Result: tweaked process objects in an array, changed, but not meaningfully so given the way you use them.

So you could drop all that for just $obj = Get-Process -IncludeUsername and it would work the same.

  • You don't need as many backticks for line continuation. PowerShell is pretty good at continuing lines where it makes sense - after pipes |, and after commas in arrays, including select property arrays. It's good because the backtick breaks if there are any spaces after it, which is hard to see.

But if you want pre-processing to make the rest of the script nicer, use a hashtable, then the lookup in the second part becomes shorter and clearer:

# Make a lookup table by process ID
$Processes = @{}
Get-Process -IncludeUserName | ForEach-Object {
    $Processes[$_.Id] = $_
}

Get-NetTCPConnection | 
    Where-Object { $_.State -eq "Established" } |
    Select-Object RemoteAddress,
        RemotePort,
        @{Name="PID";         Expression={ $_.OwningProcess }},
        @{Name="ProcessName"; Expression={ $Processes[[int]$_.OwningProcess].ProcessName }}, 
        @{Name="UserName";    Expression={ $Processes[[int]$_.OwningProcess].UserName }} |
    Sort-Object -Property ProcessName, UserName |
    Format-Table -AutoSize

Anyway, now this comes out as the same answer as @avvi, I had to stop with it written but broken because I couldn't make it work until I saw their answer - Get-Process returns the Id as a number and Get-NetTCPConnection returns the OwningProcess as a string, so there needs to be a conversion for the hashtable lookup.

Posting anyway for the discussion about the looping / array building.

like image 4
TessellatingHeckler Avatar answered Oct 09 '22 22:10

TessellatingHeckler


I couldn't find the question in Code Review, so I'm posting answer here for the moment.

Using a dictionary (hashtable) simplifies the expression and improves the lookup time per pid.

$dict=@{}

Foreach($p In (Get-Process -IncludeUserName | where {$_.UserName -ne $null} | `
  select Id, ProcessName, UserName)) {
      $properties = @{ 'PID'=$p.Id;
                       'ProcessName'=$p.ProcessName;
                       'UserName'=$p.UserName;
                     }
      $psobj = New-Object -TypeName psobject -Property $properties
      $dict[$p.Id] = $psObj

  }

Get-NetTCPConnection | where {$_.State -eq "Established"} | select `
  RemoteAddress, `
  RemotePort, ` 
  @{n="PID";e={$_.OwningProcess}}, 
  @{n="ProcessName";e={ $dict[[int]$_.OwningProcess].ProcessName }}, `
  @{n="UserName"; e={ $dict[[int]$_.OwningProcess].UserName }} |
  sort -Property ProcessName, UserName | ft -auto
like image 2
Avner Avatar answered Oct 09 '22 20:10

Avner