Get-NetConnection.ps1


<#PSScriptInfo
 
.VERSION 1.0
.GUID b727a4e5-4484-444f-a6ba-ab0ba244d9ec
.AUTHOR Mark Holderness
.TAGS NetStat Get-NetTCPConnection Get-UDPEndPoint Get-FirewallLog
.PROJECTURI https://github.com/mholderness/Get-NetConnection
 
#>


<#
 
.DESCRIPTION
 PowerShell equivalent(ish) of NetStat via Invoke-Command
 Collects TCP Connections and UDP Endpoints via Get-NetTCPConnection and Get-NETUDPEndpoint respectively; wrapped within Invoke-Command.
 Links the OwningProcess to Services and/or Processes with the GetServiceDetails and/or GetProcessDetails parameters.
 The Windows Firewall log can be parsed via the GetFirewallLog parameter.
 Output can be focused by specifying various other parameters (such as Listen, IPv4, IPv6, TCP and UDP). See Get-Help Detailed or Full for more information.
 
.SYNOPSIS
 PowerShell equivalent(ish) of NetStat via Invoke-Command
.EXAMPLE
 .\Get-NetConnection.ps1 -Listen -GetServiceDetails -WriteProgress -Verbose | Select AddressFamily, Protocol, LocalAddress, LocalPort, ServiceDisplayName | Format-Table -AutoSize -Wrap
.EXAMPLE
 .\Get-NetConnection.ps1 'ComputerA','ComputerB' -Listen -IPv4 -GetServiceDetails | Select * -ExcludeProperty Service, TcpConnection | Out-GridView
.EXAMPLE
 'ComputerA','ComputerB' | .\Get-NetConnection.ps1 -Listen -Port 445 -GetFirewallLog -GetServiceDetails | Select * -ExcludeProperty Service, TcpConnection | Out-GridView
.EXAMPLE
 Get-ADComputer -Filter {name -like 'emea-dc1-fs*'} | .\Get-NetConnection.ps1 -Port 3389 -GetServiceDetails -WriteProgress | Tee-Object -Variable RDPNetStat | Select * -ExcludeProperty FirewallLog, Process, ParentProcess, Service, TcpConnection | Out-GridView
 
#>

