PingIt.psm1
<#
.Synopsis Runs a ping-like command which is similar in feel and appearance to a typical ping command. .Description Runs a ping-like command which is similar in feel and appearance to a typical ping command. It can track latency issues either sequentially or by moving average as well as outages. The primary purpose is for data gathering so that one may be properly armed when contending with an ISP, but could equally be useful in collecting data on a troubled network. .Parameter Target The DNS name or IP address to ping. .Parameter Count How many times to ping Target. By default, Target will be pinged until such time as Ctrl-C is pressed. If a non-zero value is supplied, then Target will be pinged that many times. .Parameter BufferSize The size of the ping payload. Defaults to 32. Acceptale values range from 8 to 65527. Has an alias of 'l'. .Parameter ResolveDestination Switch parameter which if specified will signal that an attempt to resolve the DNS name of the target will be made. Has an alias of 'a'. .Parameter Timeout Sets the timeout value for the ping test. The test fails if a response isn't received before the timeout expires. The default is five seconds. Has an alias of 'w'. .Parameter LatencyThreshold Latency issues will be detected and recorded whenever a ping latency (ms) exceeds this value. Defaults to 0 which means no latency issue detection. .Parameter LatencyWindow The minimum number of sequential latency threshold occurences for a record to be created. Is also used to specify a moving average window size. Defaults to 5. .Parameter LatencyMovingAvg If specified, the moving average over LatencyWindow packets will be compared with the LatencyThreshold to determine applicability. .Parameter OutageMinPackets Sets how many packets must fail in some manner in order for an outage record to be created. Defaults to 2. .Parameter Timestamps If specified, timestamps will be displayed for each ping result. Has an alias of 'D'. .Parameter DontFragment See documentation for Test-Connection .Parameter IPv4 See documentation for Test-Connection .Parameter IPv6 See documentation for Test-Connection .Parameter MaxHops See documentation for Test-Connection .Example # Ping contoso.com continuously until Ctrl-C is pressed and track outages. Invoke-PingIt contoso.com .Example # Ping contoso.com continuously until Ctrl-C is pressed and track outages and latency issues where the latency is >= 75 for five packets in a row. Invoke-PingIt contoso.com -LatencyThreshold 75 .Example # Ping contoso.com continuously until Ctrl-C is pressed and track outages and latency issues where the moving average over five packets is >= 75. Invoke-PingIt contoso.com -LatencyThreshold 75 -LatencyMovingAvg .Example # Ping contoso.com continuously until Ctrl-C is pressed and track outages and latency issues where the moving average over five packets is >= 75. Invoke-PingIt contoso.com -LatencyThreshold 75 -LatencyMovingAvg .Example # Ping contoso.com continuously until Ctrl-C is pressed and track outages and latency issues where the moving average over five packets is >= 75. Invoke-PingIt contoso.com -LatencyThreshold 75 -LatencyMovingAvg .Example # Ping contoso.com 250 times and track outages and latency issues where the moving average over 10 packets is >= 75. Invoke-PingIt contoso.com -LatencyThreshold 75 -LatencyMovingAvg -LatencyWindow 10 -Count 250 .Example # Attempt to resolve and ping 10.0.0.1 continuously until Ctrl-C is pressed and track outages. Invoke-PingIt 10.0.0.1 -ResolveDestination .Example # Ping contoso.com continuously showing the timestamp of each until Ctrl-C is pressed and track outages Invoke-PingIt contoso.com -Timestamps #> [char]$script:e = [char]27 # console virtual terminal ESC sequence (0x1B) function ColorizeMinMax { [OutputType([string])] param( $value, [int]$collectionCount, [int]$compareIndex, [int]$valueIndexMax, [int]$valueIndexMin ) # if there are only two items, then no point in distinguishing max and min values if ($collectionCount -lt 3) { return $value } $color = 0 if ($compareIndex -eq $valueIndexMax) { $color = 31 #red } elseif ($compareIndex -eq $valueIndexMin) { $color = 33 #yellow } # $e[${color}m specifies the text color for $value # ${e}[0m resets the color of text back to normal # we use ${e} instead of $e here because $value$e doesn't parse properly ($e and ${e} evaluate to the same value) $result = "$e[${color}m$value${e}[0m" $result } function MarkMovingAvgLatencyIssueEnd { [OutputType([string])] param( [string]$value ) # '4' is the underline formatting mode # '7' is the 'Negative' formatting mode (swap foreground/background colors) "$e[$(7)m$value${e}[0m" } function CreateLatencyTracker { [OutputType('PingIt.LatencyTracker')] param ( [Parameter(Mandatory = $true)] [DateTime]$start ) [PSTypeName('PingIt.LatencyTracker')]$latencyRecord = [PSCustomObject]@{ Elapsed = New-TimeSpan # empty argument list gives a timespan of 0 End = $null PSTypeName = 'PingIt.LatencyTracker' Responses = @() Start = $start } $latencyRecord } # only utilized when LatencyMovingAvg is true function HandleLatency { [OutputType('PingIt.LatencyRecord')] param( [System.Collections.Queue]$latencyQueue, [int]$latencyWindow, [DateTime]$timestamp, [int]$latency ) [PSTypeName('PingIt.LatencyRecord')]$latencyRecord = [PSCustomObject]@{ PSTypeName = 'PingIt.LatencyRecord' Timestamp = $timestamp Latency = $latency } if ($latencyQueue.Count -ge $latencyWindow) { $latencyQueue.Dequeue() | Out-Null } $latencyQueue.Enqueue($latencyRecord) | Out-Null $latencyRecord } # only utilized when LatencyMovingAvg is true function EvaluateLatencyState { [OutputType([bool])] param( [System.Collections.Queue]$latencyQueue, [int]$latencyWindow, [int]$latencyThreshold ) [bool]$result = $false if ($latencyWindow -le $latencyQueue.Count -and $latencyThreshold -le ($latencyQueue | Measure-Object -Property Latency -Average).Average) { $result = $true } $result } function FinalizeLatencyTracker { param( [ref]$latencyIssuesParm, # we pass the array by ref because when we add an element to it, a new array is created which we must assign to the object passed in [PSTypeName('PingIt.LatencyTracker')]$latencyTracker, [DateTime]$endingTimeStamp ) $latencyTracker.End = $endingTimeStamp $latencyTracker.Elapsed = ($latencyTracker.End - $latencyTracker.Start) $latencyIssuesParm.Value += $latencyTracker } function CreateErrorRecord { [OutputType('PingIt.ErrorRecord')] [PSTypeName('PingIt.ErrorRecord')]$errorRecord = [PSCustomObject]@{ DestinationHostUnreachableCount = 0 DestinationNetworkUnreachableCount = 0 DestinationUnreachableCount = 0 Elapsed = $null End = $null NoResponseCount = 0 PacketTooBigCount = 0 PSTypeName = 'PingIt.ErrorRecord' Start = $null TimedOutCount = 0 UnknownErrorCount = 0 } $errorRecord } function FinalizeOutage { param ( [ref][bool]$outageActive, [PSTypeName('PingIt.ErrorRecord')]$errorRecord, [DateTime]$outageEndTimestamp, # need [ref] here because adding element to an array creates a new array # the actual type is [PSTypeName('PingIt.ErrorRecord')][object[]], but use 'PSCustomObject' to get pass by reference to work [ref][PSCustomObject[]]$allErrors, [int]$outageMinPackets ) $outageActive.Value = $false if ($errorRecord.DestinationHostUnreachableCount + $errorRecord.DestinationNetworkUnreachableCount + $errorRecord.DestinationUnreachableCount + $errorRecord.NoResponseCount + $errorRecord.PacketTooBigCount + $errorRecord.TimedOutCount + $errorRecord.UnknownErrorCount -lt $outageMinPackets) { return } $errorRecord.End = $outageEndTimestamp $errorRecord.Elapsed = ($errorRecord.End - $errorRecord.Start) $allErrors.Value += $errorRecord } function ResolveDestination { param ( [string]$target, [int]$bufferSize, [int]$timeoutSeconds ) [string]$err = 'errorVar' $theArgs = @{ BufferSize = $bufferSize Count = 1 ErrorAction = 'SilentlyContinue' ErrorVariable = $err ResolveDestination = $true TargetName = $target TimeoutSeconds = $timeoutSeconds } [Microsoft.Powershell.Commands.TestConnectionCommand+PingStatus]$result = $null # PingStatus requires PS >= 7.2.0 $result = Test-Connection @theArgs [bool]$isError = $false if (Test-Path variable:\$err) { [PSVariable]$variable = Get-Variable $err [System.Collections.ArrayList]$value = ($null -ne $variable) ? $variable.Value : [System.Collections.ArrayList]::new() # ternary operator requires PS >= 7.0 if ($value.Count -gt 0) { $isError = $true } } if ($isError -or $null -eq $result) { Write-Host "Pinging $target (could not resolve) with $bufferSize bytes of data:" } else { [string]$displayAddress = $result.DisplayAddress if ($displayAddress -eq '*') { $displayAddress = "(could not resolve)" } Write-Host "Pinging $($result.Destination) [$displayAddress] with $($result.BufferSize) bytes of data:" } } function Invoke-PingIt { [OutputType([string])] param( [Parameter(Mandatory = $true, HelpMessage = "DNS name or IP address", Position = 0)] [ValidateNotNullOrEmpty()] [string]$Target, [Parameter(Mandatory = $false, HelpMessage = "Count of pings to run before exiting. If 0 is supplied or if not specified, will ping until Ctrl-C is pressed.")] [ValidateRange(0, [int]::MaxValue)] [int]$Count = 0, [Parameter(Mandatory = $false, HelpMessage = "Packet size in bytes. Defaults to 32, maximum is 65,527")] [ValidateRange(8, 65527)] [Alias('l')] [int]$BufferSize = 32, [Parameter(Mandatory = $false, HelpMessage = "An attempt to resolve the DNS name of the target will be made.")] [Alias('a')] [switch]$ResolveDestination, [Parameter(Mandatory = $false, HelpMessage = "Sets the timeout value for the test. The test fails if a response isn't received before the timeout expires. The default is five seconds.")] [ValidateRange(1, 1000)] [Alias('w')] [int]$Timeout = 5, [Parameter(Mandatory = $false, HelpMessage = "Latency issues will be detected and recorded whenever a ping latency (ms) exceeds this value. Default of 0 means no latency issue detection.")] [ValidateRange(0, [int]::MaxValue)] [int]$LatencyThreshold = 0, [Parameter(Mandatory = $false, HelpMessage = "The minimum number of sequential latency threshold occurences for a record to be created. Defaults to 5.")] [ValidateRange(0, [int]::MaxValue)] [int]$LatencyWindow = 5, [Parameter(Mandatory = $false, HelpMessage = "If specified, the moving average over 'LatencyWindow' packets will be compared with the 'LatencyThreshold")] [switch]$LatencyMovingAvg, [Parameter(Mandatory = $false, HelpMessage = "Sets how many packets must fail in some manner in order for an outage record to be created. Defaults to 2.")] [int]$OutageMinPackets = 2, [Parameter(Mandatory = $false, HelpMessage = "If specified, timestamps will be displayed for each ping result.")] [Alias('D')] [switch]$Timestamps, [Parameter(Mandatory = $false, HelpMessage = "See documentation for Test-Connection.")] [switch]$DontFragment, [Parameter(Mandatory = $false, HelpMessage = "See documentation for Test-Connection.")] [switch]$IPv4, [Parameter(Mandatory = $false, HelpMessage = "See documentation for Test-Connection.")] [switch]$IPv6, [Parameter(Mandatory = $false, HelpMessage = "See documentation for Test-Connection.")] [Alias('Ttl', 'TimeToLive', 'Hops')] [int]$MaxHops = 128 ) # so we can capture Ctrl-C [Console]::TreatControlCAsInput = $true $Host.UI.RawUI.FlushInputBuffer() $theArgs = @{ BufferSize = $BufferSize # requires PS >= 5.1.0 Count = 1 # this is 1 because we want the output from Test-Connection after each call. requires PS >= 5.1.0 ErrorAction = 'SilentlyContinue' MaxHops = $MaxHops # MaxHops requires PS >= 7.2.0 TargetName = $Target # TargetName requires PS >= 7.2.0 TimeoutSeconds = $Timeout # TimeoutSeconds requires PS >= 7.2.0 } if ($DontFragment) { $theArgs.Add('DontFragment', $true) # DontFragment requires PS >= 7.2.0 } if ($IPv4) { $theArgs.Add('IPv4', $true) # IPv4 requires PS >= 7.2.0 } if ($IPv6) { $theArgs.Add('IPv6', $true) # IPv6 requires PS >= 7.2.0 } [int]$maxPings = -1 if ($Count -ne 0) { $maxPings = $Count } [PSTypeName('PingIt.ErrorRecord')][object[]]$errors = @() [PSTypeName('PingIt.ErrorRecord')]$currentError = CreateErrorRecord $latencyQueue = $null if ($LatencyMovingAvg) { $latencyQueue = [System.Collections.Queue]::new() } [PSTypeName('PingIt.LatencyRecord')]$previousLatencyRecord = $null $latencyIssues = @() [PSTypeName('PingIt.LatencyTracker')]$currentLatencyTracker = $null [int]$pingCount = 0 [int]$totalLatency = 0 [int]$minLatency = [int]::MaxValue [int]$maxLatency = [int]::MinValue [int]$successCount = 0 [int]$noResponseCount = 0 [int]$destinationHostUnreachableCount = 0 [int]$destinationNetworkUnreachableCount = 0 [int]$destinationUnreachableCount = 0 [int]$packetTooBigCount = 0 [int]$timedOutCount = 0 [int]$unknownErrorCount = 0 [bool]$errorActive = $false [bool]$doNotSleep = $false [string]$displayAddress = '' [string]$elapsedFormat = 'dd\.hh\:mm\:ss' [string]$timestampFormat = 'MM/dd hh\:mm\:ss' [string]$perPingTimestampFormat = 'HH\:mm\:ss' [DateTime]$startTimestamp = Get-Date [DateTime]$endTimestamp = 0 [bool]$ctrlCIntercepted = $false [Microsoft.Powershell.Commands.TestConnectionCommand+PingStatus]$result = $null # PingStatus requires PS >= 7.2.0 if ($ResolveDestination) { $resolveDestArgs = @{ bufferSize = $BufferSize target = $Target timeoutSeconds = $Timeout } ResolveDestination @resolveDestArgs $ResolveDestination = $false } else { Write-Host "Pinging $Target with $BufferSize bytes of data:" } try { while ($true) { # so we can capture Ctrl-C if ($Host.UI.RawUI.KeyAvailable -and ($Key = $Host.UI.RawUI.ReadKey("AllowCtrlC,NoEcho,IncludeKeyUp"))) { $keyCharacter = [Int]$Key.Character -eq 3 # Flush the key buffer again for the next loop. $Host.UI.RawUI.FlushInputBuffer() If ($keyCharacter -eq 3) { $ctrlCIntercepted = $true break } } [DateTime]$pingStart = Get-Date $result = Test-Connection @theArgs [DateTime]$pingEnd = Get-Date $pingCount++ $outputObject = [PSCustomObject]@{ Status = $null Timestamp = $pingEnd } [string]$timestamp = '' if ($Timestamps) { $timestamp = "$($outputObject.Timestamp.ToString($perPingTimestampFormat)) " } [bool]$latencyTrackingEnded = $false; [bool]$errorResult = $false [string]$pingMsg = '' [ConsoleColor]$pingMsgForegroundColor = [ConsoleColor]::White if ($null -ne $result) { [string]$errorDisplay = '' $outputObject.Status = $result.Status switch ($result.Status) { Success { # are we tracking latency issues? if ($LatencyThreshold -gt 0) { if ($result.Latency -ge $LatencyThreshold) { $pingMsgForegroundColor = [ConsoleColor]::Yellow } # sequential? if ($false -eq $LatencyMovingAvg) { # does the current latency meet or exceed the threshold? if ($result.Latency -ge $LatencyThreshold) { # create a tracker if we're not already tracking a latency issue if ($null -eq $currentLatencyTracker) { $currentLatencyTracker = CreateLatencyTracker $pingEnd } $currentLatencyTracker.Responses += $result.Latency } else { # had we already been tracking a latency issue? if ($null -ne $currentLatencyTracker) { # if the count of responses is >= $LatencyWindow, save the record if ($currentLatencyTracker.Responses.Count -ge $LatencyWindow) { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $pingEnd } # reset our latency issue state $currentLatencyTracker = $null } } } else { [PSTypeName('PingIt.LatencyRecord')]$currentLatencyRecord = HandleLatency $latencyQueue $LatencyWindow $pingEnd $result.Latency if ((EvaluateLatencyState $latencyQueue $LatencyWindow $LatencyThreshold)) { # the current latency record is the one which most recently was part of a latency trend # we save it as $previousLatencyRecord so that when a latency trend ends, we can retrieve the timestamp # of the last record in the latency queue which contributed to the latency trend $previousLatencyRecord = $currentLatencyRecord # create and initialize a tracker if we're not already tracking a latency issue if ($null -eq $currentLatencyTracker) { $currentLatencyTracker = CreateLatencyTracker $latencyQueue.Peek().Timestamp # the start is the first element which is part of the latency trend foreach ($latencyItem in $latencyQueue) { $currentLatencyTracker.Responses += $latencyItem.Latency } } else { $currentLatencyTracker.Responses += $result.Latency } } else { # we were previously in a latency trend, but now it has ended if ($null -ne $currentLatencyTracker) { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $previousLatencyRecord.Timestamp $latencyTrackingEnded = $true } $currentLatencyTracker = $null } } } # is there an outage being tracked? if so, we need to finalize the record if ($errorActive) { FinalizeOutage ([ref]$errorActive) $currentError $pingEnd ([ref]$errors) $OutageMinPackets $currentError = CreateErrorRecord } $successCount++ if ($result.Latency -lt $minLatency) { $minLatency = $result.Latency } if ($result.Latency -gt $maxLatency) { $maxLatency = $result.Latency } if ($result.Latency -gt 1000) { $doNotSleep = $true } $totalLatency += $result.Latency $outputObject | Add-Member -MemberType NoteProperty -Name 'Latency' -Value $result.Latency if ([string]::IsNullOrEmpty($displayAddress)) { $displayAddress = $result.DisplayAddress; } } DestinationHostUnreachable { $destinationHostUnreachableCount++ $currentError.DestinationHostUnreachableCount++ $errorDisplay = "Destination host unreachable" } DestinationNetworkUnreachable { $destinationNetworkUnreachableCount++ $currentError.DestinationNetworkUnreachableCount++ $errorDisplay = "Destination net unreachable" } DestinationUnreachable { $destinationUnreachableCount++ $currentError.DestinationUnreachableCount++ $errorDisplay = "Destination unreachable" } PacketTooBig { $packetTooBigCount++ $currentError.PacketTooBigCount++ $errorDisplay = "Packet too big" } TimedOut { $doNotSleep = $true $timedOutCount++ $currentError.TimedOutCount++ $errorDisplay = "Request timed out after $Timeout seconds" } default { # we can assume that this is an error because $result.Status is not Success $unknownErrorCount++ $currentError.UnknownErrorCount++ $errorDisplay = "Error - $($result.Status)" } } if ($result.Status -ne [System.Net.NetworkInformation.IPStatus]::Success) { $errorResult = $true $outputObject | Add-Member -MemberType NoteProperty -Name 'Latency' -Value 'n/a' $pingMsg = "$($timestamp)$errorDisplay" $pingMsgForegroundColor = [ConsoleColor]::Red } else { $pingMsg = "$($timestamp)Reply from $($result.DisplayAddress): bytes=$($result.BufferSize) time=$($result.Latency)ms TTL=$($result.Reply.Options.Ttl)" } } else { $errorResult = $true # not technically an error, but Test-Connection should return a value, but didn't $noResponseCount++ $currentError.NoResponseCount++ $pingMsg = "$($timestamp)No response" $pingMsgForegroundColor = [ConsoleColor]::Red } if ($latencyTrackingEnded -and $LatencyMovingAvg) { Write-Host $(MarkMovingAvgLatencyIssueEnd $pingMsg) -ForegroundColor $pingMsgForegroundColor } else { Write-Host $pingMsg -ForegroundColor $pingMsgForegroundColor } # was there an error and is it the first one of a new record? if ($errorResult -and $false -eq $errorActive) { $errorActive = $true # we got an error and it is the first one. the start of the error state is the time at which Test-Connection was invoked. if we used e.g. $pingEnd, # then if we failed due to timing out and the timeout was 5 seconds, our accounting would be off by 5 seconds $currentError.Start = $pingStart # are we tracking latency issues and are we in the middle of tracking one? if ($LatencyThreshold -gt 0 -and $null -ne $currentLatencyTracker) { if ($false -eq $LatencyMovingAvg) { # if the count of responses is >= $LatencyWindow, save the record if ($currentLatencyTracker.Responses.Count -ge $LatencyWindow) { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $pingStart # our latency issue ended when the outage (error) state began } } else { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $previousLatencyRecord.Timestamp } # reset our latency issue state $currentLatencyTracker = $null } } if ($false -eq $doNotSleep) { Start-Sleep -Seconds 1 } else { $doNotSleep = $false } if (-1 -ne $maxPings -and $pingCount -eq $maxPings) { break } } $endTimestamp = Get-Date # are we tracking latency issues and are we in the middle of tracking one? if ($LatencyThreshold -gt 0 -and $null -ne $currentLatencyTracker) { if ($false -eq $LatencyMovingAvg) { # if the count of responses is >= $LatencyWindow, save the record if ($currentLatencyTracker.Responses.Count -ge $LatencyWindow) { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $endTimestamp } } else { FinalizeLatencyTracker ([ref]$latencyIssues) $currentLatencyTracker $previousLatencyRecord.Timestamp } # reset our latency issue state $currentLatencyTracker = $null } # if we were in the middle of an error situation, finalize the accounting if ($errorActive) { FinalizeOutage ([ref]$errorActive) $currentError $endTimestamp ([ref]$errors) $OutageMinPackets } # normal ping statistics $summary = [PSCustomObject]@{ Start = $startTimestamp.ToString($timestampFormat) End = $endTimestamp.ToString($timestampFormat) Elapsed = $((New-TimeSpan -Start $startTimestamp -End $endTimestamp).ToString($elapsedFormat)) Total = $pingCount } if ($errors.Count -gt 0) { $summary | Add-Member -MemberType NoteProperty -Name 'Succeeded' -Value $successCount } Write-Host "`n`nPing statistics for $displayAddress`:" -NoNewline $summary | Format-Table # round trip times [string]$minLatencyDisplay = "$minLatency" if ($minLatency -eq [int]::MaxValue) { $minLatencyDisplay = 'n/a' } [string]$maxLatencyDisplay = "$maxLatency" if ($maxLatency -eq [int]::MinValue) { $maxLatencyDisplay = 'n/a' } [string]$avgLatencyForDisplay = 'n/a' if ($totalLatency -gt 0 -and $pingCount -gt 0) { $avgLatencyForDisplay = [int]($totalLatency/$pingCount) } $roundTripTimes = [PSCustomObject]@{ Min = $minLatencyDisplay Max = $maxLatencyDisplay Avg = $avgLatencyForDisplay } Write-Host "`nApproximate round trip times in milli-seconds:" -NoNewline $roundTripTimes | Format-Table # latency issues summary and details if ($LatencyThreshold -gt 0 -and $latencyIssues.Count -gt 0) { # summary-related variables [TimeSpan]$totalElapsedLatency = New-TimeSpan # empty argument list gives a timespan of 0 [int]$latencyIssuePingCount = 0 [int]$latencyIssueTotal = 0 [int]$latencyIssueMax = [int]::MinValue [int]$latencyIssueMin = [int]::MaxValue # detail-related variables $latencyDetailRecords = @() [TimeSpan]$minLatencyElapsed = New-TimeSpan -Days 3650 [int]$minLatencyIndex = [int]::MaxValue [TimeSpan]$maxLatencyElapsed = New-TimeSpan # empty argument list gives a timespan of 0 [int]$maxLatencyIndex = [int]::MinValue [int]$latencyIssueIndex = 0 foreach ($latencyIssue in $latencyIssues) { $totalElapsedLatency += (New-TimeSpan -Start $latencyIssue.Start -End $latencyIssue.End) $latencyIssuePingCount += $latencyIssue.Responses.Count [int]$currentLatencyTrackerMax = [int]::MinValue [int]$currentLatencyTrackerMin = [int]::MaxValue [int]$currentLatencyTrackerTotal = 0 foreach ($response in $latencyIssue.Responses) { $latencyIssueTotal += $response $currentLatencyTrackerTotal += $response if ($response -gt $latencyIssueMax) { $latencyIssueMax = $response } if ($response -lt $latencyIssueMin) { $latencyIssueMin = $response } if ($response -gt $currentLatencyTrackerMax) { $currentLatencyTrackerMax = $response } if ($response -lt $currentLatencyTrackerMin) { $currentLatencyTrackerMin = $response } } [int]$currentLatencyTrackerAvg = $currentLatencyTrackerTotal/$latencyIssue.Responses.Count $latencyDetailRecords += [PSCustomObject]@{ Avg = $currentLatencyTrackerAvg Count = $latencyIssue.Responses.Count Elapsed = $latencyIssue.Elapsed.ToString($elapsedFormat) End = $latencyIssue.End.ToString($timestampFormat) Index = $latencyIssueIndex # not for output Max = $currentLatencyTrackerMax Min = $currentLatencyTrackerMin Start = $latencyIssue.Start.ToString($timestampFormat) } $totalElapsedLatency += $latencyIssue.Elapsed if ($latencyIssue.Elapsed -ge $maxLatencyElapsed) { $maxLatencyElapsed = $latencyIssue.Elapsed $maxLatencyIndex = $latencyIssueIndex } if ($latencyIssue.Elapsed -lt $minLatencyElapsed) { $minLatencyElapsed = $latencyIssue.Elapsed $minLatencyIndex = $latencyIssueIndex } $latencyIssueIndex++ } # summary output [string]$moreInfo = 'moving average ' if ($false -eq $LatencyMovingAvg) { $moreInfo = '' } # no point in summarizing if there's only been a single latency issue if ($latencyIssues.Count -gt 1) { [int]$latencyIssueAvg = [int]($latencyIssueTotal/$latencyIssuePingCount) Write-Host "`n`n`Latency $moreInfo(>= $LatencyThreshold) issues summary:" -ForegroundColor Yellow -NoNewline $latencyIssuesSummary = [PSCustomObject]@{ Elapsed = $totalElapsedLatency.ToString($elapsedFormat) Count = $latencyIssuePingCount Min = "$($latencyIssueMin)ms" Max = "$($latencyIssueMax)ms" Avg = "$($latencyIssueAvg)ms" } $latencyIssuesSummary | Format-Table } # details output Write-Host "`nLatency ${moreInfo}issues detail ($($latencyIssues.Count)):" -ForegroundColor Yellow -NoNewline $latencyDetailRecords | Format-Table -Property Start, End, @{Name = 'Elapsed'; e={ ColorizeMinMax $_.Elapsed $latencyDetailRecords.Count $_.Index $maxLatencyIndex $minLatencyIndex }}, Count, Min, Max, Avg # no point in showing totals if there's only been a single latency issue if ($latencyIssues.Count -gt 1) { Write-Host "Latency Issues Total Elapsed: $($totalElapsedLatency.ToString($elapsedFormat))." if ($latencyDetailRecords.Count -gt 1) { [timespan]$latencyIssueAvgElapsed = ($totalElapsedLatency/$latencyIssues.Count) Write-Host "Latency Issues Average Elapsed: $($latencyIssueAvgElapsed.ToString($elapsedFormat))." } } } # error summary and details [TimeSpan]$totalElapsedError = New-TimeSpan # empty argument list gives a timespan of 0 if ($errors.Count -gt 0) { # no point in summarizing if there's only been a single outage if ($errors.Count -gt 1) { Write-Host "`n`n`Outage summary (packet counts):" -ForegroundColor Red -NoNewline $errorSummary = [PSCustomObject]@{ Total = $destinationHostUnreachableCount + $destinationNetworkUnreachableCount + $destinationUnreachableCount + $timedOutCount + $noResponseCount + $unknownErrorCount HostUnreachable = $destinationHostUnreachableCount NetworkUnreachable = $destinationNetworkUnreachableCount OtherUnreachable = $destinationUnreachableCount TimedOut = $timedOutCount Unknown = $unknownErrorCount NoResponse = $noResponseCount } $errorSummary | Format-Table } $outageDetailRecords = @() [TimeSpan]$minOutage = New-TimeSpan -Days 3650 [int]$minOutageIndex = [int]::MaxValue [TimeSpan]$maxOutage = New-TimeSpan # empty argument list gives a timespan of 0 [int]$maxOutageIndex = [int]::MinValue [int]$errorsIndex = 0 foreach ($error in $errors) { $outageDetailRecords += [PSCustomObject]@{ Count = $error.DestinationHostUnreachableCount + $error.DestinationNetworkUnreachableCount + $error.DestinationUnreachableCount + $error.NoResponseCount + $error.PacketTooBigCount + $error.TimedOutCount + $error.UnknownErrorCount Elapsed = $error.Elapsed.ToString($elapsedFormat) End = $error.End.ToString($timestampFormat) Index = $errorsIndex # not for output NoResponse = $error.NoResponseCount Start = $error.Start.ToString($timestampFormat) TimedOut = $error.TimedOutCount Unknown = $error.UnknownErrorCount Unreachable = $error.DestinationHostUnreachableCount + $error.DestinationNetworkUnreachableCount + $error.DestinationUnreachableCount } $totalElapsedError += $error.Elapsed if ($error.Elapsed -gt $maxOutage) { $maxOutage = $error.Elapsed $maxOutageIndex = $errorsIndex } if ($error.Elapsed -lt $minOutage) { $minOutage = $error.Elapsed $minOutageIndex = $errorsIndex } $errorsIndex++ } # details output Write-Host "`nOutage detail ($($errors.Count) outages):" -ForegroundColor Red -NoNewline $outageDetailRecords | Format-Table -Property Start, End, @{Name = 'Elapsed'; e={ ColorizeMinMax $_.Elapsed $outageDetailRecords.Count $_.Index $maxOutageIndex $minOutageIndex }}, Count, NoResponse, Unreachable, TimedOut, Unknown # no point in showing totals if there's only been a single outage if ($errors.Count -gt 1) { Write-Host "Outages Total Elapsed: $($totalElapsedError.ToString($elapsedFormat))." if ($outageDetailRecords.Count -gt 1) { [timespan]$outageAvgElapsed = ($totalElapsedError/$errors.Count) Write-Host "Outage Average Elapsed: $($outageAvgElapsed.ToString($elapsedFormat))." } } } if ($ctrlCIntercepted) { Write-Host "`nControl-C" Write-Host '^C' } } finally { [Console]::TreatControlCAsInput = $false # make sure this is reset } } New-Alias -Name PingIt -Value Invoke-PingIt Export-ModuleMember -Alias * -Function Invoke-PingIt |