Public/Network/Connectivity/Test-ConnectionAsync.ps1

function Test-ConnectionAsync {
  <#
  .SYNOPSIS
    Performs a ping test asynchronously
  .DESCRIPTION
    Performs a ping test asynchronously
  .PARAMETER ComputerName
    List of computers to test connection. Aliased to 'CN', 'Server'
  .PARAMETER Timeout
    Timeout in milliseconds. Default 2000 ms.
  .PARAMETER TimeToLive
    Sets a time to live on ping request. Default 128.
  .PARAMETER BufferSize
    How large you want the buffer to be. Valid range 32-65500, default of 32. If the buffer is 1501 bytes or greater then the ping can fragment.
  .PARAMETER IncludeSource
    A switch determining if you want the source computer name to appear in the output
  .PARAMETER Full
    A switch determining if full output appears
  .NOTES
    Inspired by Test-ConnectionAsync by 'Boe Prox'
    https://gallery.technet.microsoft.com/scriptcenter/Asynchronous-Network-Ping-abdf01aa
  .OUTPUTS
      [pscustomobject] with output from Net.AsyncPingResult and optionally the source address
  .EXAMPLE
      Test-ConnectionAsync google.com, youtube.com, bing.com, github.com, alainQtec.dev
 
      ComputerName IPAddress Result
      ------------ --------- ------
      google.com 172.217.170.206 Success
      youtube.com 172.217.170.174 Success
      bing.com 204.79.197.200 Success
      github.com 140.82.121.4 Success
      alainQtec.dev 185.199.110.153 Success
 
 
    Description
    -----------
    Performs asynchronous ping test against listed systems and lists brief output.
  .EXAMPLE
    Test-ConnectionAsync google.com, youtube.com, bing.com, github.com, alainQtec.dev -Full | ft
 
    ComputerName IPAddress Result BufferSize ResponseTime DontFragment Timeout TimeToLive
    ------------ --------- ------ ---------- ------------ ------------ ------- ----------
    google.com 172.217.170.206 Success 32 79 True 2000 128
    youtube.com 172.217.170.174 Success 32 79 True 2000 128
    bing.com 204.79.197.200 Success 32 137 True 2000 128
    github.com 140.82.121.3 Success 32 252 True 2000 128
    alainQtec.dev 185.199.110.153 Success 32 136 True 2000 128
 
    Description
    -----------
    Performs asynchronous ping test against listed systems and lists full output.
  .EXAMPLE
    Test-ConnectionAsync -ComputerName server1,server2 -Full -BufferSize 1500
 
    ComputerName IPAddress BufferSize Result ResponseTime
      ------------ --------- ---------- ------ ------------
    server1 192.168.1.31 1500 Success 140
    server2 192.168.1.41 1500 Success 137
 
    Description
    -----------
    Performs asynchronous ping test against listed systems and lists full output with a buffersize of 1500 bytes.
  .NOTES
    On linux ---> System.PlatformNotSupportedException: Unable to send custom ping payload. Run program under privileged user account or grant cap_net_raw capability using setcap(8).
  #>

  [CmdletBinding(ConfirmImpact = 'None')]
  [OutputType([AsyncPingResult[]])]
  Param (
    [parameter(ValueFromPipeline, Position = 0)]
    [string[]] $ComputerName,

    [parameter()]
    [int] $Timeout = 2000,

    [parameter()]
    [Alias('Ttl')]
    [int] $TimeToLive = 128,

    [parameter()]
    [validaterange(32, 65500)]
    [int] $BufferSize = 32,

    [parameter()]
    [switch] $IncludeSource,

    [parameter()]
    [switch] $Full
  )

  begin {
    #Requires -Version 3.0
    $result = @(); if ($IncludeSource) { $Source = $env:COMPUTERNAME }
    $Buffer = [System.Collections.ArrayList]::new()
    1..$BufferSize | ForEach-Object { $null = $Buffer.Add(([byte] [char] 'A')) }
    $PingOptions = [System.Net.NetworkInformation.PingOptions]::new()
    $PingOptions.Ttl = $TimeToLive
    $DontFragment = $BufferSize -le 1500 ? $true : $false
    @{
      ComputerName  = "[$($ComputerName -join ', ')]"
      BufferSize    = "[$BufferSize]"
      Timeout       = "[$Timeout]"
      TimeToLive    = "[$TimeToLive]"
      IncludeSource = "[$IncludeSource]"
      Full          = "[$Full]"
      DontFragment  = "[$DontFragment]"
    } | Write-Host -f Green
    $PingOptions.DontFragment = $DontFragment
    $Computerlist = [System.Collections.ArrayList]::new()
    class AsyncPingResult {
      [string]$Result
      [string]$IPAddress
      [int]$ResponseTime
      [int]$BufferSize
      hidden [string]$ComputerName
      hidden [bool]$DontFragment
      hidden [string]$Source
      hidden $TimeToLive
      hidden $Timeout
    }
  }

  process {
    foreach ($Computer in $ComputerName) {
      [void]$Computerlist.Add($Computer)
    }
    $Task = foreach ($Computer in $ComputerList) {
      [pscustomobject] @{
        ComputerName = $Computer
        Task         = [System.Net.NetworkInformation.Ping]::new().SendPingAsync($Computer, $Timeout, $Buffer, $PingOptions)
      }
    }
    try {
      [void] [Threading.Tasks.Task]::WaitAll($Task.Task)
    } catch {
      $_ | Format-List * -Force | Out-String | Write-Host -f Red
      Write-Error -Message "⛔ Error checking [$Computer]"
    }
    $result = @();
    $Task | ForEach-Object {
      $r = [AsyncPingResult]::new()
      if ($_.Task.IsFaulted) {
        $r.Result = $_.Task.Exception.InnerException.InnerException.Message
        $r.IPAddress = $Null
        $r.ResponseTime = $Null
      } else {
        $r.Result = $_.Task.Result.Status
        $r.IPAddress = $_.task.Result.Address.ToString()
        $r.ResponseTime = $_.task.Result.RoundtripTime
      }
      $r.ComputerName = $_.ComputerName
      $r.BufferSize = $BufferSize
      $r.Timeout = $Timeout
      $r.TimeToLive = $TimeToLive
      $r.DontFragment = $DontFragment
      if ($IncludeSource) { $r.Source = $Source }
      $result += $r
    }
  }

  end {
    return $result
  }
}