Powershell: Ping Range
From Mike Beane's Blog
Not an original script.
Usage
.\Test-IPRange-Threaded.ps1 -startIP "192.168.1.1" -endIP "192.168.1.50"
Script
# 10.101.50.70 255.255.0.0
<#
.SYNOPSIS
Pings a range of IP addresses concurrently to check their online status.
.DESCRIPTION
This script takes a start IP address and an end IP address. It iterates through
each IP address in the specified range (primarily designed for ranges within
the same /24 subnet) and sends two ping requests to each IP concurrently
using PowerShell runspaces for improved efficiency.
- If an address responds to one or more pings, it's marked as "Online".
- If an address does not respond to either ping, it's marked as "Offline".
- Errors during a specific IP ping are marked as "Error".
.PARAMETER startIP
The starting IP address of the range (e.g., "192.168.1.1"). This is a mandatory parameter.
.PARAMETER endIP
The ending IP address of the range (e.g., "192.168.1.254"). This is a mandatory parameter.
.PARAMETER ThrottleLimit
The maximum number of concurrent ping operations. Default is 30.
Adjust this based on your system's resources and network considerations.
.EXAMPLE
.\Test-IPRange-Threaded.ps1 -startIP "192.168.1.1" -endIP "192.168.1.50"
This command will ping IP addresses from 192.168.1.1 to 192.168.1.50 concurrently
(using the default throttle limit), sending two pings to each and reporting their status.
.EXAMPLE
.\Test-IPRange-Threaded.ps1 -startIP "10.0.0.1" -endIP "10.0.0.254" -ThrottleLimit 50
This command will ping IPs in the 10.0.0.x range with up to 50 concurrent operations.
.NOTES
- This script uses PowerShell Runspace Pools for threading, compatible with PowerShell 5.1+.
- For PowerShell 7.0 and newer, ForEach-Object -Parallel offers a simpler syntax for parallel execution.
- The script assumes the IP range is primarily within the same /24 subnet. It uses the first
three octets of 'startIP' as the base network prefix.
- If 'startIP' and 'endIP' have different network prefixes, a warning is displayed, and the scan
proceeds using 'startIP's' network prefix.
- To run this script:
1. Save it as a .ps1 file (e.g., Test-IPRange-Threaded.ps1).
2. Open PowerShell.
3. Navigate to the directory where you saved the file.
4. If needed, adjust your execution policy: Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
5. Run: .\Test-IPRange-Threaded.ps1 -startIP "YOUR_START_IP" -endIP "YOUR_END_IP" [-ThrottleLimit DESIRED_NUMBER]
#>
param (
[Parameter(Mandatory=$true, HelpMessage="Enter the starting IP address (e.g., 192.168.1.1)")]
[string]$startIP,
[Parameter(Mandatory=$true, HelpMessage="Enter the ending IP address (e.g., 192.168.1.254)")]
[string]$endIP,
[Parameter(HelpMessage="Maximum number of concurrent ping operations. Default is 30.")]
[ValidateRange(1,200)] # Practical limit for throttle
[int]$ThrottleLimit = 30
)
# --- Script Body ---
Write-Host "🚀 Initializing threaded ping scan..." -ForegroundColor Yellow
# Basic IP address format validation (IPv4)
$ipPattern = '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
if (-not ($startIP -match $ipPattern)) {
Write-Error "Invalid format for Start IP: '$startIP'. Please use a valid IPv4 format (e.g., 192.168.1.1)."
exit 1
}
if (-not ($endIP -match $ipPattern)) {
Write-Error "Invalid format for End IP: '$endIP'. Please use a valid IPv4 format (e.g., 192.168.1.254)."
exit 1
}
# Parse IP addresses
try {
$startAddressBytes = [System.Net.IPAddress]::Parse($startIP).GetAddressBytes()
$endAddressBytes = [System.Net.IPAddress]::Parse($endIP).GetAddressBytes()
} catch {
Write-Error "Failed to parse one or both IP addresses. Ensure they are valid IPv4 addresses."
Write-Error "Error details: $($_.Exception.Message)"
exit 1
}
$networkPrefix = "$($startAddressBytes[0]).$($startAddressBytes[1]).$($startAddressBytes[2])"
$startHostOctet = [int]$startAddressBytes[3]
$endHostOctetLoopEnd = [int]$endAddressBytes[3]
if (($startAddressBytes[0] -ne $endAddressBytes[0]) -or `
($startAddressBytes[1] -ne $endAddressBytes[1]) -or `
($startAddressBytes[2] -ne $endAddressBytes[2])) {
Write-Warning "The Start IP ($startIP) and End IP ($endIP) do not share the same first three octets."
Write-Warning "This script will use the network prefix of the Start IP ('$networkPrefix.x') and iterate the last octet from $startHostOctet to $endHostOctetLoopEnd."
}
if ($startHostOctet -gt $endHostOctetLoopEnd) {
Write-Error "The last octet of Start IP ($startHostOctet) is greater than the last octet of End IP ($endHostOctetLoopEnd) for the determined network prefix '$networkPrefix.x'. This forms an invalid range for this script's logic."
exit 1
}
# Generate list of IPs to test
$ipsToTest = [System.Collections.Generic.List[string]]::new()
for ($currentHostOctet = $startHostOctet; $currentHostOctet -le $endHostOctetLoopEnd; $currentHostOctet++) {
$ipsToTest.Add("$networkPrefix.$currentHostOctet")
}
if ($ipsToTest.Count -eq 0) {
Write-Warning "No IP addresses generated for the given range. Exiting."
exit 0
}
$fullStartRangeIPToScan = $ipsToTest[0]
$fullEndRangeIPToScan = $ipsToTest[$ipsToTest.Count -1]
Write-Host "Scanning IP addresses from $fullStartRangeIPToScan to $fullEndRangeIPToScan."
Write-Host "Using up to $ThrottleLimit concurrent threads."
Write-Host "Each IP will be pinged twice."
Write-Host "--------------------------------------------------"
# Define the script block to be executed in each thread
$scriptBlock = {
param($targetIPToPing)
# Test-Connection sends ICMP echo requests.
$pingReplies = Test-Connection -ComputerName $targetIPToPing -Count 2 -ErrorAction SilentlyContinue -WarningAction SilentlyContinue
if ($pingReplies) {
return [PSCustomObject]@{
IPAddress = $targetIPToPing
Status = "Online"
Timestamp = Get-Date
}
} else {
return [PSCustomObject]@{
IPAddress = $targetIPToPing
Status = "Offline"
Timestamp = Get-Date
}
}
}
# Initialize Runspace Pool
$runspacePool = [runspacefactory]::CreateRunspacePool(1, $ThrottleLimit)
$runspacePool.Open()
$activeJobs = [System.Collections.Generic.List[object]]::new()
$finalResults = [System.Collections.Generic.List[object]]::new()
try {
foreach ($ip in $ipsToTest) {
$powershell = [powershell]::Create().AddScript($scriptBlock).AddArgument($ip)
$powershell.RunspacePool = $runspacePool
$activeJobs.Add([PSCustomObject]@{
IP = $ip
Pipe = $powershell
AsyncHandle = $powershell.BeginInvoke()
})
}
$totalIPs = $ipsToTest.Count
$processedCount = 0
# Monitor and process completed jobs
while ($activeJobs.Count -gt 0) {
# Create a temporary list of jobs that have completed in this iteration
$completedThisIteration = [System.Collections.Generic.List[object]]::new()
foreach ($jobHandle in $activeJobs) {
if ($jobHandle.AsyncHandle.IsCompleted) {
$completedThisIteration.Add($jobHandle)
try {
$resultObject = $jobHandle.Pipe.EndInvoke($jobHandle.AsyncHandle)
if ($resultObject) {
$finalResults.Add($resultObject)
}
} catch {
Write-Warning "Error processing IP $($jobHandle.IP): $($_.Exception.Message)"
$finalResults.Add([PSCustomObject]@{
IPAddress = $jobHandle.IP
Status = "Error"
Message = $_.Exception.Message
Timestamp = Get-Date
})
} finally {
$jobHandle.Pipe.Dispose()
$processedCount++
}
}
}
# Remove all completed jobs from the active list
if ($completedThisIteration.Count -gt 0) {
$completedThisIteration | ForEach-Object { $activeJobs.Remove($_) }
}
if ($activeJobs.Count -gt 0) {
Write-Progress -Activity "Pinging IPs" -Status "Processing... ($processedCount/$totalIPs completed)" -PercentComplete ([math]::Round(($processedCount / $totalIPs) * 100)) -CurrentOperation "$($activeJobs.Count) tasks remaining"
Start-Sleep -Milliseconds 200 # Wait a bit before checking again
}
}
Write-Progress -Activity "Pinging IPs" -Completed
}
finally {
# Close and dispose the runspace pool
if ($runspacePool) {
$runspacePool.Close()
$runspacePool.Dispose()
}
Write-Host "Runspace pool closed." -ForegroundColor DarkGray
}
# Sort and display all collected results
Write-Host "`n--- Scan Results (Sorted by IP) ---" -ForegroundColor Yellow
$sortedResults = $finalResults | Sort-Object { [System.Net.IPAddress]$_.IPAddress }
foreach ($result in $sortedResults) {
switch ($result.Status) {
"Online" { Write-Host "> $($result.IPAddress) - $($result.Status) ✅" -ForegroundColor Green }
#"Offline" { Write-Host "> $($result.IPAddress) - $($result.Status) ❌" -ForegroundColor Red }
#"Error" { Write-Host "> $($result.IPAddress) - $($result.Status) ⚠️ (Message: $($result.Message))" -ForegroundColor Magenta }
#default { Write-Host "> $($result.IPAddress) - Status Unknown" }
}
}
Write-Host "--------------------------------------------------"
Write-Host "🎉 Threaded ping scan finished." -ForegroundColor Yellow