Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

Parallel Ping and Port Testing in PowerShell Using Runspaces

In this article, I demonstrate how to test network connectivity for a list of computer names or IP addresses using PowerShell. Each target system is tested for ICMP reachability (ping) and TCP connectivity on port 445, which is commonly used for SMB.

The core logic is implemented in a custom function named Test-TcpPort, which resolves the computer name to an IPv4 address, performs a ping test, checks TCP port availability, and returns a structured PowerShell object.

To improve performance, a runspace pool is used with a defined thread limit. This allows all checks to run in parallel instead of sequentially, significantly reducing execution time when testing multiple systems.

The same functionality is demonstrated using two different runspace techniques, both producing identical results but using different approaches to function loading and execution.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
$computers = "ad001", "server1", "psdomain02", "192.168.34.39"

$functionDefinition = ${function:Test-TcpPort}.ToString()

$maxThreads = 10

$runspacePool = [runspacefactory]::CreateRunspacePool(1, $maxThreads)
$runspacePool.Open()

$jobs = @()

foreach ($computer in $computers) {

    $ps = [powershell]::Create()
    $ps.RunspacePool = $runspacePool

    $script = $ps.AddScript({
        param($ComputerName, $Port, $FunctionText)

        #region function Test-TcpPort

            function Test-TcpPort {
                param (
                    [parameter(Mandatory)][String]$ComputerName,
                    [parameter(Mandatory)][Int]$Port,
                    [parameter(Mandatory=$false)][Int]$ConnectionTimeout = 500
                )

                try {
                    $hostEntry = [System.Net.Dns]::GetHostAddresses($ComputerName)
                    $ip = ($hostEntry | Where-Object AddressFamily -eq 'InterNetwork').IPAddressToString
                } catch {
                    $ip = $ComputerName
                }

                try {
                    $ping = New-Object System.Net.NetworkInformation.Ping
                    $pingReply = $ping.Send($ip)
                    $pingStatus = ($pingReply.Status -eq 'Success')
                } catch {
                    $pingStatus = $false
                }

                $tcpClient = New-Object System.Net.Sockets.TcpClient
                $portConnectionStatus = $false

                try {
                    $async = $tcpClient.BeginConnect($ip, $Port, $null, $null)
                    if ($async.AsyncWaitHandle.WaitOne($ConnectionTimeout, $false)) {
                        $tcpClient.EndConnect($async)
                        $portConnectionStatus = $true
                    }
                }
                finally {
                    $tcpClient.Close()
                }

                [pscustomobject]@{
                    ComputerName = $ComputerName
                    IP           = $ip
                    PingStatus   = $pingStatus
                    PortConnectionStatus = $portConnectionStatus
                }
            }

        #endregion function Test-TcpPort
        
        Test-TcpPort -ComputerName $ComputerName -Port $Port
    })

    $script.AddArgument($computer) | Out-Null
    $script.AddArgument(445) | Out-Null
    $script.AddArgument($functionDefinition) | Out-Null

    $jobs += [pscustomobject]@{
        PowerShell = $ps
        Handle     = $ps.BeginInvoke()
        Computer   = $computer
    }
}

$results = foreach ($job in $jobs) {
    try {
        $job.PowerShell.EndInvoke($job.Handle)
    }
    finally {
        $job.PowerShell.Dispose()
    }
}

$runspacePool.Close()
$runspacePool.Dispose()

$results

Check previous related articles
High-Performance Network Auditing: Custom Ping and TCP Port Testing in PowerShell .Net Objects
Building a Unified Ping and Port Connectivity Report with PowerShell

In below script I am passing function to runspaces and this script will be executed in below workflow and will provide you quick result.

STEP 1: Define the list of computers or IP addresses.
STEP 2: Specify the TCP port to test.
STEP 3: Define the Test-TcpPort function to resolve IPv4, test ping, and validate port connectivity.
STEP 4: Create an Initial Session State to make the function available to runspaces.
STEP 5: Create and open a runspace pool with a defined thread limit.
STEP 6: Define the script block executed independently inside each runspace.
STEP 7: Create PowerShell instances and start parallel execution for each computer.
STEP 8: Wait for all runspaces to complete execution.
STEP 9: Collect output and clean up PowerShell instances.
STEP 10: Dispose of the runspace pool.
STEP 11: Display the final results.

