Public/network/Get-NetworkStatistic.ps1
|
#Requires -Version 5.1 function Get-NetworkStatistic { <# .SYNOPSIS Retrieves TCP and UDP connection statistics on one or more Windows computers. .DESCRIPTION Queries active network connections using Get-NetTCPConnection and Get-NetUDPEndpoint, then enriches each entry with the owning process name. Supports filtering by protocol, connection state, local/remote address, local/remote port, and process name. For the local machine, cmdlets are called directly. For remote machines, the query is executed via Invoke-Command, which requires WinRM / WS-Man enabled on the target. .PARAMETER ComputerName One or more computer names to query. Accepts pipeline input by value and by property name. Defaults to the local machine ($env:COMPUTERNAME). .PARAMETER Credential Optional PSCredential for authenticating to remote machines. Ignored for local machine queries. .PARAMETER Protocol Filter by protocol. Valid values: TCP, UDP. By default both are returned. .PARAMETER State Filter TCP connections by state (e.g. Established, Listen, TimeWait, CloseWait). Ignored for UDP endpoints (UDP is stateless). .PARAMETER LocalAddress Filter by local IP address. Supports wildcards. .PARAMETER LocalPort Filter by local port number. .PARAMETER RemoteAddress Filter by remote IP address. Supports wildcards. .PARAMETER RemotePort Filter by remote port number. .PARAMETER ProcessName Filter by owning process name. Supports wildcards. .PARAMETER Continuous Enable real-time auto-refresh mode. The function loops, clears the screen, and re-queries all target computers at each interval. Output is written directly to the host (not to the pipeline). Press Ctrl+C to stop. .PARAMETER RefreshInterval Refresh interval in seconds when using -Continuous. Default: 2. Valid range: 1–300 seconds. .EXAMPLE Get-NetworkStatistic Returns all TCP and UDP connections on the local machine. .EXAMPLE Get-NetworkStatistic -Protocol TCP -State Established Returns only established TCP connections on the local machine. .EXAMPLE Get-NetworkStatistic -ComputerName 'SRV01', 'SRV02' -Protocol TCP -State Listen Returns listening TCP connections on two remote servers. .EXAMPLE Get-NetworkStatistic -ProcessName 'svchost' -LocalPort 443 Returns connections on local port 443 owned by svchost. .EXAMPLE 'SRV01', 'SRV02' | Get-NetworkStatistic -Credential (Get-Credential) -Protocol TCP Queries multiple servers via pipeline with explicit credentials. .EXAMPLE Get-NetworkStatistic -Continuous Starts real-time monitoring of all network connections on the local machine, refreshing every 2 seconds. Press Ctrl+C to stop. .EXAMPLE Get-NetworkStatistic -Continuous -RefreshInterval 5 -Protocol TCP -State Established Monitors only established TCP connections with a 5-second refresh interval. .EXAMPLE Get-NetworkStatistic -ComputerName 'SRV01', 'SRV02' -Continuous -Protocol TCP Monitors TCP connections on two remote servers in real time. .OUTPUTS PSWinOps.NetworkStatistic Network connection details including protocol, addresses, ports, state, and process info. .NOTES Author: Franck SALLET Version: 1.1.0 Last Modified: 2026-03-21 Requires: PowerShell 5.1+ / Windows only Permissions: No admin required for basic queries Remote: Requires WinRM / WS-Man enabled on target machines Inspired by AdminToolbox.Networking Get-NetworkStatistics by TheTaylorLee. .LINK https://docs.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Write-Host is intentional in -Continuous mode for interactive console display')] [CmdletBinding()] [OutputType('PSWinOps.NetworkStatistic')] param( [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('CN', 'Name', 'DNSHostName')] [string[]]$ComputerName = $env:COMPUTERNAME, [Parameter(Mandatory = $false)] [PSCredential]$Credential, [Parameter(Mandatory = $false)] [ValidateSet('TCP', 'UDP')] [string[]]$Protocol = @('TCP', 'UDP'), [Parameter(Mandatory = $false)] [ValidateSet('Bound', 'Closed', 'CloseWait', 'Closing', 'DeleteTCB', 'Established', 'FinWait1', 'FinWait2', 'LastAck', 'Listen', 'SynReceived', 'SynSent', 'TimeWait')] [string[]]$State, [Parameter(Mandatory = $false)] [SupportsWildcards()] [string]$LocalAddress, [Parameter(Mandatory = $false)] [ValidateRange(1, 65535)] [int]$LocalPort, [Parameter(Mandatory = $false)] [SupportsWildcards()] [string]$RemoteAddress, [Parameter(Mandatory = $false)] [ValidateRange(1, 65535)] [int]$RemotePort, [Parameter(Mandatory = $false)] [SupportsWildcards()] [string]$ProcessName, [Parameter(Mandatory = $false)] [switch]$Continuous, [Parameter(Mandatory = $false)] [ValidateRange(1, 300)] [int]$RefreshInterval = 2 ) begin { Write-Verbose "[$($MyInvocation.MyCommand)] Starting network statistics query" $localNames = @($env:COMPUTERNAME, 'localhost', '.') $hasCredential = $PSBoundParameters.ContainsKey('Credential') # When -Continuous is used, collect all computers first, then loop in end {} if ($Continuous) { $continuousComputers = [System.Collections.Generic.List[string]]::new() } # Build the scriptblock that collects connections (runs locally or via Invoke-Command) $queryScriptBlock = { param( [string[]]$QueryProtocol, [string[]]$QueryState, [string]$QueryLocalAddress, [int]$QueryLocalPort, [string]$QueryRemoteAddress, [int]$QueryRemotePort, [string]$QueryProcessName ) $results = [System.Collections.Generic.List[PSObject]]::new() # Build process lookup table once (cast to [int] — OwningProcess is UInt32, # Process.Id is Int32; mismatched key types cause lookup failures) $processLookup = @{} foreach ($proc in (Get-Process -ErrorAction SilentlyContinue)) { $pidKey = [int]$proc.Id if (-not $processLookup.ContainsKey($pidKey)) { $processLookup[$pidKey] = $proc.ProcessName } } # TCP connections if ($QueryProtocol -contains 'TCP') { $tcpParams = @{ ErrorAction = 'SilentlyContinue' } if ($QueryState) { $tcpParams['State'] = $QueryState } $tcpConnections = Get-NetTCPConnection @tcpParams foreach ($conn in $tcpConnections) { $ownerPid = [int]$conn.OwningProcess $procName = if ($processLookup.ContainsKey($ownerPid)) { $processLookup[$ownerPid] } else { 'Unknown' } $obj = [PSCustomObject]@{ Protocol = 'TCP' LocalAddress = $conn.LocalAddress LocalPort = $conn.LocalPort RemoteAddress = $conn.RemoteAddress RemotePort = $conn.RemotePort State = [string]$conn.State ProcessId = $conn.OwningProcess ProcessName = $procName } $results.Add($obj) } } # UDP endpoints if ($QueryProtocol -contains 'UDP') { $udpEndpoints = Get-NetUDPEndpoint -ErrorAction SilentlyContinue foreach ($ep in $udpEndpoints) { $ownerPid = [int]$ep.OwningProcess $procName = if ($processLookup.ContainsKey($ownerPid)) { $processLookup[$ownerPid] } else { 'Unknown' } $obj = [PSCustomObject]@{ Protocol = 'UDP' LocalAddress = $ep.LocalAddress LocalPort = $ep.LocalPort RemoteAddress = '*' RemotePort = 0 State = 'Stateless' ProcessId = $ep.OwningProcess ProcessName = $procName } $results.Add($obj) } } # Apply client-side filters $filtered = $results if ($QueryLocalAddress) { $filtered = @($filtered | Where-Object { $_.LocalAddress -like $QueryLocalAddress }) } if ($QueryLocalPort -gt 0) { $filtered = @($filtered | Where-Object { $_.LocalPort -eq $QueryLocalPort }) } if ($QueryRemoteAddress) { $filtered = @($filtered | Where-Object { $_.RemoteAddress -like $QueryRemoteAddress }) } if ($QueryRemotePort -gt 0) { $filtered = @($filtered | Where-Object { $_.RemotePort -eq $QueryRemotePort }) } if ($QueryProcessName) { $filtered = @($filtered | Where-Object { $_.ProcessName -like $QueryProcessName }) } $filtered } } process { # In -Continuous mode, just collect computer names for the end {} block if ($Continuous) { foreach ($c in $ComputerName) { $continuousComputers.Add($c) } return } foreach ($targetComputer in $ComputerName) { try { $isLocal = $localNames -contains $targetComputer $timestamp = Get-Date -Format 'o' Write-Verbose "[$($MyInvocation.MyCommand)] Querying '$targetComputer' (local: $isLocal)" $queryArgs = @( , $Protocol $(if ($State) { , $State } else { , $null }) $(if ($PSBoundParameters.ContainsKey('LocalAddress')) { $LocalAddress } else { $null }) $(if ($PSBoundParameters.ContainsKey('LocalPort')) { $LocalPort } else { 0 }) $(if ($PSBoundParameters.ContainsKey('RemoteAddress')) { $RemoteAddress } else { $null }) $(if ($PSBoundParameters.ContainsKey('RemotePort')) { $RemotePort } else { 0 }) $(if ($PSBoundParameters.ContainsKey('ProcessName')) { $ProcessName } else { $null }) ) if ($isLocal) { $rawResults = & $queryScriptBlock @queryArgs } else { $invokeParams = @{ ComputerName = $targetComputer ScriptBlock = $queryScriptBlock ArgumentList = $queryArgs ErrorAction = 'Stop' } if ($hasCredential) { $invokeParams['Credential'] = $Credential } $rawResults = Invoke-Command @invokeParams } foreach ($entry in $rawResults) { [PSCustomObject]@{ PSTypeName = 'PSWinOps.NetworkStatistic' ComputerName = $targetComputer Protocol = $entry.Protocol LocalAddress = $entry.LocalAddress LocalPort = $entry.LocalPort RemoteAddress = $entry.RemoteAddress RemotePort = $entry.RemotePort State = $entry.State ProcessId = $entry.ProcessId ProcessName = $entry.ProcessName Timestamp = $timestamp } } } catch { Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_" } } } end { if ($Continuous) { Write-Host "Network Statistics Monitor — Refresh every ${RefreshInterval}s — Press Ctrl+C to stop" -ForegroundColor Cyan try { while ($true) { $allResults = [System.Collections.Generic.List[PSObject]]::new() foreach ($targetComputer in $continuousComputers) { try { $isLocal = $localNames -contains $targetComputer $timestamp = Get-Date -Format 'o' $queryArgs = @( , $Protocol $(if ($State) { , $State } else { , $null }) $(if ($PSBoundParameters.ContainsKey('LocalAddress')) { $LocalAddress } else { $null }) $(if ($PSBoundParameters.ContainsKey('LocalPort')) { $LocalPort } else { 0 }) $(if ($PSBoundParameters.ContainsKey('RemoteAddress')) { $RemoteAddress } else { $null }) $(if ($PSBoundParameters.ContainsKey('RemotePort')) { $RemotePort } else { 0 }) $(if ($PSBoundParameters.ContainsKey('ProcessName')) { $ProcessName } else { $null }) ) if ($isLocal) { $rawResults = & $queryScriptBlock @queryArgs } else { $invokeParams = @{ ComputerName = $targetComputer ScriptBlock = $queryScriptBlock ArgumentList = $queryArgs ErrorAction = 'Stop' } if ($hasCredential) { $invokeParams['Credential'] = $Credential } $rawResults = Invoke-Command @invokeParams } foreach ($entry in $rawResults) { $allResults.Add([PSCustomObject]@{ PSTypeName = 'PSWinOps.NetworkStatistic' ComputerName = $targetComputer Protocol = $entry.Protocol LocalAddress = $entry.LocalAddress LocalPort = $entry.LocalPort RemoteAddress = $entry.RemoteAddress RemotePort = $entry.RemotePort State = $entry.State ProcessId = $entry.ProcessId ProcessName = $entry.ProcessName Timestamp = $timestamp }) } } catch { Write-Error "[$($MyInvocation.MyCommand)] Failed on '$targetComputer': $_" } } Clear-Host $now = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $computerList = $continuousComputers -join ', ' Write-Host "=== Network Statistics on $computerList — $now — ${RefreshInterval}s refresh — Ctrl+C to stop ===" -ForegroundColor Cyan Write-Host "Total connections: $($allResults.Count)" -ForegroundColor DarkGray Write-Host '' if ($allResults.Count -gt 0) { $allResults | Sort-Object ComputerName, ProcessName, Protocol, RemoteAddress | Format-Table -AutoSize | Out-Host } else { Write-Host '(No matching connections found)' -ForegroundColor Yellow } Start-Sleep -Seconds $RefreshInterval } } catch { Write-Verbose "[$($MyInvocation.MyCommand)] Continuous monitoring interrupted: $_" } finally { Write-Host '' Write-Host 'Network Statistics Monitor stopped.' -ForegroundColor Cyan } } Write-Verbose "[$($MyInvocation.MyCommand)] Completed network statistics query" } } |