GetNetStat.psm1



#region Compile C# Type
Add-Type -TypeDefinition @"
using System;
using System.Net;
using System.Runtime.InteropServices;
public class NetStat
{
    [DllImport("iphlpapi.dll", SetLastError = true)]
    static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwOutBufLen, bool sort, int ipVersion, TCP_TABLE_CLASS tblClass, int reserved);
    [DllImport("iphlpapi.dll", SetLastError = true)]
    static extern uint GetExtendedUdpTable(IntPtr pUdpTable, ref int dwOutBufLen, bool sort, int ipVersion, UDP_TABLE_CLASS tblClass, int reserved);
    [StructLayout(LayoutKind.Sequential)]
    public struct MIB_TCPROW_OWNER_PID
    {
        public uint dwState;
        public uint dwLocalAddr;
        public uint dwLocalPort;
        public uint dwRemoteAddr;
        public uint dwRemotePort;
        public uint dwOwningPid;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct MIB_UDPROW_OWNER_PID
    {
        public uint dwLocalAddr;
        public uint dwLocalPort;
        public uint dwOwningPid;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct MIB_TCPTABLE_OWNER_PID
    {
        public uint dwNumEntries;
        MIB_TCPROW_OWNER_PID table;
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct MIB_UDPTABLE_OWNER_PID
    {
        public uint dwNumEntries;
        MIB_UDPROW_OWNER_PID table;
    }
    enum TCP_TABLE_CLASS
    {
        TCP_TABLE_BASIC_LISTENER,
        TCP_TABLE_BASIC_CONNECTIONS,
        TCP_TABLE_BASIC_ALL,
        TCP_TABLE_OWNER_PID_LISTENER,
        TCP_TABLE_OWNER_PID_CONNECTIONS,
        TCP_TABLE_OWNER_PID_ALL,
        TCP_TABLE_OWNER_MODULE_LISTENER,
        TCP_TABLE_OWNER_MODULE_CONNECTIONS,
        TCP_TABLE_OWNER_MODULE_ALL
    }
    enum UDP_TABLE_CLASS
    {
        UDP_TABLE_BASIC,
        UDP_TABLE_OWNER_PID,
        UDP_OWNER_MODULE
    }
    public enum State
    {
        Closed,
        Listening,
        SynSent,
        SynReceived,
        Established,
        Finished1,
        Finished2,
        CloseWait,
        Closing,
        LastAcknowledge,
        TimeWait,
        DeleteTcb,
        Unknown
    }
    public static Connection[] GetTCP()
    {
        MIB_TCPROW_OWNER_PID[] tTable;
        int AF_INET = 2;
        int buffSize = 0;
        uint ret = GetExtendedTcpTable(IntPtr.Zero, ref buffSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
        IntPtr buffTable = Marshal.AllocHGlobal(buffSize);
        try
        {
            ret = GetExtendedTcpTable(buffTable, ref buffSize, true, AF_INET, TCP_TABLE_CLASS.TCP_TABLE_OWNER_PID_ALL, 0);
            if (ret != 0)
            {
                Connection[] con = new Connection[0];
                return con;
            }
            MIB_TCPTABLE_OWNER_PID tab = (MIB_TCPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_TCPTABLE_OWNER_PID));
            IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
            tTable = new MIB_TCPROW_OWNER_PID[tab.dwNumEntries];
            for (int i = 0; i < tab.dwNumEntries; i++)
            {
                MIB_TCPROW_OWNER_PID tcpRow = (MIB_TCPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_TCPROW_OWNER_PID));
                tTable[i] = tcpRow;
                rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(tcpRow)); // next entry
            }
        }
        finally
        { Marshal.FreeHGlobal(buffTable); }
        Connection[] cons = new Connection[tTable.Length];
        for (int i = 0; i < tTable.Length; i++)
        {
            IPAddress localip = new IPAddress(BitConverter.GetBytes(tTable[i].dwLocalAddr));
            IPAddress remoteip = new IPAddress(BitConverter.GetBytes(tTable[i].dwRemoteAddr));
            byte[] barray = BitConverter.GetBytes(tTable[i].dwLocalPort);
            int localport = (barray[0] * 256) + barray[1];
            barray = BitConverter.GetBytes(tTable[i].dwRemotePort);
            int remoteport = (barray[0] * 256) + barray[1];
            State state;
            switch (tTable[i].dwState)
            {
                case 1:
                    state = State.Closed;
                    break;
                case 2:
                    state = State.Listening;
                    break;
                case 3:
                    state = State.SynSent;
                    break;
                case 4:
                    state = State.SynReceived;
                    break;
                case 5:
                    state = State.Established;
                    break;
                case 6:
                    state = State.Finished1;
                    break;
                case 7:
                    state = State.Finished2;
                    break;
                case 8:
                    state = State.CloseWait;
                    break;
                case 9:
                    state = State.Closing;
                    break;
                case 10:
                    state = State.LastAcknowledge;
                    break;
                case 11:
                    state = State.TimeWait;
                    break;
                case 12:
                    state = State.DeleteTcb;
                    break;
                default:
                    state = State.Unknown;
                    break;
            }
            Connection tmp = new Connection(localip, localport, remoteip, remoteport, (int)tTable[i].dwOwningPid, state);
            cons[i] = (tmp);
        }
        return cons;
    }
    public static Connection[] GetUDP()
    {
        MIB_UDPROW_OWNER_PID[] tTable;
        int AF_INET = 2; // IP_v4
        int buffSize = 0;
        uint ret = GetExtendedUdpTable(IntPtr.Zero, ref buffSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
        IntPtr buffTable = Marshal.AllocHGlobal(buffSize);
        try
        {
            ret = GetExtendedUdpTable(buffTable, ref buffSize, true, AF_INET, UDP_TABLE_CLASS.UDP_TABLE_OWNER_PID, 0);
            if (ret != 0)
            {//none found
                Connection[] con = new Connection[0];
                return con;
            }
            MIB_UDPTABLE_OWNER_PID tab = (MIB_UDPTABLE_OWNER_PID)Marshal.PtrToStructure(buffTable, typeof(MIB_UDPTABLE_OWNER_PID));
            IntPtr rowPtr = (IntPtr)((long)buffTable + Marshal.SizeOf(tab.dwNumEntries));
            tTable = new MIB_UDPROW_OWNER_PID[tab.dwNumEntries];
 
            for (int i = 0; i < tab.dwNumEntries; i++)
            {
                MIB_UDPROW_OWNER_PID udprow = (MIB_UDPROW_OWNER_PID)Marshal.PtrToStructure(rowPtr, typeof(MIB_UDPROW_OWNER_PID));
                tTable[i] = udprow;
                rowPtr = (IntPtr)((long)rowPtr + Marshal.SizeOf(udprow));
            }
        }
        finally
        { Marshal.FreeHGlobal(buffTable); }
        Connection[] cons = new Connection[tTable.Length];
        for (int i = 0; i < tTable.Length; i++)
        {
            IPAddress localip = new IPAddress(BitConverter.GetBytes(tTable[i].dwLocalAddr));
            byte[] barray = BitConverter.GetBytes(tTable[i].dwLocalPort);
            int localport = (barray[0] * 256) + barray[1];
            Connection tmp = new Connection(localip, localport, (int)tTable[i].dwOwningPid);
            cons[i] = tmp;
        }
        return cons;
    }
}
public class Connection
{
    private IPAddress _localip, _remoteip;
    private int _localport, _remoteport, _pid;
    private NetStat.State _state;
    private string _proto;
    public Connection(IPAddress Local, int LocalPort, IPAddress Remote, int RemotePort, int PID, NetStat.State State)
    {
        _proto = "TCP";
        _localip = Local;
        _remoteip = Remote;
        _localport = LocalPort;
        _remoteport = RemotePort;
        _pid = PID;
        _state = State;
    }
    public Connection(IPAddress Local, int LocalPort, int PID)
    {
        _proto = "UDP";
        _localip = Local;
        _localport = LocalPort;
        _pid = PID;
    }
    public IPAddress LocalIP { get { return _localip; } }
    public IPAddress RemoteIP { get { return _remoteip; } }
    public int LocalPort { get { return _localport; } }
    public int RemotePort { get { return _remoteport; } }
    public int PID { get { return _pid; } }
    public NetStat.State State { get { return _state; } }
    public string Protocol { get { return _proto; } }
    public string PIDName { get { return (System.Diagnostics.Process.GetProcessById(_pid)).ProcessName; } }
}
"@

#endregion

#region Cmdlet Definitions
function Resolve-HostNameProperty
{
    <#
            .SYNOPSIS
            Batch-resolves IP addresses to host names
 
            .DESCRIPTION
            Takes *any* object and resolves IP addresses in *any* of its properties
 
            .PARAMETER Property
            List of properties to resolve. Can be one property name or a comma-separated list
 
            .PARAMETER ThrottleLimit
            Number of parallel threads. Defaults to 80.
 
            .PARAMETER InputObject
            The object with the properties to resolve
 
            .PARAMETER PassThru
            When specified, no resolution takes place, and the objects are passed through unchanged
            This can be useful if you want to use this command inside a pipeline and resolve based on
            some user-submitted parameter.
 
            .EXAMPLE
            1..255 | ForEach-Object { [PSCustomObject]@{IP1 = "192.168.2.$_"; IP2="40.112.72.$_"}} | Resolve-HostNameProperty -Property IP1, IP2
            Creates dummy objects with properties IP1 and IP2 containing dummy IP addresses.
            Next, properties IP1 and IP2 of all of these objects are resolved.
            This typically would take many minutes. Thanks to caching and multithreading, it takes only a few seconds.
 
            .LINK
            https://github.com/TobiasPSP/GetNetStat
    #>



    param
    (
        [Parameter(Mandatory)]
        [string[]]
        $Property,
        
        [int]
        $ThrottleLimit = 80,
        
        [Parameter(Mandatory,ValueFromPipeline)]
        [Object[]]
        $InputObject,
        
        [switch]
        $PassThru
    )

    begin
    {
        # if the user specified -PassThru then do nothing special, pass incoming objects on
        if ($PassThru.IsPresent -eq $false)
        {
            # else, set up multithreading with a runspace pool for queueing:
            $state = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
            $pool = [RunspaceFactory]::CreateRunspacePool(1, $ThrottleLimit, $state, $Host)
            $pool.Open()
            
            # thread-safe dictionary to cache resolved IP addresses and not resolve repeating
            # IP addresses again:
            $cache = [System.Collections.Concurrent.ConcurrentDictionary[string,string]]::new()
            
            # list of background threads:
            $threads = [System.Collections.ArrayList]::new()
        
            # code to be executed inside a background thread:
            $ScriptBlock = {
                # takes object, list of properties to resolve, and reference to
                # DNS cache dictionary:
                param($object, $property, $lookup)
            
                # check each property:
                foreach($name in $property)
                {
                    # read property content as string:
                    $value = $object.$name.ToString()
                    
                    # if it is not empty...
                    if ([string]::IsNullOrEmpty($value) -eq $false)
                    {
                        # if the content is new...
                        if ($lookup.ContainsKey($value) -eq $false)
                        {
                            # ...try and resolve the new content first:
                            $lookup[$value] = try { [System.Net.Dns]::GetHostEntry($value).HostName } catch { $value }
                        }
                        # if the content can be resolved...
                        if ($value -ne $lookup[$value])
                        {
                            # "overwrite" the original property by a NoteProperty. This way, even objects with
                            # read-only properties can be updated without having to clone the object or change
                            # its type:
                            $object | Add-Member -MemberType NoteProperty -Name $name -Value $lookup[$value] -Force
                        }
                    }
                }
            
                # return object with attached note properties for every resolved property:
                $object
            }
        
            # function checks threads in the background to see if there are any
            # pending, and to see if there are results reads to get:
            function Get-Results 
            {
                # when -Wait is specified, waits for the last thread to finish before it returns:
                param([switch]$wait)
                
                # when -Wait is specified, iterate until all threads are finished and no more
                # data needs to be retrieved. Without -Wait, the loop runs only once:
                do {
                    $hasdata = $false
                    # check each background thread:
                    foreach($thread in $threads) {
                        # for any thread that has completed...
                        if ($thread.Handle -ne $null -and $thread.Handle.isCompleted) 
                        {
                            # read the results the thread produced:
                            $thread.powershell.EndInvoke($thread.Handle)
                            # remove the thread from memory:
                            $thread.powershell.dispose()
                            # blank the thread in the $threads list:
                            $thread.Handle = $null
                            $thread.powershell = $null
                        } 
                        elseif ($thread.Handle -ne $null) 
                        {
                            # if there are any threads that haven't completed,
                            # set a flag indicating that we need to check again later:
                            $hasdata = $true
                        }
                    } 
                    # if there is going to be a reiteration, wait 100ms before checking again:
                    if ($hasdata -and $wait) {Start-Sleep -Milliseconds 100}
                    # repeat if -Wait was specified and there are still threads pending:
                } while ($hasdata -and $wait)
            }
        }
    }
    
    process
    {
        # take any number of objects via the pipeline OR the parameter -InputObject:
        foreach($object in $InputObject)
        {
            # if user specified -PassThru, simply pass the object through:
            if ($PassThru.IsPresent)
            {
                $object
            }
            # else, do the object manipulation (resolving) in a background thread:
            else
            {
                # create a new background thread, add the prepared scriptblock, and submit the
                # arguments to it:
                $p = [PowerShell]::Create().AddScript($ScriptBlock).AddArgument($_).AddArgument($Property).AddArgument($cache)
                # attach the runspace pool to it so the background thread won't run by itself but instead is queued
                # and uses a thread from the pool one one becomes available:
                $p.RunspacePool = $pool
                # remember the background thread so we can check its state later:
                $rv = [PSCustomObject]@{
                    PowerShell = $p
                    Handle = $p.BeginInvoke()
                }
                # save the thread info in the threads list:
                [void]$threads.Add($rv) 
                
                # go check whether there are results reads from any of the previously
                # launched threads by chance:
                Get-Results
            }
        }
    }
    
    end 
    {
        # if -PassThru was NOT specified...
        if ($PassThru.IsPresent -eq $false)
        {
            # ...check a last time for pending background jobs, and this time *wait*
            # until all background threads have been completed:
            Get-Results -Wait
            # close the pool:
            $pool.Close()
        }
    }
}

function Get-NetStat
{
    <#
            .SYNOPSIS
            Implements part of the functionality found in netstat.exe on Windows
            based on .NET Core so it runs cross-platform
 
            .DESCRIPTION
            returns list of connections and ports
 
            .PARAMETER LocalPort
            Lists all connections with the specified local port
 
            .PARAMETER RemotePort
            Lists all connections with the specified remote port
 
            .PARAMETER State
            Describe parameter -State.
 
            .PARAMETER PidName
            Lists all connections with the specified state
 
            .PARAMETER ProcessId
            Lists all connections with the specified process id
 
            .PARAMETER TCP
            Limit to TCP connections only
 
            .PARAMETER UDP
            Limit to UDP connections only
 
            .PARAMETER Resolve
            Resolve ip addresses to host names
 
            .PARAMETER IncludeOrigin
            Reports remote ip address owner information
 
            .EXAMPLE
            Get-NetStat
            Lists all connections and ports
 
            .EXAMPLE
            Get-NetStat -Resolve -IncludeOrigin
            Lists all connections and ports, resolves IP addresses, and includes owner information for remote ip addresses.
 
            .EXAMPLE
            Get-NetStat -TCP
            Lists TCP connections and ports
 
            .EXAMPLE
            Get-NetStat -LocalPort 5985
            Checks local port 5985 (PowerShell Remoting) to see whether it is accepting incoming requests
 
            .EXAMPLE
            (Get-NetStat -LocalPort 5985).State -eq [NetStat+State]::Listening
            Check whether PowerShell Remoting port 5985 is in state "Listening"
 
            .EXAMPLE
            Get-NetStat -State Listening
            List all ports in state "Listening"
 
            .EXAMPLE
            Get-NetStat -State Established -Resolve
            List all established connections, and resolve local and remote IP addresses to host names
 
            .EXAMPLE
            Get-NetStat -PidName chrome -Resolve
            List all connections used by the "chrome" browser, and resolve IP addresses
 
            .NOTES
            When -Resolve is specified, both LocalIP and RemoteIP is resolved. DNS resolution typically is slow
            (especially for non-responding systems). That's why multithreading is used with support for up to
            80 parallel name resolutions.
 
            .LINK
            https://github.com/TobiasPSP/GetNetStat
    #>



    [CmdletBinding(DefaultParameterSetName='TCP')]
    param
    (
        [UInt16]
        $LocalPort,
        
        [UInt16]
        $RemotePort,
        
        [NetStat+State]
        $State,

        [string]
        $PidName,

        [int]
        [Alias('Pid')]
        $ProcessId,
    
        [Parameter(ParameterSetName='TCP')]
        [switch]
        $TCP,
        
        [Parameter(ParameterSetName='UDP')]
        [switch]
        $UDP,
        
        [switch]
        $Resolve,
        
        [switch]
        $IncludeOrigin
    )
    
    # there are TWO sources for information: GetTCP() and GetUDP().
    # I'd like to output either both or just one of them, based on user parameters
    # a great trick to do so is to place the calls into a scriptblock and
    # call it with "&".
    & {
        if (!$UDP) { [Netstat]::GetTCP() }
        if (!$TCP) { [Netstat]::GetUDP() }
    } | 
    # for convenience, a function should always include the most commonly used
    # where-object filters so the user doesn't have to add code
    # as a best practice, the filter parameters should be named like the
    # properties that they filter.
    # the filter will be active ONLY when the user specified the appropriate
    # filter parameter:
    Where-Object { (!$PSBoundParameters.ContainsKey('LocalPort')) -or ($_.LocalPort -eq $LocalPort)} |
    Where-Object { (!$PSBoundParameters.ContainsKey('RemotePort')) -or ($_.RemotePort -eq $RemotePort)} |
    Where-Object { (!$PSBoundParameters.ContainsKey('State')) -or ($_.State -eq $State)} |
    Where-Object { (!$PSBoundParameters.ContainsKey('PidName')) -or ($_.PidName -like $PidName)} |
    Where-Object { (!$PSBoundParameters.ContainsKey('ProcessId')) -or ($_.Pid -eq $ProcessId)} |
    # if -IncludeOrigin is specified, query the origin information by a webservice
    ForEach-Object {
      $origin = ''
      if ($IncludeOrigin)
      {
        $_.PSTypeNames.Add('connectionOrigin')
        $null = $_.PSTypeNames.Remove('Connection')
        try
        {
          $ip = $_.RemoteIp
          if ([string]::IsNullOrEmpty($ip) -eq $false -and ($ip -ne '0.0.0.0') -and ([System.Net.IPAddress]::IsLoopback($ip) -eq $false))
          {
            $info = Invoke-RestMethod -Uri "http://ipinfo.io/$ip/json" -UseBasicParsing -ErrorAction Ignore
            $origin = $info.org.Trim()
          }
        } catch {}
      } 
      $_ | Add-Member -MemberType NoteProperty -Name Origin -Value $origin -PassThru
    } |
    # DNS Resolution is expensive and slow, so it is done only on special request
    # Furthermore, it is NOT included in this function. Rather, to resolve IP addresses,
    # a highly optimized and reusable Resolve-HostNameProperty command is used:
    Resolve-HostNameProperty -Property RemoteIp, LocalIp -PassThru:$(!$Resolve.IsPresent)
    # this command takes ANY object and tries to resolve ANY property.
    # In this specific case, the properties RemoteIp and LocalIp will be resolved
    # The command uses multithreading to resolve up to 80 host names in parallel
}
#endregion