1

New to Powershell and Stackoverflow. Here's my first Powershell Script that I'm trying to optimize to the best of my abilities. My goal is to have the code run as efficiently as possible. Any help/suggestions on that front would be much appreciated!

This script shows new 'Established' TCP Connections (Get-NetTCPConnection) and their associated DNS Hostnames (Resolve-DnsName). Each new Connection is compared to an array of previous Connections. If they have the same 'RemoteAddress', the DNS Hostname is copied over to the new Connection and displayed; otherwise it creates a new (Resolve-DnsName) (Start-ThreadedJob), and moves on to the next new Connection. Once a Job is 'Completed' it copies over the 'NameHost' and displays the Connection.

I have hit a roadblock in my understanding. When the code is running, the Job 'Ids' seem to be incrementing very quickly even though no new Jobs where created in between the last Job and the new Job.

To test the script, run it and visit any Site. Watch as the 'Id' increment very quickly. Please note that it will create a Log File in "C:\Temp\Active_Connections.csv"

$logFile = 'C:\Temp\Active_Connections.csv'
if (-not(Test-Path $logFile -PathType Leaf)){
    New-Item -ItemType File -Force -Path $logFile | Out-Null
    } else {
    Clear-Content $logFile
}
$headersAdded = $true
$newConnections = @()
While ($true){
    $connections = @(Get-NetTCPConnection)
    foreach ($connection in $connections){
        if ($connection.State -eq "Established"){
            if ($newConnections.InstanceID -notcontains $connection.InstanceID){
                if ($newConnections.RemoteAddress -notcontains $connection.RemoteAddress){
                    if ((Get-Job).Name -notcontains $connection.RemoteAddress){
                        Start-ThreadJob -Name $connection.RemoteAddress -ScriptBlock {param($remoteAddress) Resolve-DNSName -Name $remoteAddress} -ArgumentList $connection.RemoteAddress >$null
                    }else{
                        $job = Get-Job | Where-Object {$_.Name -eq $connection.RemoteAddress}
                        if ($job.State -eq "Completed"){
                            Add-Member -InputObject $connection -MemberType NoteProperty -Name "Id" -Value $job.Id -Force
                            Try {
                                $receivedJob = $job | Receive-Job -ErrorAction Stop
                                Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $receivedJob.NameHost -Force
                            }catch{
                                $na = "N/A"
                                Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $na -Force
                            }
                            #Remove-Job -Id $job.Id
                        }
                    }
                }else{
                    foreach ($newConnection in $newConnections){
                        if ($newConnection.RemoteAddress -eq $connection.RemoteAddress){
                            Add-Member -InputObject $connection -MemberType NoteProperty -Name "NameHost" -Value $newConnection.NameHost -Force
                        }
                    }
                }
            }
            if ($null -ne $connection.NameHost){
                if ($headersAdded) {
                    $formatting = @{n='CreationTime';e={$_.CreationTime.ToString("h:mm:ss tt")}},'Id','LocalAddress','LocalPort','RemoteAddress','RemotePort','NameHost'
                    $properties = @{Expression="CreationTime";Width=13},@{Expression="Id";Width=4},@{Expression="LocalAddress";Width=15},@{Expression="LocalPort";Width=10;Alignment="Left"},@{Expression="RemoteAddress";Width=15},@{Expression="RemotePort";Width=10;Alignment="Left"},@{Expression="NameHost";Width=100}
                    ($connection | Select-Object $formatting | Format-Table -Property $properties | Out-String).Trim() | Tee-Object -FilePath $logFile -Append
                    $headersAdded = $false
                } else {
                    ($connection | Select-Object $formatting | Format-Table -HideTableHeaders -Property $properties | Out-String).Trim() | Tee-Object -FilePath $logFile -Append
                }
                $newConnections += $connection
            }
        }
    }
}

Please, let me know what I can do better and if you have any ideas as to why the Job Id's are incrementing so quickly between new Connections.

