Get Your TCP Ports Here!

Once again, the PowerShell forum at ScriptingAnswers.com has undone me. I was answering a question about running Netstat in a PowerShell session on a remote computer which got me thinking about a PowerShell function to turn NETSTAT.EXE output into objects. Once you have an object then you can do all sorts of things.  Needless to say I got hooked on working something up instead of working on what I had planned for the day. The good news is that you get a tool and there’s still a few hours of the day left for me to get something else accomplished. I’m sure there are plenty of variations on this topic already out there, but here’s my contribution: Get-TCP.

Function Get-TCP {
<#
.Synopsis
Get TCP Netstat information
.Description
This function calls the command line NETSTAT.EXE tool and returns an object representation
of the TCP data.

Protocol   : TCP
Localhost  : 172.16.10.127
LocalPort  : 49259
RemoteHost : 74.201.86.29
RemotePort : https
Status     : ESTABLISHED

The default is the local computer without name resolution. However you can specify a remote
computername, assuming the remote computer is running PowerShell 2.0 and has remoting enabled.
Use -ResolveHost to resolve IP addresses to host names. This is a little slower.

This function will only return IPv4 hosts and addresses
.Parameter Computername
The name of the computer to query. The default is the localhost.
.Parameter ResolveHost
Resolve IP addresses to host names. This is a little slower. The is the equivalent of running
Netstat.exe without any parameters.
.Parameter ResolvePort
Resolve the service name associated with the port. This requires access to the legacy
Services file found at $env:windir\system32\drivers\etc\services.
.Parameter IncludeRaw
Include the raw netstat data.
.Example
PS C:\> get-tcp Return TCP Netstat information for the local computer. .Example PS C:\&gt; get-tcp "Server1","Server2" -resolve

Return TCP Netstat information for computers Server1 and Server2 with resolved IP addresses.
.Example
PS C:\> get-content computers.txt } get-tcp -resolveHost | where {$_.RemotePort -eq 80} | format-table -autosize

Get HTTP connections for every computer in computers.txt and present as a formatted table.
.Example
PS C:\> get-tcp | sort RemotePort | Select RemotePort -unique

Get a sorted list of all remote connections by port.
.Inputs
Strings
.Outputs
Custom object
.Link
http://jdhitsolutions.com/blog

.Link
Invoke-Command

.Notes
NAME:      Get-TCP
VERSION:   1.5
AUTHOR:    Jeffery Hicks
LASTEDIT:  July 29, 2010

Learn more with a copy of Windows PowerShell 2.0: TFM (SAPIEN Press 2010)

#>


[cmdletbinding()]

Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[string[]]$Computername=$env:computername,
[switch]$ResolveHost,
[switch]$ResolvePort,
[switch]$IncludeRaw
)

Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"
if ($ResolveHost) { Write-Verbose "Resolving host names"}
if ($ResolvePort) {Write-Verbose "Resolving port names"}
#put everything into a script block so that if the computer
#is remote it can be executed using Invoke-Command
$scriptblock={netstat.exe -n | where {$_.Contains("TCP") -AND $_ -notmatch "\["}}

Write-Verbose "Caching Services information"
$file="$env:windir\system32\drivers\etc\services"
#get just tcp services
$services=Get-Content -Path $file | Select-String -Pattern "/tcp"

Write-Verbose $($scriptblock.ToString())

#define the name resolution function
Function Resolve-DNS  {

[cmdletbinding()]

Param(
[Parameter(Position=0,Mandatory=$True,ValueFromPipeline=$True,
HelpMessage="Enter an IP Address to resolve")]
[string[]]$Address
)
Begin {
Write-Verbose "Starting Resolve-DNS"
#turn off error pipeline
$errorActionPreference="SilentlyContinue"
}
Process {
foreach ($IP in $Address) {
Write-Verbose "Resolving $IP"
$dns=[System.net.DNS]::GetHostByAddress("$IP")
if (-not $dns) {
Write-Verbose "no record found for $IP"
$h=$IP
$a=$null
}
else {
$h=$dns.hostname
$a=$dns.AddressList
}
New-Object -TypeName PSobject -Property @{
Hostname=$h
Addresses=$a
}
} #foreach
} #process

End {
#turn On error pipeline
$errorActionPreference="Continue"
}
} #end function

} #begin

