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.
[cc lang=”Powershell”]
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:\> 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=&$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=&$getsvc $localPort
[string]$remotePort=&$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[/cc]
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.

[cc lang=DOS]
Protocol   : TCP
Localhost  : 172.16.10.122
LocalPort  : 60553
RemoteHost : 74.201.86.29
RemotePort : 443
Status     : ESTABLISHED[/cc]

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.