functions/Ping-IpList.ps1
|
function Ping-IpList { <# .SYNOPSIS Advanced parallel ping utility for multiple IP addresses with history tracking and statistics. .DESCRIPTION Ping-IpList provides powerful network connectivity testing capabilities with features like: - Parallel ping execution for multiple targets - Continuous monitoring mode - Ping history visualization - DNS resolution - Customizable ping parameters - Support for IP ranges and CIDR notation - Downtime tracking By default only hosts that are currently responding are displayed, which keeps the console readable when scanning large ranges (e.g. a /24 CIDR) where most addresses time out. Use -ShowAll to display every target, including timeouts, as in previous versions. Regardless of display mode, a summary is printed once the function stops - either because it ran its configured -Count of sequences or because it was interrupted with Ctrl+C (useful with -Continuous). .PARAMETER FromClipBoard Reads IP addresses from clipboard. .PARAMETER ipList Array of IP addresses or hostnames to ping. .PARAMETER range IP address range in format "192.168.1.1-192.168.1.254". .PARAMETER cidr CIDR notation subnet like "192.168.1.0/24". .PARAMETER Count Number of ping attempts (default: 4, use -Continuous for endless). .PARAMETER BufferSize Size of ping packet in bytes (default: 32). .PARAMETER DontFragment Sets the Don't Fragment flag in ping packet. .PARAMETER Ttl Time to live value (default: 128). .PARAMETER Timeout Ping timeout in milliseconds (default: 100). .PARAMETER Continuous Enables continuous ping mode. .PARAMETER ResolveDNS Resolves IP addresses to hostnames. .PARAMETER ShowHistory Displays ping history using symbols (! for success, . for failure). .PARAMETER ShowAll Displays every target in the console output, including hosts that are currently timing out. Without this switch, only currently responding hosts are shown (the "Responding hosts: X/Y" header still reflects the full list). .PARAMETER HistoryResetCount Number of results to keep in history before reset (default: 100). .PARAMETER DontSortIpList Prevents automatic IP address sorting. .PARAMETER MaxThreads Maximum number of concurrent ping threads (default: 100). .PARAMETER OutToPipe Outputs results to pipeline instead of console. .PARAMETER logEvents Logs events related to downtime and uptime. .EXAMPLE # Copy these IPs to your clipboard: # 192.168.1.10 # 8.8.8.8 # 1.1.1.1 Ping-IpList -FromClipBoard -ShowHistory Output: 2024-11-19 14:01:24, Ping sequence: 1 192.168.1.10 [t:1ms DownFor:0s]:! 8.8.8.8 [t:27ms DownFor:0s]:! 1.1.1.1 [t:18ms DownFor:0s]:! This example shows how to quickly ping multiple IPs by copying them from any source (text file, Excel, web page). Just ensure each IP is on a new line before copying. .EXAMPLE Ping-IpList -ipList "8.8.8.8","1.1.1.1" -Count 10 Output: IPAddress ResponsTime Result DownTime --------- ----------- ------ -------- 1.1.1.1 18 Success 8.8.8.8 27 Success .EXAMPLE Ping-IpList -range "192.168.1.1-192.168.1.10" -Continuous -ShowHistory Output: 2024-11-19 13:57:24, Ping sequence: 7 192.168.1.1 [t:1ms DownFor:0s]:!!!!!!! 192.168.1.2 [t:-ms DownFor:9s]:....... 192.168.1.3 [t:-ms DownFor:9s]:....... [...] .EXAMPLE Ping-IpList -cidr "10.0.0.0/29" -ResolveDNS Output (only currently responding hosts are listed by default): 2024-11-19 13:58:09, Ping sequence: 4, Responding hosts: 3/6 IPAddress ResponsTime Result DownTime --------- ----------- ------ -------- host1.example.com 1 Success 10.0.0.2 1 Success host3.example.com 1 Success [...] .EXAMPLE Ping-IpList -cidr "10.200.12.0/24" -ShowAll Shows every address in the /24, including the ones that are timing out, restoring the pre-1.2.2 default behavior. .EXAMPLE Ping-IpList -range "192.168.1.1-192.168.1.10" -Continuous # Press Ctrl+C to stop Output (printed once the run stops, whether by Ctrl+C or reaching -Count): ==================== Ping Summary ==================== Started : 2026-08-10 14:20:00 Ended : 2026-08-10 14:23:45 Duration: 00:03:45 Sequences: 45 192.168.1.1 Sent:45 Success:45 Failed:0 Rate:100.0% DownTime:0s 192.168.1.2 Sent:45 Success:10 Failed:35 Rate: 22.2% DownTime:35s [...] ======================================================== .NOTES Author: Iman Edrisian Date: October 2025 Requires: PowerShell 5.1 or higher Tags: Network, Monitoring, Ping #> [CmdletBinding()] param ( [switch]$FromClipBoard, [Parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [Alias('list')] [String[]]$ipList, [string]$range, [string]$cidr, [Alias('n')] [int]$Count = 4, [Alias('l')] [int]$BufferSize = 32, [Alias('f')] [switch]$DontFragment, [Alias('i')] [int]$Ttl = 128, [int]$Timeout = 100, [Alias('c', 't')] [switch]$Continuous, [Alias('a')] [switch]$ResolveDNS, [switch]$ShowHistory, [switch]$ShowAll, [int]$HistoryResetCount = 100, [switch]$DontSortIpList, [int]$MaxThreads = 100, [switch]$OutToPipe, [switch]$logEvents ) if ($range) { $ipList = Get-IpAddressesInRange $range } elseif ($cidr) { $ipList = Get-IPAddressesInSubnet $cidr } else { if ($FromClipBoard) { $ipList = Get-Clipboard } # Trim leading or trailing white spaces from each entry in the list $ipList = $ipList | ForEach-Object { $_.Trim() } # Removing any non-IP addresses or blank entry from the list $ipList = $ipList -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$" # Check if the list does not contain any hostnames if (-not $DontSortIpList) { if (($ipList -match "^(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$").Count -eq 0) { $ipList = Sort-IpAddress $ipList } } } if ($ResolveDNS) { for ($i = 0; $i -lt $ipList.Count; $i++) { if ($ipList[$i] -match "\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}") { $NameHost = (Resolve-DnsName $ipList[$i] -Type PTR -DnsOnly -ErrorAction SilentlyContinue).NameHost if ($NameHost) { if ($NameHost.Count -gt 1) { $ipList[$i] = $NameHost[0] } else { $ipList[$i] = $NameHost } } } } } try { if ($Continuous) { $Count = -1 } $iCount = 0 $pingHistory = [ordered]@{} $scriptStartTime = Get-Date while ($iCount -ne $Count) { # Create and open a runspace pool $pool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads) $pool.Open() # Collect the runspaces $runspaces = New-Object System.Collections.ArrayList foreach ($ip in $ipList) { # Prepare a script block to run in parallel $scriptBlock = { param($ip, $BufferSize, $Ttl, $DontFragment, $Timeout, $HistoryResetCount) function Send-Ping { [CmdletBinding()] param ( [string]$target, [int]$BufferSize = 32, [switch]$DontFragment, [int]$Ttl = 128, [int]$Timeout = 100 ) $ping = New-Object System.Net.NetworkInformation.Ping $pingOptions = New-Object System.Net.NetworkInformation.PingOptions($Ttl, $DontFragment) $pingResult = $ping.Send($target, $Timeout, [System.Text.Encoding]::ASCII.GetBytes(("a" * $BufferSize)), $pingOptions) Return $pingResult } # Simulating a ping result $pingResult = send-ping -Target $ip -BufferSize $BufferSize -Ttl $Ttl -DontFragment:$DontFragment -Timeout $Timeout $pingStatistics = [pscustomobject]@{ IPAddress = $ip ResponsTime = [double]0.0 Result = "" ResultHistory = "" DownTimeStart = $null DownTime = $null } if ($pingResult.Status -eq "Success") { $pingStatistics.ResponsTime = $pingResult.RoundtripTime $pingStatistics.Result = $pingResult.Status $pingStatistics.ResultHistory = "!" } else { $pingStatistics.ResponsTime = "-" $pingStatistics.Result = "TimedOut" $pingStatistics.ResultHistory = "." } # Return the result return @($ip, $pingStatistics) } # Create a new PowerShell runspace and configure it $powershell = [powershell]::Create().AddScript($scriptBlock).AddArgument($ip).AddArgument($BufferSize).AddArgument($Ttl).AddArgument($DontFragment).AddArgument($Timeout).AddArgument($HistoryResetCount) $powershell.RunspacePool = $pool # Start the asynchronous execution of the PowerShell instance $runspaces.Add([PSCustomObject]@{ Pipe = $powershell AsyncResult = $powershell.BeginInvoke() }) | Out-Null } # Collect and process the results as they complete foreach ($runspace in $runspaces) { $result = $runspace.Pipe.EndInvoke($runspace.AsyncResult) $runspace.Pipe.Dispose() $ipAddress = $result[0] $pingStatistics = $result[1] if ($pingHistory.Contains($ipAddress)) { if ($pingHistory[$ipAddress].ResultHistory.Length -eq $HistoryResetCount) { $pingHistory[$ipAddress].ResultHistory = "" } $pingHistory[$ipAddress].ResponsTime = $pingStatistics.ResponsTime $pingHistory[$ipAddress].Result = $pingStatistics.Result $pingHistory[$ipAddress].ResultHistory += $pingStatistics.ResultHistory } else { $pingStatistics | Add-Member -NotePropertyName SuccessCount -NotePropertyValue 0 $pingStatistics | Add-Member -NotePropertyName FailCount -NotePropertyValue 0 $pingHistory.Add($ipAddress, $pingStatistics) } # Track cumulative success/failure counts for the end-of-run summary if ($pingStatistics.Result -eq "Success") { $pingHistory[$ipAddress].SuccessCount++ } else { $pingHistory[$ipAddress].FailCount++ } } # Close and dispose of the runspace pool $pool.Close() $pool.Dispose() # Check for DownTime foreach ($item in $pingHistory.Values) { $timeStamp = Get-Date if ($item.Result -eq "TimedOut") { if ($item.DownTimeStart) { $item.DownTime += ($timeStamp - $item.DownTimeStart).TotalSeconds $item.DownTimeStart = $timeStamp } else { $item.DownTimeStart = $timeStamp if ($logEvents) { Write-Log -Message "$($item.IPAddress) went down" -Level Warning -fileNamePrefix "Ping-IpList" } } } else { if ($logEvents -and $item.DownTimeStart) { Write-Log -Message "$($item.IPAddress) is back online" -Level Info -fileNamePrefix "Ping-IpList" } $item.DownTimeStart = $null } } # By default only currently responding hosts are displayed, so a large CIDR/range # scan isn't drowned out by timeouts. -ShowAll restores the full list. $displayItems = if ($ShowAll) { $pingHistory.Values } else { @($pingHistory.Values | Where-Object { $_.Result -eq "Success" }) } if (-not $OutToPipe) { Clear-Host $successCount = ($pingHistory.Values | Where-Object { $_.Result -eq "Success" }).Count $totalCount = $pingHistory.Values.Count $color = if ($successCount -eq $totalCount) { 'Green' } elseif ($successCount -eq 0) { 'Red' } else { 'Yellow' } Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), Ping sequence: $($iCount + 1), Responding hosts: " -NoNewline Write-Host "$successCount/$totalCount" -ForegroundColor $color if (-not $ShowAll) { Write-Host "(showing responding hosts only, use -ShowAll to include timeouts)" -ForegroundColor DarkGray } if ($ShowHistory) { foreach ($item in $displayItems) { # $paddingSize = 20 - $item.IPAddress.length # if ($paddingSize -lt 0) { $paddingSize = 0 } Write-Host -ForegroundColor Green -NoNewline "$($item.IPAddress) [" Write-Host -NoNewline "t:"#.PadLeft($paddingSize, " ") Write-Host -ForegroundColor Green -NoNewline "$($item.ResponsTime)ms " Write-Host -NoNewline "DownFor:" Write-Host -ForegroundColor Green -NoNewline "$([math]::Round($item.DownTime,2))s]:" if ($item.ResultHistory.EndsWith("..")) { Write-Host -ForegroundColor Red "$($item.ResultHistory)" } elseif ($item.ResultHistory -like "*.*") { Write-Host -ForegroundColor Yellow "$($item.ResultHistory)" } else { Write-Host "$($item.ResultHistory)" } } } else { Write-Output -InputObject $displayItems | Select-Object IPAddress, ResponsTime, Result, DownTime | Format-Table -RepeatHeader -AutoSize } } else { Write-Host "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), Ping sequence: $($iCount + 1)" if ($iCount -eq ($Count - 1)) { Write-Output -InputObject $displayItems | Select-Object IPAddress, ResponsTime, Result, ResultHistory, DownTime } } $iCount++ Start-Sleep -Seconds 1 } } finally { # Runs both when the loop finishes normally (reached -Count) and when interrupted # with Ctrl+C, so cleanup and the summary always happen. if ($pool) { try { if ($pool.RunspacePoolStateInfo.State -ne 'Closed') { $pool.Close() } } catch {} try { $pool.Dispose() } catch {} } if ($pingHistory -and $pingHistory.Count -gt 0) { $endTime = Get-Date $duration = $endTime - $scriptStartTime Write-Host "" Write-Host "==================== Ping Summary ====================" -ForegroundColor Cyan Write-Host "Started : $($scriptStartTime.ToString('yyyy-MM-dd HH:mm:ss'))" Write-Host "Ended : $($endTime.ToString('yyyy-MM-dd HH:mm:ss'))" Write-Host "Duration: $($duration.ToString('hh\:mm\:ss')) Sequences: $iCount" Write-Host "" foreach ($item in $pingHistory.Values) { $sent = $item.SuccessCount + $item.FailCount $successRate = if ($sent -gt 0) { [math]::Round(($item.SuccessCount / $sent) * 100, 1) } else { 0 } $rateColor = if ($successRate -eq 100) { 'Green' } elseif ($successRate -eq 0) { 'Red' } else { 'Yellow' } Write-Host ("{0,-16}" -f $item.IPAddress) -NoNewline Write-Host ("Sent:{0,-5} " -f $sent) -NoNewline Write-Host "Success:" -NoNewline Write-Host ("{0,-5} " -f $item.SuccessCount) -ForegroundColor Green -NoNewline Write-Host "Failed:" -NoNewline Write-Host ("{0,-5} " -f $item.FailCount) -ForegroundColor Red -NoNewline Write-Host ("Rate:{0,6}% " -f $successRate) -ForegroundColor $rateColor -NoNewline Write-Host ("DownTime:{0}s" -f [math]::Round($item.DownTime, 2)) } Write-Host "========================================================" -ForegroundColor Cyan } } } |