FastPing.psm1

<#
    .SYNOPSIS
    Performs a series of asynchronous pings against a set of target hosts.

    .DESCRIPTION
    This function uses System.Net.Networkinformation.Ping object to perform
    a series of asynchronous pings against a set of target hosts.

    .PARAMETER HostName
    A string array of target hosts to ping.

    .PARAMETER RoundtripAveragePingCount
    The number of pings to send against each host for calculating the Roundtrip average. Defaults to 4.

    .PARAMETER WaitTimeMilliseconds
    The time in milliseconds to wait for each ping to complete. Defaults to 500.

    .EXAMPLE
    Invoke-FastPing -HostName 'andrewpearce.io'

    HostName RoundtripAverage Online
    -------- ---------------- ------
    andrewpearce.io 22 True

    .EXAMPLE
    Invoke-FastPing -HostName 'andrewpearce.io','doesnotexist.andrewpearce.io'

    HostName RoundtripAverage Online
    -------- ---------------- ------
    doesnotexist.andrewpearce.io False
    andrewpearce.io 22 True
#>

function Invoke-FastPing
{
    [alias('FastPing', 'fping', 'fp')]
    param
    (
        [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias('Computer', 'ComputerName', 'Host')]
        [String[]] $HostName,

        [Int] $RoundtripAveragePingCount = 4,

        [Int] $WaitTimeMilliseconds = 500
    )

    process
    {
        # Objects to hold items as we process pings
        $queue = [System.Collections.Queue]::new()
        $pingHash = @{}

        # Start an asynchronous ping against each computer
        foreach ($hn in $HostName)
        {
            if ($pingHash.Keys -notcontains $hn)
            {
                $pingHash.Add($hn, [System.Collections.ArrayList]::new())
            }

            for ($i = 0; $i -lt $RoundtripAveragePingCount; $i++)
            {
                $ping = [System.Net.Networkinformation.Ping]::new()
                $object = @{
                    Host  = $hn
                    Ping  = $ping
                    Async = $ping.SendPingAsync($hn)
                }
                $queue.Enqueue($object)
            }
        }

        # Process the asynchronous pings
        while ($queue.Count -gt 0)
        {
            $object = $queue.Dequeue()

            try
            {
                # Wait for completion
                if ($object.Async.Wait($WaitTimeMilliseconds) -eq $true)
                {
                    [Void]$pingHash[$object.Host].Add(@{
                            Host          = $object.Host
                            RoundtripTime = $object.Async.Result.RoundtripTime
                            Status        = $object.Async.Status
                        })
                    continue
                }
            }
            catch
            {
                if ($object.Async.IsCompleted -eq $true)
                {
                    [Void]$pingHash[$object.Host].Add(@{
                            Host          = $object.Host
                            RoundtripTime = $object.Async.Result.RoundtripTime
                            Status        = $object.Async.Status
                        })
                    continue
                }
                else
                {
                    Write-Warning -Message $_.Exception.Message
                }
            }

            $queue.Enqueue($object)
        }

        # Using the ping results in pingHash, calculate the average RoundtripTime
        foreach ($key in $pingHash.Keys)
        {
            if (($pingHash.$key.Status | Select-Object -Unique) -eq 'RanToCompletion')
            {
                $online = $true
            }
            else
            {
                $online = $false
            }

            if ($online -eq $true)
            {
                $latency = [System.Collections.ArrayList]::new()
                foreach ($value in $pingHash.$key)
                {
                    if ($value.RoundtripTime)
                    {
                        [Void]$latency.Add($value.RoundtripTime)
                    }
                }

                $average = $latency | Measure-Object -Average
                if ($average.Average)
                {
                    $roundtripAverage = [Math]::Round($average.Average, 0)
                }
                else
                {
                    $roundtripAverage = $null
                }
            }
            else
            {
                $roundtripAverage = $null
            }

            [PSCustomObject]@{
                HostName         = $key
                RoundtripAverage = $roundtripAverage
                Online           = $online
            }
        } # End result processing

    } # End Process
}