[cmdletbinding()]
Param(
    [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)][Alias("CN","MachineName","DNSHostName")]
    $ComputerName = $Env:Computername,
    <#
        Specifies the properties to return from Get-NetTCPConnection and Get-NETUDPEndpoint.
        Portions of the script require these properties (State is required to link established TCP Connections to Listening ports).
        Parameters within the script may rely on some of these properties. For example:
            -Listen requires State
            -IPv4 and -IPv6 require LocalAddress to calculate the AddressFamily
            -GetServiceDetails and -GetProcessDetails require OwningProcess to match against Win32_Service and Win32_Process
    #>

    [string[]]$NetProperties = @("CreationTime","LocalAddress","LocalPort","OwningProcess","State"),
    <#
        Return only TCP connections in a Listen state (and UDP endpoints). By default, all TCP connections and UDP endpoints are returned.
    #>

    [switch]$Listen,
    <#
        Return connections where the Local Address is part of the IPv4 Address Family.
        If the IPv4 parameter is present and the IPv6 parameter is not, only IPv4 connections are returned.
        If both (or neither) of the IPv4 and IPv6 parameters are present, all connections are returned.
    #>

    [switch]$IPv4,
    <#
        Return connections where the Local Address is part of the IPv6 Address Family.
        If the IPv6 parameter is present and the IPv4 parameter is not, only IPv6 connections are returned.
        If both (or neither) of the IPv4 and IPv6 parameters are present, all connections are returned.
    #>

    [switch]$IPv6,
    <#
        Return TCP connections.
        If the TCP parameter is present and the UDP parameter is not, only TCP connections are returned.
        If both (or neither) of the TCP and UDP parameters are present, all TCP connections and UDP endpoints are returned.
    #>

    [switch]$TCP,
    <#
        Return UDP endpoints.
        If the UDP parameter is present and the TCP parameter is not, only UDP endpoints are returned.
        If both (or neither) of the TCP and UDP parameters are present, all TCP connections and UDP endpoints are returned.
    #>

    [switch]$UDP,
    <#
        Only return results where the LocalPort or RemotePort -eq Port
    #>

    [ValidateRange(1,65535)][uint16[]]$Port,
    <#
        Specifies whether to link a TCP connection or UDP endpoint to Win32_Service by the OwningProcess via Get-CimInstance (if possible, not all Processes are Services).
        Appends the ServiceDisplayName and Service properties to each connection in the output.
    #>

    [switch]$GetServiceDetails,
    [string[]]$ServiceProperties = @("Name","DisplayName","StartName","PathName","Description"),
    <#
        Specifies whether to link a TCP connection or UDP endpoint to Win32_Process by the OwningProcess via Get-CimInstance and Invoke-CimMethod (to inititiate GetOwner).
        Appends the ProcessName, Process, ParentProcessName and ParentProcess properties to each connection in the output.
    #>

    [switch]$GetProcessDetails,
    [string[]]$ProcessProperties = @("Name","Owner","CreationDate","ExecutablePath","CommandLine","Handles","ParentProcessID"),
    <#
        Specifies whether to retrieve Windows Defender firewall logs; making use of Get-NetFirewallSetting, Get-NetFirewallProfile and Get-Content.
        Appends the FirewallLog, FirewallLogAllowCount, FirewallLogAllowRemoteAddress, FirewallLogDropCount, FirewallLogDropRemoteAddress properties to each connection in the output.
    #>

    [switch]$GetFirewallLog,
    <#
        If Windows Defender has been configured to log the Domain, Private and Public profiles to separate files:
            Specifies whether to return logs from the Active Windows Defender firewall profile. By default, firewall logs from all profiles are returned.
    #>

    [switch]$FirewallLogActiveProfileOnly,
    <#
        Specifies the number of lines to parse from the end of each log file.
            If used with the $TailTime parameter, Tail will occur first and TailTime will filter solely on those log entries.
    #>

    [int32]$FirewallLogTail,
    <#
        Similar to Tail but specifies log entries to return based on their timestamp.
        Timestamp is calculated by converting the date and time fields to [DateTime].
        The denomination used by TailTime can be adjusted with the $TailTimeDenomination parameter. The default denomination is Minutes.
    #>

    [int32]$FirewallLogTailTime,
    <#
        The denomination referred to in FirewallLogTailTime. Possible values are Minutes, Hours, Days, Months or Years. Default value: Minutes.
    #>

    [ValidateSet("Minutes","Hours","Days","Months","Years")][String]$FirewallLogTailTimeDenomination = "Minutes",
    <#
        Specifies whether to call Write-Progress during script execution.
    #>

    [switch]$WriteProgress,
    <#
        Specifies the ID used by Write-Progress if the $WriteProgress parameter is used.
    #>

    [int32]$ProgressID = 1,
    <#
        Specifies how many machines to be queried in parallel. Defines maxRunspaces when calling [runspacefactory]::CreateRunspacePool.
    #>

    [int32]$Throttle = 5,
    <#
        Specifies whether to call [System.GC]::GetTotalMemory('forceFullCollection').
    #>

    [bool]$ForceGarbageCollection = $True
)
Begin{
    
    $GetNetConnectionScriptBlock = {
        Param (
            $Computer,
            $GetNetConnectionParameters
        )
        $VerbosePreference = $GetNetConnectionParameters.VerbosePreference
        $WriteProgress = $GetNetConnectionParameters.WriteProgress
        Write-Verbose "$(Get-Date) : $Computer : Invoked GetNetConnectionScriptBlock."
        If($WriteProgress) {
            Write-Progress -Activity $Computer -Status "Invoked GetNetConnectionScriptBlock. Building Invoke-Command parameters." -ID $GetNetConnectionParameters.ProgressID -ParentID $GetNetConnectionParameters.ProgressParentID
        }
        If(Test-NetConnection $Computer -CommonTCPPort WINRM -InformationLevel 'Quiet') {
            $InvokeCommandSplat = @{}
            $InvokeCommandSplat.ComputerName = $Computer
            $InvokeCommandSplat.ScriptBlock = {
                Param (
                    $GetNetConnectionParameters
                )
                $Computer = $Env:Computername
                Function Get-SelectProperties($Object,[string[]]$PropertyOrderPreference)
                {    $SelectProperties = @()
                    $ObjectProperties = $Object | ForEach-Object {$_.PSObject.Properties.Name} | Select-Object -Unique
                    If($PropertyOrderPreference)
                    {    ForEach($Property in $PropertyOrderPreference)
                        {    If($ObjectProperties -Contains $Property)
                            {    $SelectProperties += @{ "Name" = $Property; "Expression" = [scriptblock]::Create("`$`_.`"$Property`"")}
                            }
                        }
                    }
                    ForEach($Property in $ObjectProperties)
                    {    If($SelectProperties.Name -Contains $Property)
                        {    Write-Verbose "$(Get-Date) : $Property already processed due to PropertyOrderPreference."
                        }
                        Else
                        {    #Write-Verbose "$(Get-Date) : $Property to be added to the SelectProperties object."
                            $Hash = @{}
                            $Hash.Add("Name",$Property)
                            $Hash.Add("Expression",[scriptblock]::Create("`$`_.`"$Property`""))
                            $SelectProperties += $Hash
                        }
                    }
                    $SelectProperties
                }
                Function Get-FirewallLog
                {    
                    [cmdletbinding()]
                    Param(
                        [uint16[]]$Port,
                        [switch]$ActiveProfileOnly,
                        [int32]$Tail,
                        [DateTime]$TailTimeThreshold,
                        [System.Management.Automation.ActionPreference]$VerbosePreference
                    )
                    $Computer = $Env:Computername
                    Write-Verbose "$(Get-Date) : $Computer : Invoke-Command : Get-NetConnection : Get-FirewallLog"
                    $GetNetFirewallSplat = [ordered]@{PolicyStore="ActiveStore"}
                    If($ActiveProfileOnly) {
                        #For($i=0;$i -lt 8;$i++) {"$i {@($([Microsoft.PowerShell.Cmdletization.GeneratedTypes.NetSecurity.Profile]($i)))}"}
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : ActiveProfileOnly parameter found. Get-NetFirewallSetting called to find active firewall profiles."
                        $NetFirewallSetting = Get-NetFirewallSetting @GetNetFirewallSplat
                        switch ($NetFirewallSetting.Profile) {
                            0    {$ActiveProfile = @('Any')}
                            1    {$ActiveProfile = @('Domain')}
                            2    {$ActiveProfile = @('Private')}
                            3    {$ActiveProfile = @('Domain', 'Private')}
                            4    {$ActiveProfile = @('Public')}
                            5    {$ActiveProfile = @('Domain', 'Public')}
                            6    {$ActiveProfile = @('Private', 'Public')}
                            7    {$ActiveProfile = @('Domain', 'Private', 'Public')}
                        }
                    }
                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Get-NetFirewallProfile called to find firewall profiles."
                    $NetFirewallProfile = Get-NetFirewallProfile @GetNetFirewallSplat -All
                    If($ActiveProfileOnly -And $ActiveProfile -ne 'Any') {
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Filtering NetFirewallProfile to active firewall profiles."
                        $NetFirewallProfile = $NetFirewallProfile | Where-Object {$ActiveProfile -Contains $_.Name}
                    }
                    ForEach($LogFile in ($NetFirewallProfile.LogFileName | Select-Object -Unique)) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Get-Content against $Computer for $LogFile."
                        $Path = "$([System.Environment]::ExpandEnvironmentVariables($LogFile))"
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : LogFile expanded to $Path"
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Get-Content $Path."
                        $LogContent = Get-Content $Path | Select-Object -Skip 3
                        Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : LogContent line count: $($LogContent.Count)."
                        If($LogContent.Count -gt 2) {
                            Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Retrieving Log File fields."
                            $Header = $LogContent[0].Replace("#Fields: ","").Split(" ")
                            If($Tail) {
                                Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Returning the last $Tail log entries due to -Tail."
                                $LogContentStart = $LogContent.Count-$Tail
                            }
                            Else {
                                $LogContentStart = 2
                            }
                            $LogContentEnd = $LogContent.Count-1
                            Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : ConvertFrom-Csv running against LogContent found in $LogFile."
                            $Logs = ConvertFrom-Csv -InputObject ($LogContent[$LogContentStart..$LogContentEnd]) -Delimiter " " -Header $Header
                            Remove-Variable $LogContent
                            If($Logs) {
                                $FirstLogEntryDateTime = [DateTime]"$($Logs[0].date) $($Logs[0].Time)"
                                $LastLogEntryDateTime = [DateTime]"$($Logs[-1].date) $($Logs[-1].Time)"
                                Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : $($Logs.Count) log entries found."
                                Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : First log entry: $FirstLogEntryDateTime."
                                Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Last log entry: $LastLogEntryDateTime."
                                If($TailTimeThreshold) {
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Filtering log entries based on TailTimeThreshold: $TailTimeThreshold"
                                    $Logs = ForEach($Log in $Logs) {
                                        $LogDateTime = [DateTime]"$($Log.date) $($Log.Time)"
                                        If($LogDateTime -gt $TailTimeThreshold) {
                                            Add-Member -InputObject $Log -NotePropertyName DateTime -NotePropertyValue $LogDateTime
                                            $Log
                                        }
                                    }
                                    $FirstLogEntryDateTime = [DateTime]"$($Logs[0].date) $($Logs[0].Time)"
                                    $LastLogEntryDateTime = [DateTime]"$($Logs[-1].date) $($Logs[-1].Time)"
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : $($Logs.Count) log entries found."
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : First log entry: $FirstLogEntryDateTime."
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Last log entry: $LastLogEntryDateTime."        
                                }
                                If($Port) {
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Filtering log entries based on Port: $($Port)"
                                    $Logs = ForEach($Log in $Logs) {
                                        If($Port -Contains [uint16]$_.'src-port' -Or $Port -Contains [uint16]$_.'dst-port') {
                                            $Log
                                        }
                                    }
                                    Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : $($Logs.Count) log entries found."
                                }
                                Write-Verbose "$(Get-Date) : $Computer : Get-FirewallLog : Appending custom properties to log entries."
                                $Logs | Select-Object *,
                                    @{n="LocalPort";e={    If($_.Path -eq "RECEIVE") {$_."dst-port"} ElseIf($_.Path -eq "SEND") {$_."src-port"} Else {"WTF"}}},
                                    @{n="RemotePort";e={ If($_.Path -eq "RECEIVE") {$_."src-port"} ElseIf($_.Path -eq "SEND") {$_."dst-port"} Else {"WTF"}}},
                                    @{n="LocalAddress";e={ If($_.Path -eq "RECEIVE") {$_."dst-ip"} ElseIf($_.Path -eq "SEND") {$_."src-ip"} Else {"WTF"}}},
                                    @{n="RemoteAddress";e={ If($_.Path -eq "RECEIVE") {$_."src-ip"} ElseIf($_.Path -eq "SEND") {$_."dst-ip"} Else {"WTF"}}},
                                    @{n="Ports";e={@($_."src-port",$_."dst-port")}},
                                    @{n="IPs";e={@($_."src-ip",$_."dst-ip")}},
                                    @{n="AddressFamily";e={@($_."src-ip",$_."dst-ip") | ForEach-Object {([IPAddress]$_).AddressFamily} | Select-Object -Unique | ForEach-Object {If($_ -eq "InterNetwork") {"IPv4"}ElseIf("InterNetworkV6") {"IPv6"}}}},
                                    @{n="LogSource";e={$Computer}}
                            }
                        }
                    }
                }
                Function Get-NetConnection
                {
                    [cmdletbinding()]
                    Param(
                        [string[]]$NetProperties,
                        [switch]$Listen,
                        [switch]$IPv4,
                        [switch]$IPv6,
                        [switch]$TCP,
                        [switch]$UDP,
                        [int[]]$Port,
                        [switch]$GetServiceDetails,
                        [string[]]$ServiceProperties,
                        [switch]$GetProcessDetails,
                        [string[]]$ProcessProperties,
                        [switch]$GetFirewallLog,
                        [switch]$FirewallLogActiveProfileOnly,
                        [int32]$FirewallLogTail,
                        [DateTime]$FirewallLogTailTimeThreshold,
                        [switch]$WriteProgress,
                        [int32]$ProgressID,
                        [int32]$ProgressParentID,
                        [System.Management.Automation.ActionPreference]$VerbosePreference
                    )
                    $Computer = $Env:Computername
                    Write-Verbose "$(Get-Date) : $Computer : Invoke-Command : Get-NetConnection"
                    If($WriteProgress) {
                        Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection" -ID $ProgressID -ParentID $ProgressParentID
                    }
                    Foreach($PSBoundParameter in $PSBoundParameters.GetEnumerator())
                    {    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection | PSBoundParameter: $($PSBoundParameter.Key) = $($PSBoundParameter.Value)"
                    }                
                    If($GetServiceDetails) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : GetServiceDetails"
                        If($WriteProgress) {
                            Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : GetServiceDetails" -ID $ProgressID -ParentID $ProgressParentID
                        }
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : GetServiceDetails : Get-CimInstance Win32_Service to link the owning process of connections to Process IDs of Services."
                        If($ServiceProperties -notcontains "DisplayName") {$ServiceProperties += "DisplayName"}
                        $Win32Service = @{}
                        Get-CimInstance Win32_Service -Filter 'State="Running"' | ForEach-Object {
                            $ServiceKey = $_.ProcessId
                            $ServiceValue = [ordered]@{}
                            ForEach($ServiceProperty in $ServiceProperties) {
                                If($_."$ServiceProperty") {
                                    $ServiceValue."$ServiceProperty" = $_."$ServiceProperty"
                                }
                            }
                            If($Win32Service.ContainsKey($ServiceKey)) {
                            }
                            Else {
                                $Win32Service.Add($ServiceKey,[System.Collections.ArrayList]::new())
                            }            
                            [void]$Win32Service.$ServiceKey.Add([PSCustomObject]$ServiceValue)
                        }
                    }
                    If($GetProcessDetails) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : GetProcessDetails"
                        If($WriteProgress) {
                            Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : GetProcessDetails" -ID $ProgressID -ParentID $ProgressParentID
                        }
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : GetProcessDetails : Get-CimInstance Win32_Process to link the owning process of connections to Process IDs of Processes."
                        If($ProcessProperties -notcontains "Name") {$ProcessProperties += "Name"}
                        $Win32Process = @{}
                        Get-CimInstance Win32_Process | ForEach-Object {
                            $ProcessKey = $_.ProcessId
                            $ProcessValue = [ordered]@{}
                            $ProcessValue.Name = $_.Name
                            If($ProcessProperties -Contains 'Owner') {
                                $ProcessValue.Owner = Invoke-CimMethod -InputObject $_ -MethodName GetOwner | ForEach-Object{
                                    If($_.ReturnValue -eq 0) {
                                        @($_.Domain,$_.User) -Join "\"
                                    }
                                    Else {
                                        "Error. ReturnValue: $($_.ReturnValue)."
                                    }
                                }
                            }
                            ForEach($ProcessProperty in $ProcessProperties) {
                                If($_."$ProcessProperty") {
                                    $ProcessValue."$ProcessProperty" = $_."$ProcessProperty"
                                }
                            }
                            $Win32Process.Add($ProcessKey,[PSCustomObject]$ProcessValue)
                        }
                    }
                    If($GetFirewallLog) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : GetFirewallLog"
                        If($WriteProgress) {
                            Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : GetFirewallLog" -ID $ProgressID -ParentID $ProgressParentID
                        }
                        $GetFirewallLogSplat = [ordered]@{}
                        If($Port) {
                            $GetFirewallLogSplat.Port = $Port
                        }
                        If($FirewallLogActiveProfileOnly) {
                            $GetFirewallLogSplat.ActiveProfileOnly = $True
                        }
                        If($FirewallLogTail) {
                            $GetFirewallLogSplat.Tail = $FirewallLogTail
                        }
                        If($FirewallLogTailTimeThreshold) {
                            $GetFirewallLogSplat.TailTimeThreshold = $FirewallLogTailTimeThreshold
                        }
                        $GetFirewallLogSplat.VerbosePreference=$VerbosePreference
                        Foreach($GetFirewallLogParameter in $GetFirewallLogSplat.GetEnumerator())
                        {    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection | GetFirewallLog: $($GetFirewallLogParameter.Key) = $($GetFirewallLogParameter.Value)"
                        }
                        $FirewallLog = @{}
                        ForEach($FirewallLogEntry in (Get-FirewallLog @GetFirewallLogSplat)) {
                            $FirewallLogEntryKey = "$($FirewallLogEntry.Action)$($FirewallLogEntry.AddressFamily)$($FirewallLogEntry.Protocol)$($FirewallLogEntry.LocalPort)"
                            If($FirewallLog.ContainsKey($FirewallLogEntryKey)) {
                            }
                            Else {
                                $FirewallLog.$FirewallLogEntryKey = [System.Collections.ArrayList]::new()
                            }
                            [void]$FirewallLog.$FirewallLogEntryKey.Add($FirewallLogEntry)
                        }
                    }
    
                    $NetConnections = [System.Collections.ArrayList]::new()
                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Getting TCP connections and/or UDP endpoints."
                    If($WriteProgress) {
                        Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : Getting TCP connections and/or UDP endpoints." -ID $ProgressID -ParentID $ProgressParentID
                    }
                    If($TCP -Or !$UDP) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetTCPConnection."
                        [array]$NetTCPConnection = Get-NetTCPConnection
                        If($Port) {
                            Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetTCPConnection : Filtering Get-NetTCPConnection results ($($NetTCPConnection.Count)) via Port and populating NetConnections ArrayList."
                            $NetTCPConnection | Where-Object {$Port -Contains $_.LocalPort -Or $Port -Contains $_.RemotePort} | ForEach-Object { [void]$NetConnections.Add($_) }    
                        }
                        Else {
                            Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetTCPConnection : Add Get-NetTCPConnection results ($($NetTCPConnection.Count)) to NetConnections ArrayList."
                            $NetTCPConnection | ForEach-Object { [void]$NetConnections.Add($_) }    
                        }
                    }
                    If($UDP -Or !$TCP) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetUDPEndpoint."
                        [array]$NetUDPEndpoint = Get-NetUDPEndpoint
                        If($Port) {
                            Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetUDPEndpoint : Filtering Get-NetUDPEndpoint ($($NetUDPEndpoint.Count)) via Port and populating NetConnections ArrayList."
                            $NetUDPEndpoint | Where-Object {$Port -Contains $_.LocalPort -Or $Port -Contains $_.RemotePort} | ForEach-Object { [void]$NetConnections.Add($_) }    
                        }
                        Else {
                            Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Get-NetUDPEndpoint : Add Get-NetUDPEndpoint ($($NetUDPEndpoint.Count)) results to NetConnections ArrayList."
                            $NetUDPEndpoint | ForEach-Object { [void]$NetConnections.Add($_) }    
                        }
                    }
                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : $($NetConnections.Count) connections in NetConnections ArrayList to process."
                    $NetworkConnection = ForEach($NetConnection in $NetConnections) {
                        If($WriteProgress) {
                            Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : Processing TCP connections and/or UDP endpoints." -ID $ProgressID -ParentID $ProgressParentID
                        }
                        $NetConnectionProperties = [Ordered]@{Computer=$Computer}
                        If($NetProperties -Contains "LocalAddress") {
                            $NetConnectionProperties.AddressFamily = switch (([IPAddress]$NetConnection.LocalAddress).AddressFamily) {
                                "InterNetwork"        {"IPv4"}
                                "InterNetworkV6"    {"IPv6"}
                            }
                            If($IPv4 -Or $IPv6) {
                                If($NetConnectionProperties.AddressFamily -eq "IPv4" -And !$IPv4) {
                                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : $($NetConnectionProperties.AddressFamily) excluded. Parameters: IPv4 ($IPv4) & IPv6 ($IPv6)."
                                    continue
                                }
                                If($NetConnectionProperties.AddressFamily -eq "IPv6" -And !$IPv6) {
                                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : $($NetConnectionProperties.AddressFamily) excluded. Parameters: IPv4 ($IPv4) & IPv6 ($IPv6)."
                                    continue
                                }
                            }
                        }
                        $NetConnectionProperties.Protocol = switch ($NetConnection.CimClass.CimClassName) {
                            "MSFT_NetTCPConnection"    {"TCP"}
                            "MSFT_NetUDPEndpoint"    {"UDP"}
                        }
                        ForEach($NetProperty in $NetProperties) {
                            $NetConnectionProperties."$NetProperty" = $NetConnection."$NetProperty"
                        }
                        If($GetFirewallLog) {
                            If(($NetConnectionProperties.Protocol -eq "TCP" -And $NetConnection.State -eq "Listen") -Or $NetConnectionProperties.Protocol -eq "UDP") {
                                #Attach firewall logs for TCP Connections in the Listen state and UDP endpoints
                                $NetConnectionProperties.FirewallLog = [PSCustomObject][Ordered]@{
                                    Allow=""
                                    Drop=""
                                }
                                $FirewallLogAllowKey = "ALLOW$($NetConnectionProperties.AddressFamily)$($NetConnectionProperties.Protocol)$($NetConnectionProperties.LocalPort)"
                                If($FirewallLog.ContainsKey($FirewallLogAllowKey)) {
                                    $NetConnectionProperties.FirewallLog.Allow = $FirewallLog.$FirewallLogAllowKey
                                    $NetConnectionProperties.FirewallLogAllowCount = $NetConnectionProperties.FirewallLog.Allow.Count
                                    $NetConnectionProperties.FirewallLogAllowRemoteAddress = (($NetConnectionProperties.FirewallLog.Allow).RemoteAddress | Select-Object -Unique) -Join "`r`n"
                                }
                                $FirewallLogDropKey = "DROP$($NetConnectionProperties.AddressFamily)$($NetConnectionProperties.Protocol)$($NetConnectionProperties.LocalPort)"
                                If($FirewallLog.ContainsKey($FirewallLogDropKey)) {
                                    $NetConnectionProperties.FirewallLog.Drop = $FirewallLog.$FirewallLogDropKey
                                    $NetConnectionProperties.FirewallLogDropCount = $NetConnectionProperties.FirewallLog.Drop.Count
                                    $NetConnectionProperties.FirewallLogDropRemoteAddress = (($NetConnectionProperties.FirewallLog.Drop).RemoteAddress | Select-Object -Unique) -Join "`r`n"
                                }
                            }
                        }
                        If($NetProperties -Contains "State") {
                            If($NetConnectionProperties.Protocol -eq "TCP") {
                                If($NetConnection.State -eq "Listen") {
                                    #Stage properties for TCP connections directed at listening TCP ports.
                                    $NetConnectionProperties.TcpConnection = [System.Collections.ArrayList]::new()
                                    $NetConnectionProperties.TcpConnectionSummary = ""
                                }
                                Else {
                                    #Record properties for TCP connections directed at listening TCP ports.
                                    $NetConnectionProperties.RemoteAddress = $NetConnection.RemoteAddress
                                    $NetConnectionProperties.RemotePort = $NetConnection.RemotePort
                                }
                            }
                        }
                        If($GetServiceDetails -And $NetConnection.OwningProcess) {
                            $Service = $Win32Service.($NetConnection.OwningProcess)
                            If($Service) {
                                $NetConnectionProperties.ServiceDisplayName = $Service.DisplayName -Join "`r`n"
                                $NetConnectionProperties.Service = $Service
                            }
                        }
                        If($GetProcessDetails -And $NetConnection.OwningProcess) {
                            $Process = $Win32Process.($NetConnection.OwningProcess)
                            If($Process) {
                                $NetConnectionProperties.ProcessName = $Process.Name
                                $NetConnectionProperties.Process = $Process
                                If($Process.ParentProcessId) {
                                    $ParentProcess = $Win32Process.($Process.ParentProcessId)
                                    If($ParentProcess) {
                                        $NetConnectionProperties.ParentProcessName = $ParentProcess.Name
                                        $NetConnectionProperties.ParentProcess = $ParentProcess
                                    }
                                }
                            }
                        }
                        [PSCustomObject]$NetConnectionProperties
                    }
                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : $($NetworkConnection.Count) connections processed. Linking TCP connections not in a Listen state to TCP connections in a Listen state."
                    If($NetProperties -Contains "State") {
                        If($WriteProgress) {
                            Write-Progress -Activity $Computer -Status "Invoke-Command : Get-NetConnection : Linking TCP connections to listening ports." -ID $ProgressID -ParentID $ProgressParentID
                        }
                        ForEach($NetConnection in ($NetworkConnection | Where-Object {$_.Protocol -eq "TCP" -And $_.State -eq "Listen"})) {
                            [array]$TcpConnections = $NetworkConnection | Where-Object {$_.Protocol -eq "TCP" -And $_.AddressFamily -eq $NetConnection.AddressFamily -And $_.LocalPort -eq $NetConnection.LocalPort}
                            If($TcpConnections.Count -ge 1) {
                                ForEach($TcpConnection in ($TcpConnections | Where-Object State -ne "Listen")) {
                                    [void]$NetConnection.TcpConnection.Add($TcpConnection)
                                }
                                If($NetConnection.TcpConnection.Count -ge 1) {
                                    Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : $($NetConnection.TcpConnection.Count) TCP connection(s) found that match the AddressFamily ($($NetConnection.AddressFamily)) and LocalPort $($NetConnection.LocalPort) of a listening service or process."
                                    $NetConnection.TcpConnectionSummary = ($NetConnection.TcpConnection | Group-Object RemoteAddress, State | ForEach-Object {"$($_.Name), $($_.Count)"}) -Join "`r`n"
                                }
                            }
                        }
                    }
                    If($Listen) {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Filtering $($NetworkConnection.Count) connections to only include TCP connections in a Listen state and UDP endpoints."
                        $NetworkConnection | Where-Object {($_.Protocol -eq "TCP" -And $_.State -eq "Listen") -Or $_.Protocol -eq "UDP"} | Select-Object (Get-SelectProperties $NetworkConnection)
                    }
                    Else {
                        Write-Verbose "$(Get-Date) : $Computer : Get-NetConnection : Returning $($NetworkConnection.Count) connections."
                        $NetworkConnection | Select-Object (Get-SelectProperties $NetworkConnection)
                    }
                }
    
                Get-NetConnection @using:GetNetConnectionParameters
            }
            Write-Verbose "$(Get-Date) : $Computer : Calling Invoke-Command."
            If($WriteProgress) {
                Write-Progress -Activity $Computer -Status "Calling Invoke-Command." -ID $GetNetConnectionParameters.ProgressID -ParentID $GetNetConnectionParameters.ProgressParentID
            }
            Invoke-Command @InvokeCommandSplat
            If($WriteProgress) {
                Write-Progress -Activity $Computer -ID $GetNetConnectionParameters.ProgressID -ParentID $GetNetConnectionParameters.ProgressParentID -Completed
            }

        }
        Else {
            Write-Warning "$(Get-Date) : $Computer : `'Test-NetConnection $Computer -Port 5985 -InformationLevel Quiet`' failed."
        }

    }

    $GetNetConnectionParameters = @{}
    ForEach($Parameter in @('NetProperties','Listen','IPv4','IPv6','TCP','UDP','Port','GetServiceDetails','ServiceProperties','GetProcessDetails','ProcessProperties','GetFirewallLog','FirewallLogActiveProfileOnly','FirewallLogTail','WriteProgress','VerbosePreference')) {
        $Variable = $Null; $Variable = Get-Variable $Parameter
        If($Variable) {
            $GetNetConnectionParameters."$($Variable.Name)" = $Variable.Value
            Write-Verbose "$(Get-Date) : Parameters : $($Variable.Name) = $($Variable.Value)."
        }
    }

    If($FirewallLogTailTime) {
        Write-Verbose "$(Get-Date) : FirewallLogTailTime parameter passed based on $FirewallLogTailTime $FirewallLogTailTimeDenomination. Calculating threshold."
        $Now = [datetime]::Now
        $GetNetConnectionParameters.FirewallLogTailTimeThreshold = Switch ($FirewallLogTailTimeDenomination) {
            'Minutes'    { $Now.AddMinutes(-$FirewallLogTailTime) }
            'Hours'        { $Now.AddHours(-$FirewallLogTailTime) }
            'Days'        { $Now.AddDays(-$FirewallLogTailTime) }
            'Months'    { $Now.AddMonths(-$FirewallLogTailTime) }
            'Years'        { $Now.AddYears(-$FirewallLogTailTime) }
        }
    }

    If($WriteProgress) {
        Write-Progress -Activity "$($MyInvocation.MyCommand.Name)" -ID $ProgressID
        $GetNetConnectionParameters.ProgressParentID = $ProgressID
        $GetNetConnectionParameters.ProgressID = $ProgressID+1
    }

    $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
    $RunspacePool = [runspacefactory]::CreateRunspacePool(1, $Throttle, $SessionState, $Host)
    $RunspacePool.Open()
    $RunspaceJobs = New-Object System.Collections.ArrayList

    Function Get-RunspaceResults
    {
        param(
            $RunspaceJobs,
            [switch]$Wait
        )
        Do {
            $InstanceHasData = $false
            ForEach($Instance in $RunspaceJobs) {
                If($Instance.Job.isCompleted) {
                    $Instance.PowerShell.EndInvoke($Instance.Job)
                    $Instance.powershell.Dispose()
                    $Instance.Job = $Null
                    $Instance.PowerShell = $Null
                    If($ForceGarbageCollection) {
                        $GarbageCollectionMaxGeneration = [System.GC]::MaxGeneration
                        $GarbageCollectionLogContentVariableGeneration = [System.GC]::GetGeneration($Instance)
                        $GarbageCollectionTotalMemory = [math]::ceiling(([System.GC]::GetTotalMemory($False))/1MB*100)/100
                        Write-Verbose "GC::GetGeneration(<Variable>) : $GarbageCollectionLogContentVariableGeneration | GC::MaxGeneration : $GarbageCollectionMaxGeneration | GC::TotalMemory : $GarbageCollectionTotalMemory"
                        Write-Verbose "Invoke GC::Collect()"
                        [System.GC]::Collect()
                        $GarbageCollectionTotalMemory = [math]::ceiling(([System.GC]::GetTotalMemory($False))/1MB*100)/100
                        Write-Verbose "GC::TotalMemory : $GarbageCollectionTotalMemory"
                        Write-Verbose "Invoke GC::GetTotalMemory(forceFullCollection)"
                        $GarbageCollectionTotalMemory = [math]::ceiling(([System.GC]::GetTotalMemory($True))/1MB*100)/100
                        Write-Verbose "GC::TotalMemory : $GarbageCollectionTotalMemory"
                    }
                }
                ElseIf($Null -ne $Instance.Job) {
                    $InstanceHasData = $True
                }
            }
            If($InstanceHasData -and $Wait) {
                Start-Sleep -Milliseconds 100
            }
        }
        While ($InstanceHasData -and $Wait)
    }

}
Process {
    ForEach($Computer in $ComputerName) {
        If($Computer.GetType().FullName -eq "Microsoft.ActiveDirectory.Management.ADComputer") {
            Do {
                ForEach($Property in @('DNSHostname','Name','CN')) {
                    If($Null -ne $Computer.$Property) {
                        Write-Verbose "$(Get-Date) : $Computer : Using ADComputer property $Property : $($Computer.$Property)"
                        $Computer = $Computer.$Property
                        break
                    }
                }
            }
            Until ($Computer.GetType().FullName -eq "System.String" -Or $Property -eq 'CN')
        }
        If($Computer.GetType().FullName -eq "Microsoft.ActiveDirectory.Management.ADComputer") {
            Write-Verbose "$(Get-Date) : $Computer : Unable to convert to ComputerName or DNS hostname."
            continue
        }
        Write-Verbose "$(Get-Date) : $Computer."
        $GetNetConnectionParameters.ProgressID++
        $PowerShell = [powershell]::Create().AddScript($GetNetConnectionScriptBlock).AddArgument($Computer).AddArgument($GetNetConnectionParameters)
        $PowerShell.RunspacePool = $RunspacePool
        $RunspaceProperties = [Ordered]@{}
        $RunspaceProperties.PowerShell = $PowerShell
        $RunspaceProperties.Job = $PowerShell.BeginInvoke()
        [void]$RunspaceJobs.Add([PSCustomObject]$RunspaceProperties)
        Get-RunspaceResults $RunspaceJobs
    }
}
End {
    Get-RunspaceResults $RunspaceJobs -Wait
    If($WriteProgress) {
        Write-Progress -Activity "$($MyInvocation.MyCommand.Name)" -ID $ProgressID -Completed
    }
}