Public/network/Watch-PingStatus.ps1

#Requires -Version 5.1

function Watch-PingStatus {
    <#
    .SYNOPSIS
        Displays a real-time multi-host ping monitoring dashboard.
    .DESCRIPTION
        Continuously pings multiple hosts and displays a live-updating table
        showing status (Up/Down), response time, and packet loss statistics.
 
        Similar to a NOC dashboard. Press Ctrl+C to stop and display final statistics.
 
        This is an interactive display function that writes directly to the console.
        It does not output objects to the pipeline.
    .PARAMETER ComputerName
        One or more hostnames or IP addresses to monitor.
    .PARAMETER RefreshInterval
        Refresh interval in seconds. Default: 2. Valid range: 1-60.
    .PARAMETER PingTimeoutMs
        Timeout per ping in milliseconds. Default: 2000. Valid range: 500-10000.
    .EXAMPLE
        Watch-PingStatus -ComputerName 'SRV01', 'SRV02', 'SRV03', 'gateway'
 
        Monitors 4 hosts with a live dashboard. Press Ctrl+C to stop.
    .EXAMPLE
        Watch-PingStatus -ComputerName (Get-Content servers.txt) -RefreshInterval 5
 
        Monitors hosts from a file with 5-second refresh.
    .EXAMPLE
        Watch-PingStatus -ComputerName '8.8.8.8', '1.1.1.1', 'gateway.local' -PingTimeoutMs 1000
 
        Monitors with a 1-second ping timeout.
    .NOTES
        Author: Franck SALLET
        Version: 1.0.0
        Last Modified: 2026-03-21
        Requires: PowerShell 5.1+ / Windows only
        Permissions: No admin required (ICMP may be blocked by firewall)
        Output: Writes to console only, no pipeline output.
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '',
        Justification = 'Write-Host is intentional for interactive console dashboard display')]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '',
        Justification = 'Watch is the most intuitive verb for continuous monitoring dashboard')]
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateNotNullOrEmpty()]
        [Alias('CN', 'Name', 'DNSHostName')]
        [string[]]$ComputerName,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 60)]
        [int]$RefreshInterval = 2,

        [Parameter(Mandatory = $false)]
        [ValidateRange(500, 10000)]
        [int]$PingTimeoutMs = 2000
    )

    begin {
        Write-Verbose "[$($MyInvocation.MyCommand)] Starting ping dashboard for $($ComputerName.Count) host(s)"

        # Initialize stats per host
        $hostStats = @{}
        foreach ($targetHost in $ComputerName) {
            $hostStats[$targetHost] = @{
                Sent     = 0
                Received = 0
                LastMs   = $null
                MinMs    = [double]::MaxValue
                MaxMs    = 0.0
                SumMs    = 0.0
                Status   = 'Pending'
            }
        }

        $pingSender = New-Object System.Net.NetworkInformation.Ping
        $buffer = [byte[]]::new(32)
        $pingOptions = New-Object System.Net.NetworkInformation.PingOptions(128, $true)
        $startTime = Get-Date
    }

    process {
        try {
            while ($true) {
                # Ping all hosts
                foreach ($target in $ComputerName) {
                    $stats = $hostStats[$target]
                    $stats.Sent++

                    try {
                        $reply = $pingSender.Send($target, $PingTimeoutMs, $buffer, $pingOptions)
                        if ($reply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) {
                            $stats.Received++
                            $stats.LastMs = $reply.RoundtripTime
                            $stats.SumMs += $reply.RoundtripTime
                            if ($reply.RoundtripTime -lt $stats.MinMs) { $stats.MinMs = $reply.RoundtripTime }
                            if ($reply.RoundtripTime -gt $stats.MaxMs) { $stats.MaxMs = $reply.RoundtripTime }
                            $stats.Status = 'Up'
                        } else {
                            $stats.LastMs = $null
                            $stats.Status = 'Down'
                        }
                    } catch {
                        $stats.LastMs = $null
                        $stats.Status = 'Down'
                    }
                }

                # Render dashboard
                Clear-Host
                $elapsed = (Get-Date) - $startTime
                $elapsedStr = '{0:hh\:mm\:ss}' -f $elapsed
                Write-Host "=== Ping Monitor === $($ComputerName.Count) host(s) === Elapsed: $elapsedStr === Refresh: ${RefreshInterval}s === Ctrl+C to stop ===" -ForegroundColor Cyan
                Write-Host ''

                # Table header
                $headerFmt = '{0,-28} {1,-8} {2,8} {3,8} {4,8} {5,8} {6,8} {7,8}'
                Write-Host ($headerFmt -f 'Host', 'Status', 'Last', 'Min', 'Avg', 'Max', 'Loss%', 'Sent') -ForegroundColor White
                Write-Host ($headerFmt -f '----', '------', '----', '---', '---', '---', '-----', '----') -ForegroundColor DarkGray

                foreach ($target in $ComputerName) {
                    $stats = $hostStats[$target]
                    $lossPercent = if ($stats.Sent -gt 0) { [math]::Round((($stats.Sent - $stats.Received) / $stats.Sent) * 100, 1) } else { 0 }
                    $avgMs = if ($stats.Received -gt 0) { [math]::Round($stats.SumMs / $stats.Received, 1) } else { $null }
                    $minDisplay = if ($stats.MinMs -lt [double]::MaxValue) { '{0}ms' -f [math]::Round($stats.MinMs, 0) } else { '-' }
                    $maxDisplay = if ($stats.MaxMs -gt 0) { '{0}ms' -f [math]::Round($stats.MaxMs, 0) } else { '-' }
                    $avgDisplay = if ($avgMs) { '{0}ms' -f $avgMs } else { '-' }
                    $lastDisplay = if ($null -ne $stats.LastMs) { '{0}ms' -f $stats.LastMs } else { '-' }

                    $statusColor = switch ($stats.Status) {
                        'Up'      { 'Green' }
                        'Down'    { 'Red' }
                        'Pending' { 'Yellow' }
                        default   { 'White' }
                    }

                    $lossColor = if ($lossPercent -eq 0) { 'Green' } elseif ($lossPercent -lt 10) { 'Yellow' } else { 'Red' }

                    Write-Host ('{0,-28} ' -f $target) -NoNewline
                    Write-Host ('{0,-8} ' -f $stats.Status) -ForegroundColor $statusColor -NoNewline
                    Write-Host ('{0,8} {1,8} {2,8} {3,8} ' -f $lastDisplay, $minDisplay, $avgDisplay, $maxDisplay) -NoNewline
                    Write-Host ('{0,8} ' -f "$lossPercent%") -ForegroundColor $lossColor -NoNewline
                    Write-Host ('{0,8}' -f $stats.Sent)
                }

                Write-Host ''
                $upCount = @($ComputerName | Where-Object { $hostStats[$_].Status -eq 'Up' }).Count
                $downCount = $ComputerName.Count - $upCount
                Write-Host "Summary: " -NoNewline
                Write-Host "$upCount Up" -ForegroundColor Green -NoNewline
                Write-Host ' / ' -NoNewline
                if ($downCount -gt 0) {
                    Write-Host "$downCount Down" -ForegroundColor Red
                } else {
                    Write-Host '0 Down' -ForegroundColor Green
                }

                Start-Sleep -Seconds $RefreshInterval
            }
        } catch {
            Write-Verbose "[$($MyInvocation.MyCommand)] Dashboard interrupted: $_"
        } finally {
            $pingSender.Dispose()

            # Final summary
            Write-Host ''
            Write-Host '=== Final Statistics ===' -ForegroundColor Cyan
            foreach ($target in $ComputerName) {
                $stats = $hostStats[$target]
                $lossPercent = if ($stats.Sent -gt 0) { [math]::Round((($stats.Sent - $stats.Received) / $stats.Sent) * 100, 1) } else { 0 }
                $avgMs = if ($stats.Received -gt 0) { [math]::Round($stats.SumMs / $stats.Received, 1) } else { 0 }
                Write-Host " $target - Sent: $($stats.Sent), Received: $($stats.Received), Loss: ${lossPercent}%, Avg: ${avgMs}ms"
            }
            Write-Host ''
            Write-Host 'Ping Monitor stopped.' -ForegroundColor Cyan
        }
    }
}