Actions

Powershell: Ping Range

From Rabbi Blog

Revision as of 19:58, 6 August 2026 by Rabbi Bob (talk | contribs) (Created page with "Category:Powershell Category:Weblog-2026-08 Not an original script. =Usage= <pre> .\Test-IPRange-Threaded.ps1 -startIP "192.168.1.1" -endIP "192.168.1.50" </pre> =Script= <pre> # 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 range...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)


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