Public/Test-Connections.ps1

function Test-Connections {
    <#
        .Synopsis
            Test-Connection to multiple devices in parallel.
        .Description
            Test-Connection to multiple devcies in parallel with a color and "watch" feature.
        .Example
            Test-Connections -TargetName 1.1.1.1 -Watch
        .Example
            Test-Connections -TargetName 1.1.1.1, 1.0.0.1, 8.8.4.4, 8.8.8.8, 9.9.9.9 -Watch
        .Example
            Test-Connections 1.1.1.1, 1.0.0.1, 8.8.4.4, 8.8.8.8, 9.9.9.9 -Watch -Repeat
        .Example
            Test-Connections 1.1.1.1, 1.0.0.1, 8.8.4.4, 8.8.8.8, 9.9.9.9 -Count 10 -Watch
        .Example
            Test-Connections $(Get-Content servers.txt) -Watch
        .Example
            @("1.1.1.1", "1.0.0.1", "8.8.4.4", "8.8.8.8", "9.9.9.9") | Test-Connections -Watch
        .Example
            Connect-VIServer esxi.local
            Get-VM | Test-Connections -Watch
        .Example
            (1..10) | ForEach-Object { "192.168.0.$_" } | Test-Connections -Watch
        .Notes
            Name: Test-Connections
            Author: David Isaacson
            Last Edit: 2022-04-24
            Keywords: Test-Connection, ping, icmp
        .Link
            https://github.com/daisaacson/test-connections
        .Inputs
            TargetName[]
        .Outputs
            none
        #Requires -Version 2.0
    #>

    [CmdletBinding(SupportsShouldProcess = $True)]
    Param (
        [Parameter(Mandatory = $True, ValueFromPipeline = $True, HelpMessage = "Stop after sending Count pings")]
        [string[]]$TargetName,

        [Parameter(Mandatory = $False)]
        [Alias("c")]
        [int]$Count = 4,

        [Parameter(Mandatory = $False, HelpMessage = "Continjously send pings")]
        [Alias("Continuous", "t")]
        [switch]$Repeat,

        [Parameter(Mandatory = $False, HelpMessage = "Delay between pings")]
        [int]$Delay = 1,

        [Parameter(Mandatory = $False, HelpMessage = "Interval between pings")]
        [Alias("u")]
        [int]$Update = 1000,

        [Parameter(Mandatory = $False, HelpMessage = "Watch")]
        [Alias("w")]
        [Switch]$Watch
    )

    Begin {
        Write-Verbose -Message "Begin $($MyInvocation.MyCommand)"
        $Targets = @()
        # Destingwish between Windows PowerShell and PowerShell Core
        $WindowsPowerShell = $PSVersionTable.PSEdition -and $PSVersionTable.PSEdition -eq 'Desktop'
        $PowerShellCore = ! $WindowsPowerShell
    }

    Process {
        Write-Verbose -Message "Process $($MyInvocation.MyCommand)"
        If ($pscmdlet.ShouldProcess("$TargetName")) {
            ForEach ($Target in $TargetName) {
                Write-Verbose -Message "$Target, $Count, $Delay, $Repeat"
                Try {
                    If ($WindowsPowerShell) {
                        # Create new Target and Start-Job
                        # Windows PowerShell 5.1 Test-Connection sucks, wrapper for Test-Connection to behave more like Test-Connection in PowerShell Core
                        $Targets += [Target]::new($Target, $(Start-Job -ScriptBlock $([ScriptBlock]::Create({
                                            Param ([String]$TargetName, [int]$Count = 4, [int]$Delay = 1, [bool]$Repeat)
                                            $Ping = 0
                                            While ($Repeat -or $Count -gt $Ping) {
                                                Write-Verbose "$($Repeat) $($Count) $($Ping)"
                                                $Ping++
                                                $icmp = Test-Connection -ComputerName $TargetName -Count 1 -ErrorAction SilentlyContinue
                                                If ($icmp) {
                                                    [PSCustomObject]@{
                                                        Ping    = $Ping;
                                                        Status  = "Success"
                                                        Latency = $icmp.ResponseTime
                                                    }
                                                } else {
                                                    [PSCustomObject]@{
                                                        Ping    = $Ping
                                                        Status  = "Failed"
                                                        Latency = 9999
                                                    }
                                                }
                                                Start-Sleep -Seconds $Delay
                                            }
                                        }
                                    )
                                ) -ArgumentList $Target, $Count, $Delay, $Repeat
                            )
                        )
                    } else {
                        If ($Repeat) {
                            $Targets += [Target]::new($Target, $(Start-Job -ScriptBlock $([ScriptBlock]::Create({ Param ($Target) Test-Connection -TargetName $Target -Ping -Repeat })) -ArgumentList $Target))
                        } else {
                            $Targets += [Target]::new($Target, $(Start-Job -ScriptBlock $([ScriptBlock]::Create({ Param ($Target, $Count) Test-Connection -TargetName $Target -Ping -Count $Count })) -ArgumentList $Target, $Count))
                        }
                    }
                } Catch { $_ }
            }
        }
    }

    End {
        Write-Verbose -Message "End $($MyInvocation.MyCommand)"
        If ($pscmdlet.ShouldProcess("$TargetName")) {
            # https://blog.sheehans.org/2018/10/27/powershell-taking-control-over-ctrl-c/
            # Change the default behavior of CTRL-C so that the script can intercept and use it versus just terminating the script.
            [Console]::TreatControlCAsInput = $True
            # Sleep for 1 second and then flush the key buffer so any previously pressed keys are discarded and the loop can monitor for the use of
            # CTRL-C. The sleep command ensures the buffer flushes correctly.
            Start-Sleep -Seconds 1
            $Host.UI.RawUI.FlushInputBuffer()
            # Continue to loop while there are pending or currently executing jobs.
            While ($Targets.Job.HasMoreData -contains "True") {
                # If a key was pressed during the loop execution, check to see if it was CTRL-C (aka "3"), and if so exit the script after clearing
                # out any running jobs and setting CTRL-C back to normal.
                If ($Host.UI.RawUI.KeyAvailable -and ($Key = $Host.UI.RawUI.ReadKey("AllowCtrlC,NoEcho,IncludeKeyUp"))) {
                    If ([Int]$Key.Character -eq 3) {
                        Write-Warning -Message "Removing Test-Connection Jobs"
                        If ($PowerShellCore) { Write-Host "`e[2A" }
                        $Targets.Job | Remove-Job -Force
                        $killed = $True
                        [Console]::TreatControlCAsInput = $False

                        break
                    }
                    # Flush the key buffer again for the next loop.
                    $Host.UI.RawUI.FlushInputBuffer()
                }
                # Perform other work here such as process pending jobs or process out current jobs.

                # Get Test-Connection updates
                $Targets.Update()

                # Print Output
                $Targets.ToTable() | Format-Table

                # Move cursor up to overwrite old output
                If ($Watch -and $PowerShellCore) {
                    Write-Host "`e[$($Targets.length+5)A"
                }

                # Output update delay
                Start-Sleep -Milliseconds $Update
            }

            # Clean up jobs
            If (!$killed) {
                $Targets.Job | Remove-Job -Force
            }

            # If in "Watch" mode, print output one last time
            If ($Watch) {
                $Targets.ToTable() | Format-Table
            }
        }
    }
} #End function