Powershell one-liner to show process on same line as port using netstat issue

First I just want to make sure user Erik Bitemo gets credit for the original code I'm using here. The output is what I'm looking for with one exception: One of the ports disappears and "System System5" show up in its place and I can't figure out why it's happening.

Goal: Show all TCP (Listening) and UDP ports and the process associated with each on same line.

One liner being used:

$nets = netstat -bano|select-string 'LISTENING|UDP'; foreach ($n in $nets)    {    $p = $n -replace ' +',' ';    $nar = $p.Split(' ');    $pname = $(Get-Process -id $nar[-1]).ProcessName;    $n -replace "$($nar[-1])","$($ppath) $($pname)";     }

Example Output:

TCP 0.0.0.0:135 0.0.0.0:0 LISTENING svchost
TCP 0.0.0.0: System System5 0.0.0.0:0 LISTENING System
TCP 0.0.0.0:623 0.0.0.0:0 LISTENING LMS

The port that it changes is 445 but I have no idea why it's changing only that one when the rest of the ports are working as intended. Why is the script changing 445 to "System System5"?

Using other tools is unfortunately not possible so I'm limited to using built-in Windows tools.


$p could be something like TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4 and $nar[-1] is string 4 so -replace operator takes all 4s:

TCP 0.0.0.0:445 0.0.0.0:0 LISTENING 4
            ↑↑                      ↑

Force replacing only last occurrence of $nar[-1] using end of line anchor (escaped $):

$p -replace "$($nar[-1])`$","$ppath $pname"

Read as well Matt's answer to Replacing last occurrence of substring in string at stackoverflow.

BTW:

  • $ppath is not defined…
  • …and netstat -ano should suffice (note that -b option can be time-consuming and will fail unless you have sufficient permissions).

JosefZ's answer perfectly explains your issue. You are using regex and it is doing exactly what you are asking it to do just perhaps replacing more than you expected.

A side note is that you are asking netstat to

displays the executable involved in creating each connection or listening port

with the b switch. However you are dropping that with select-string since that process appears on its own line after the other data. That is not the end of the world but because you can use things like -Context of Select-String to get that but that will have to be looked at more closely another time.

I would like to offer other suggestions as to what you can do in this situation.

Using other tools is unfortunately not possible so I'm limited to using built-in Windows tools.

Funny thing about that, do you have at least Windows 8? If you do you could just use the Get-NetTCPConnection cmdlet which is essentially netstat in object form.

So you could do this which should get you the same information without the hassle

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

Don't have Windows 8+? Well then we could improve on your parsing script. Taking it one step further to create object will prevent issue with regex matching.

netstat -ano | Where-Object{$_ -match 'LISTENING|UDP'} | ForEach-Object{
    $split = $_.Trim() -split "\s+"
    [pscustomobject][ordered]@{
        "Proto" = $split[0]
        "Local Address" = $split[1]
        "Foreign Address" = $split[2]
        # Some might not have a state. Check to see if the last element is a number. If it is ignore it
        "State" = if($split[3] -notmatch "\d+"){$split[3]}else{""}
        # The last element in every case will be a PID
        "Process Name" = $(Get-Process -Id $split[-1]).ProcessName
    }
}

If you were limited to PowerShell v2 then you would need to change the psobject and ordered casts

netstat -ano | Where-Object{$_ -match 'LISTENING|UDP'} | ForEach-Object{
    $split = $_.Trim() -split "\s+"
    New-Object -Type pscustomobject -Property @{
        "Proto" = $split[0]
        "Local Address" = $split[1]
        "Foreign Address" = $split[2]
        # Some might not have a state. Check to see if the last element is a number. If it is ignore it
        "State" = if($split[3] -notmatch "\d+"){$split[3]}else{""}
        # The last element in every case will be a PID
        "Process Name" = $(Get-Process -Id $split[-1]).ProcessName
    }
} | Select "Proto", "Local Address", "Foreign Address", "State", "Process Name" 

The last select statement guarantees the property order, which would be shuffled otherwise, and is the functional equivalent to [ordered]

So that will net you output like this...

Proto Local Address Foreign Address State     Process Name  
----- ------------- --------------- -----     ------------  
TCP   0.0.0.0:135   0.0.0.0:0       LISTENING svchost       
TCP   0.0.0.0:445   0.0.0.0:0       LISTENING System        
TCP   0.0.0.0:1279  0.0.0.0:0       LISTENING PlexDlnaServer
TCP   0.0.0.0:2869  0.0.0.0:0       LISTENING System  

Which you can then treat like you would any PowerShell object and filter as you see fit or output to CSV or whatever you need to do. It is structured now.

Depending on your PowerShell version you can also use Convert-FromString which takes single line strings and converts them to objects as well. Something else to look up.