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 {
                            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 {Param ($Target) Test-Connection -TargetName $Target -Ping -Repeat} -ArgumentList $Target))
                        } else {
                            $Targets += [Target]::new($Target,(Start-Job -ScriptBlock {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