Public/Test-Connections.ps1

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
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