Search code examples
powershellmethodstrimwindows-server-2008-r2netstat

Getting Netstat output - code fails on Server 2008 R2


I am quite new to PowerShell, and scripting in general. I have been asked to generate a list of all listening TCP ports on a large set of servers over a period of time, returning a big csv file that can be imported and searched. Unfortunately some of them are still running Server 2008R2 (yeah, yeah I know...) so using Get-NetTCPConnection is out of the question. I pretty much have to try and run NetStat and make use of the output from that. I found a wonderful script written by Adam Bertram in 2015 called Get-LocalPort.ps1 which coverts the output to proper Powershell objects and appears to be ideal, however it doesn't run on Server 2008R2 either. It produces the error Method invocation failed because [System.Object[]] doesn't contain a method named 'Trim'. which I believe is coming from the line $Netstat = (netstat -anb | where {$_ -and ($_ -ne 'Active Connections')}).Trim() | Select-Object -Skip 1 | foreach {$_ -replace '\s{2,}','|'} I don't understand why that line works on newer versions but not on 2008R2. Can anyone help me tweak this so it runs on older versions of Powershell? Thanks very much.

The whole script is as follows:

<#
.SYNOPSIS
    This parses the native netstat.exe's output using the command line "netstat -anb" to find
    all of the network ports in use on a local machine and all associated processes and services
.NOTES
    Created on:     2/15/2015
    Created by:     Adam Bertram
    Filename:   Get-LocalPort.ps1
.EXAMPLE
    PS> Get-LocalPort.ps1
 
    This example will find all network ports in uses on the local computer with associated
    processes and services
 
.EXAMPLE
    PS> Get-LocalPort.ps1 | Where-Object {$_.ProcessOwner -eq 'svchost.exe'}
 
    This example will find all network ports in use on the local computer that were opened
    by the svchost.exe process.
 
.EXAMPLE
    PS> Get-LocalPort.ps1 | Where-Object {$_.IPVersion -eq 'IPv4'}
 
    This example will find all network ports in use on the local computer using IPv4 only.
#>
[CmdletBinding()]
param ()
 
begin {
    Set-StrictMode -Version Latest
    $ErrorActionPreference = 'Stop'
}
 
process {
    try {
        ## Capture the output of the native netstat.exe utility
        ## Remove the top row from the result and trim off any leading or trailing spaces from each line
        ## Replace all instances of more than 1 space with a pipe symbol.  This allows easier parsing of
        ## the fields
        $Netstat = (netstat -anb | where {$_ -and ($_ -ne 'Active Connections')}).Trim() | Select-Object -Skip 1 | foreach {$_ -replace '\s{2,}','|'}
 
        $i = 0
        foreach ($Line in $Netstat) { 
            ## Create the hashtable to conver to object later
            $Out = @{
                'Protocol' = ''
                'State' = ''
                'IPVersion' = ''
                'LocalAddress' = ''
                'LocalPort' = ''
                'RemoteAddress' = ''
                'RemotePort' = ''
                'ProcessOwner' = ''
                'Service' = ''
            }
            ## If the line is a port
            if ($Line -cmatch '^[A-Z]{3}\|') {
                $Cols = $Line.Split('|')
                $Out.Protocol = $Cols[0]
                ## Some ports don't have a state.  If they do, there's always 4 fields in the line
                if ($Cols.Count -eq 4) {
                    $Out.State = $Cols[3]
                }
                ## All port lines that start with a [ are IPv6
                if ($Cols[1].StartsWith('[')) {
                    $Out.IPVersion = 'IPv6'
                    $Out.LocalAddress = $Cols[1].Split(']')[0].TrimStart('[')
                    $Out.LocalPort = $Cols[1].Split(']')[1].TrimStart(':')
                    if ($Cols[2] -eq '*:*') {
                       $Out.RemoteAddress = '*'
                       $Out.RemotePort = '*'
                    } else {
                       $Out.RemoteAddress = $Cols[2].Split(']')[0].TrimStart('[')
                       $Out.RemotePort = $Cols[2].Split(']')[1].TrimStart(':')
                    }
                } else {
                    $Out.IPVersion = 'IPv4'
                    $Out.LocalAddress = $Cols[1].Split(':')[0]
                    $Out.LocalPort = $Cols[1].Split(':')[1]
                    $Out.RemoteAddress = $Cols[2].Split(':')[0]
                    $Out.RemotePort = $Cols[2].Split(':')[1]
                }
                ## Because the process owner and service are on separate lines than the port line and the number of lines between them is variable
                ## this craziness was necessary.  This line starts parsing the netstat output at the current port line and searches for all
                ## lines after that that are NOT a port line and finds the first one.  This is how many lines there are until the next port
                ## is defined.
                $LinesUntilNextPortNum = ($Netstat | Select-Object -Skip $i | Select-String -Pattern '^[A-Z]{3}\|' -NotMatch | Select-Object -First 1).LineNumber
                ## Add the current line to the number of lines until the next port definition to find the associated process owner and service name
                $NextPortLineNum = $i + $LinesUntilNextPortNum
                ## This would contain the process owner and service name
                $PortAttribs = $Netstat[($i+1)..$NextPortLineNum]
                ## The process owner is always enclosed in brackets of, if it can't find the owner, starts with 'Can'
                $Out.ProcessOwner = $PortAttribs -match '^\[.*\.exe\]|Can'
                if ($Out.ProcessOwner) {
                    ## Get rid of the brackets and pick the first index because this is an array
                    $Out.ProcessOwner = ($Out.ProcessOwner -replace '\[|\]','')[0]
                }
                ## A service is always a combination of multiple word characters at the start of the line
                if ($PortAttribs -match '^\w+$') {
                    $Out.Service = ($PortAttribs -match '^\w+$')[0]
                }
                [pscustomobject]$Out
            }
            ## Keep the counter
            $i++
        }       
    } catch {
        Write-Error "Error: $($_.Exception.Message) - Line Number: $($_.InvocationInfo.ScriptLineNumber)"
    }
}

Solution

  • Thanks AdminOfThings your first comment was on the right track. The line $Netstat = netstat -anb | where {$_ -and ($_ -ne 'Active Connections')} | foreach { $_.Trim() } | Select-Object -Skip 1 | foreach {$_ -replace '\s{2,}','|'}' works however I also had to change the line at the end from [pscustomobject]$Out to New-Object -TypeName PSObject -Property $Out because [pscustomobject] apparently was another new thing in PS 3. With these changes it works on Server 2008R2, 2012R2, and 2016.