Process {
foreach ($computer in $computername) {
Write-Verbose "Getting raw NETSTAT data from $($computer.toUpper())"
if ($computer -eq $env:computername) {
#just run the script block if local computer
$data=&amp;$scriptblock
}
else {
Try {
$data=Invoke-Command -ScriptBlock $scriptblock -computername $computer -errorAction "Stop"
}
Catch {
Write-Warning "Failed to run command on $computer"
Write-Warning $error[0].exception.message
}
} #else
#process data
Write-Verbose "Returned $($data.count) items"
Write-Verbose "Parsing data"
#initialize a hash table for IP names
$hash=@{}
foreach ($tcp in $data) {
Write-Verbose $tcp.trim()
#split the line removing empty spaces
$arr=$tcp.trim().split() | where {$_}
$localData=$arr[1].Split(":")
$remoteData=$arr[2].Split(":")
$protocol=$arr[0]
$raw=$tcp.trim()
$local=$localData[0]
[int]$localPort=$localData[1]
$remote=$remoteData[0]
[int]$remotePort=$remoteData[1]
$status=$arr[3]

#if resolve host requested build a dictionary or
#ip addresses so we don't need to resolve one already resolved
#and change the property value accordingly
if ($resolveHost) {
if ($hash.contains("$local")) {
$local=$hash.Item("$local")
}
else {
#look up name
Write-Verbose "Resolving $local"
$localLookup=Resolve-DNS "$local"
#add to hash
$hash.Add("$local",$localLookup.Hostname)
#update property
$local=$localLookup.Hostname
}
#do the same for remote
if ($hash.contains("$remote")) {
$remote=$hash.Item("$remote")
}
else {
#look up name
Write-Verbose "Resolving $remote"
$remoteLookup=Resolve-DNS "$remote"
#add to hash
$hash.Add("$remote",$localLookup.Hostname)
#update property
$remote=$RemoteLookup.Hostname
}
} #if $resolveHost

#resolve ports if specified
if ($resolvePort) {
#this is a mini scriptblock to return the service name
#in lieu of a full-blown function
$getsvc={Param ([string]$port)
Write-Verbose "searching for $port"
$service=$services | Select-String -pattern  "\s$port/tcp"

if ($service) {
$data=$service.ToString().Trim().split() | where {$_}
Write-Output $data[0]
}
else {
Write-Output $port
}

} #end $getSvc

[string]$localPort=&amp;$getsvc $localPort
[string]$remotePort=&amp;$getsvc $remotePort

} #end resolveport

#create a new object and pipe it to Select-Object so that
#the properties are in a nice order
$obj=New-Object -TypeName "PSObject" -Property @{
Protocol=$protocol
Localhost=$local
LocalPort=$localPort
RemoteHost=$remote
RemotePort=$remotePort
Status=$status
}
if ($IncludeRaw) {
$obj | Add-Member -MemberType "Noteproperty" -Name "Raw" -Value $raw -PassThru |
Select-Object -Property Protocol,LocalHost,LocalPort,RemoteHost,RemotePort,Status,Raw
}
else {
$obj | Select-Object -Property Protocol,LocalHost,LocalPort,RemoteHost,RemotePort,Status
}
} #foreach $tcp
} #foreach computer
} #Process

End {
Write-Verbose "Ending $($myinvocation.mycommand)"
}

} #end function

This is an advanced Windows PowerShell 2.0 function. By default it converts output from Netstat -n to object. I’ve parsed the output so you get an object like this for each line.

Protocol   : TCP
Localhost  : 172.16.10.122
LocalPort  : 60553
RemoteHost : 74.201.86.29
RemotePort : 443
Status     : ESTABLISHED

When you run Netstat without any parameters it resolves both host and port names. I’ve separated the two so you can use either -ResolveHost and/or -ResolvePort. The former uses an embedded function to get the host entry name by IP address. If not found, then the IP address is used. This function by the way only returns IPv4 information. Service ports are resolved by finding the associated TCP port in the legacy Services file. What you end up with is something like this:

The function can also accept a remote computername as it uses Invoke-Command to run the Netstat command remotely.  All the results are then processed locally. You can pipe computernames to the function or it accepts arrays.

If you have any questions on the nitty-gritty details, please post a comment.

Download Get-TCP.ps1.

Post to Twitter Post to Plurk Post to Yahoo Buzz Post to Delicious Post to Digg Post to Facebook Post to FriendFeed Post to Google Buzz Post to Ping.fm Post to Reddit Post to Slashdot Post to StumbleUpon Post to Technorati

This entry was posted in CommandLine, PowerShell v2.0, Windows 7, Windows Server and tagged , , , , . Bookmark the permalink.

2 Responses to Get Your TCP Ports Here!

  1. Jeffery Hicks says:

    Formatting the code in the blog entry is a little wonky. The file you download and run is properly formatted.

  2. Jeffery Hicks says:

    You can tell I’m not a developer. I didn’t even think about the .NET classes which will also return this type of information.

    $properties=[System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties()
    $udp=$properties.GetActiveUdpListeners()
    $tcp=$properties.GetActiveTcpConnections()

    Thanks to JVierra for pointing this out.