Appreciate the help, Chris

  • There may be old jobs that are getting removed and not new ones. Open Task Manager and compare before and after code runs to see what job are being created. Add print statement to the code to see detail of what is happening. Double check that two jobs are being create for each task an not a lot more. Other tasks are being run on your machine and they may be the reason the job numbers are increasing rapidly. – jdweng Feb 19 '23 at 14:20
  • Thanks for the response. I went ahead and checked each 'Get-Job' and each job only shows 1 ChildJob. I also opened Task Manager and compared as you have stated. It would seem as though only 1 process is starting any time a new Connection is made. Maybe it's a labelling issue within Powershell itself? – Christien Roy Feb 19 '23 at 14:38
  • Task Manager only shows the tasks started by the owner and system unless you are an Admin. Powershell is probably behaving the same as task manager. Also you will only get the current running tasks. So if you are only seeing one task it either means the seconds task finished or is was launch by a different users. Maybe the PS script need to run as Admin? PS does run As Admin unless you right click the PS shortcut and select Run As Admin. – jdweng Feb 19 '23 at 14:49
  • @jdweng the script is running as Admin. – Christien Roy Feb 19 '23 at 15:18

1 Answers1

0
  • I have no explanation for the jumps in job ID values. While it would be good to know the reason, pragmatically speaking, it isn't necessarily a problem.

  • Your code creates a tight loop which is best avoided.

  • The following is a PowerShell-idiomatic reformulation of your code that tries to get results as early as possible, while sleeping a fixed amount of time between tries (which you can obviously adjust).

    • The upshot is that the output objects won't necessarily be ordered chronologically.

    • The Id property (column) of the output objects reflects the original output order as returned by Get-NetTCPConnection

# NOTE: What is created is NOT a CSV file.
#       It is a plain-text file in tabular format FOR THE HUMAN OBSERVER.
$logFile = 'C:\Temp\Active_Connections.csv'
& {
  $newConnections = [ordered] @{} # (Ordered) hashtable that stores all new connections.
  while ($true) {
    # Look for new connections, and start a thread job for each 
    # in order to resolve the remote adddress to a domain name, if possible.
    Get-NetTCPConnection | 
      Where-Object { $_.State -eq 'Established' -and -not $newConnections.Contains($_.InstanceID) } |
      ForEach-Object {
        $jb = Start-ThreadJob { Resolve-DNSName -Name ($using:_).RemoteAddress }
        $newConnections[$_.InstanceID] = 
          $_ |
            Select-Object CreationTime,
              @{
                n = 'Id'
                e = { $jb.Id }
              },
              LocalAddress, LocalPort, RemoteAddress, RemotePort, 
              @{
                n = 'NameHost'
                e = { $jb }
              }
      }
    # Sleep a little, to avoid a tight loop.
    Start-Sleep -Milliseconds 300
    # Look for thread jobs that have completed, and output
    # the connection-info objects with the job result.
    $newConnections.Keys |
      ForEach-Object {
        if (($obj = $newConnections[$_]) -and ($jb = $obj.NameHost).State -notin 'NotStarted', 'Running') { 
          # A completed job: get its result.
          $result = try { $jb | Receive-Job -ErrorAction Stop } catch { @{ NameHost = 'n/a' } }
          $jb | Remove-Job -Force # Remove the completed job.
          $obj.NameHost = $result.NameHost # Update the object with the job result.
          $obj # Output the updated object.
          $newConnections[$_] = $null # No need to hang on to the object in the hasthable.
        }
      }
  }
} |
  Format-Table  @{ Name = 'CreationTime'; Expression = { $_.CreationTime.ToString('h:mm:ss tt') }; Width = 13 }, 
                @{Expression = "Id"; Width = 4 }, 
                @{Expression = "LocalAddress"; Width = 15 }, 
                @{Expression = "LocalPort"; Width = 10; Alignment = "Left" },
                @{Expression = "RemoteAddress"; Width = 15 }, @{Expression = "RemotePort"; Width = 10; Alignment = "Left" }, 
                @{Expression = "NameHost"; Width = 100 } |
  Tee-Object -FilePath $logFile
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    I will look over this code to try and understand the logic behind it. There's quite a bit of new material to unpack that I am not too familiar with! Thanks for giving me a detailed response, much appreciated! – Christien Roy Feb 19 '23 at 23:00