Download this PortScanner_WithRunSpaceExamples.zip here or it is also available on github.com.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
$computers = "ad001", "server1", "psdomain02", "192.168.34.39"
$port = 445

function Test-TcpPort {
    param (
        [parameter(Mandatory)][String]$ComputerName,
        [parameter(Mandatory)][Int]$Port,
        [parameter(Mandatory=$false)][Int]$ConnectionTimeout = 500
    )

    try {
        $hostEntry = [System.Net.Dns]::GetHostAddresses($ComputerName)
        # Fix: Ensure we take the first IPv4 address found
        $ip = ($hostEntry | Where-Object { $_.AddressFamily -eq 'InterNetwork' } | Select-Object -First 1).IPAddressToString
    } catch {
        $ip = $ComputerName
    }

    if (-not $ip) { $ip = $ComputerName }

    try {
        $ping = New-Object System.Net.NetworkInformation.Ping
        $pingReply = $ping.Send($ip, $ConnectionTimeout)
        $pingStatus = ($pingReply.Status -eq 'Success')
    } catch {
        $pingStatus = $false
    }

    $tcpClient = New-Object System.Net.Sockets.TcpClient
    $portConnectionStatus = $false

    try {
        $async = $tcpClient.BeginConnect($ip, $Port, $null, $null)
        if ($async.AsyncWaitHandle.WaitOne($ConnectionTimeout, $false)) {
            $tcpClient.EndConnect($async)
            $portConnectionStatus = $true
        }
    } catch {
        $portConnectionStatus = $false
    }
    finally {
        $tcpClient.Dispose()
    }

    [pscustomobject]@{
        ComputerName         = $ComputerName
        IP                   = $ip
        PingStatus           = $pingStatus
        PortConnectionStatus = $portConnectionStatus
    }
}

$initialSessionState = [InitialSessionState]::CreateDefault()
$functioDefinition = Get-Content Function:\Test-TcpPort
$addMessageSessionStateFunction = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList 'Test-TcpPort', $functioDefinition
$initialSessionState.Commands.Add($addMessageSessionStateFunction)

$runspacePool = [RunspaceFactory]::CreateRunspacePool(2, 10, $initialSessionState, $Host)
$runspacePool.ThreadOptions = "ReuseThread"
$runspacePool.Open()

$syncHash = [hashtable]::Synchronized(@{})

$scriptBlock = {
    param($ComputerName, $Port)
    $testResult = Test-TcpPort -ComputerName $ComputerName -Port $port
    $testResult
    $syncHash = $testResult
}

$jobs = foreach ($computerName in $computers) {
    $ps = [PowerShell]::Create()
    $ps.AddScript($scriptBlock.ToString())
    $ps.AddParameter('computerName', $computerName)
    $ps.AddParameter('port', $port)
    $ps.RunspacePool = $runspacePool
    New-Object PSObject -Property @{
        PowerShell = $ps
        Handle   = $ps.BeginInvoke()
    }
}

while ($jobs.Handle | Where-Object { -not $_.IsCompleted }) {
    Start-Sleep -Seconds 1
}

$results = foreach ($job in $jobs) {
    $psJob = $job.PowerShell
    $psHandle = $job.Handle

    if ($psJob) {
        $output = $psJob.EndInvoke($psHandle)
        $psJob.Dispose()
        $output
    }

}

$runspacePool.Dispose()
$ps.Dispose()

$results

Useful Articles
Executing PowerShell script from PHP HTML web server
Send system disk space utilization HTML report Email using PowerShell
Send Email using PowerShell with .net object System.Net.Mail.MailMessage
PowerShell XML OperationStopped: No coercion operator is defined between types 'System.Object&' and 'System.Object'
Powershell Create new file if not exist, if exist Rename file
PowerShell Create XML document with XmlWriter .net object
PowerShell save export data to XML file
Bulk change multiple file names with OneLiner PowerShell command
Resolved PowerShell Visual studio code stuck with 'Starting up PowerShell' in status bar
Building basic simple Web Server using PowerShell
Running Your First PowerShell Scripts With Jenkins and Git
Git clone or push Missing or invalid credentials fatal authentication failed
PowerShell How to find file and folders in Azure Storage Account Blobs and Containers

Go Back

Comment

Protected by Mathcha

Blog Search

Page Views

1 4 6 7 6 4 8 4

Archive

Follow me on Blogarama