Map active TCP connections to processes
Documentation: Microsoft Learn
Established connections come back from Get-NetTCPConnection, and ForEach-Object builds a fresh [pscustomobject] per row carrying the local port with the owning process name.
The script
Script reader
Inspect before you run
Inspect the script below. It does not run in this browser.
Inspection only — no command has run.
Show reference output
Inspect this example’s structure
Read the command and its supplied arguments. This example requires real PowerShell or its module; this diagram does not execute it.
Get-NetTCPConnection -State Established | ForEach-Object { [pscustomobject]@{LocalPort= $_.LocalPort;RemoteAddress= $_.RemoteAddress;RemotePort= $_.RemotePort;Process=(Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue).ProcessName} }- Stage 1Get-NetTCPConnection
-State - Stage 2ForEach-Object
Positional values or no named parameters
How it works
Established connections come back from Get-NetTCPConnection, and ForEach-Object builds a fresh [pscustomobject] per row carrying the local port with the owning process name. The Process field calls Get-Process against OwningProcess inside the loop and reads ProcessName from the result, with -EA SilentlyContinue absorbing the lookup when the process has already exited.
Adapting it
Reading Path instead of ProcessName gives the full executable location, which matters when two unrelated binaries share a name. Grouping the finished output on Process turns a socket list into a per-application summary.
If the result is unexpected
The Process column is blank on several rows, and the command takes an unreasonable amount of time to finish on a busy server.
Each row triggers its own process lookup, and a short-lived connection whose process has already exited returns nothing, which the suppressed error turns into an empty field. On a host holding thousands of connections that is thousands of separate lookups, and the elapsed time reflects exactly that. An unelevated session also cannot open processes owned by other accounts.
- Build the process list once before the loop and index it into a hashtable keyed by Id.
- Rerun elevated and check whether previously blank names are now populated.
- Compare the connection count against the total from Get-NetTCPConnection to gauge how much work the loop is doing.
- Drop connections in TimeWait first, since those frequently have no live owning process at all.
- Keep the PID in the output alongside the name so a failed lookup still leaves something to investigate.
Validate the result
Every row carries either a process name or a PID that can be accounted for, and the command finishes in seconds rather than minutes.
Keep the check reliable
Cache lookups outside loops whenever the same query would otherwise repeat per row, which is the general fix for this whole class of slowness.