Public/Measure-LissWindowsNetworkReliability.ps1

function Measure-LissWindowsNetworkReliability {
    <#
    .SYNOPSIS
    Measures bounded intermittent packet loss and latency.
    .DESCRIPTION
    Samples one or more targets with System.Net.NetworkInformation.Ping, calculates loss and latency summaries, and captures adapter counters before and after. It makes no configuration changes and enforces a maximum duration of one hour.
    .PARAMETER Target
    One or more diagnostic targets.
    .PARAMETER DurationSeconds
    Total sampling duration, from 1 through 3600 seconds.
    .PARAMETER IntervalMilliseconds
    Delay between sampling rounds, from 50 through 60000 milliseconds.
    .PARAMETER TimeoutMilliseconds
    Timeout for each ICMP sample, from 100 through 10000 milliseconds.
    .PARAMETER IncludeRawSamples
    Includes each individual ICMP result in the returned object.
    .EXAMPLE
    Measure-LissWindowsNetworkReliability -Target 1.1.1.1,8.8.8.8 -DurationSeconds 30 -IncludeRawSamples
    #>

    [CmdletBinding()]
    [OutputType('LISSTech.WindowsNetworkDiagnostics.ReliabilityResult')]
    param(
        [Parameter(Mandatory)][ValidateNotNullOrEmpty()][ValidateCount(1, 16)][string[]]$Target,
        [ValidateRange(1, 3600)][int]$DurationSeconds = 60,
        [ValidateRange(50, 60000)][int]$IntervalMilliseconds = 1000,
        [ValidateRange(100, 10000)][int]$TimeoutMilliseconds = 1000,
        [switch]$IncludeRawSamples
    )

    $time = Get-LissTimeContext
    $before = Invoke-LissDataSource { Get-LissAdapterStatisticsSnapshot }
    $sampleByTarget = @{}
    foreach ($targetName in $Target) {
        $sampleByTarget[$targetName] = New-Object 'Collections.Generic.List[object]'
    }
    $deadline = [DateTime]::UtcNow.AddSeconds($DurationSeconds)
    do {
        foreach ($targetName in $Target) {
            $sampleByTarget[$targetName].Add((Invoke-LissPingSample -Target $targetName -TimeoutMilliseconds $TimeoutMilliseconds))
        }
        $remaining = ($deadline - [DateTime]::UtcNow).TotalMilliseconds
        if ($remaining -gt 0) {
            Start-Sleep -Milliseconds ([int][Math]::Min($IntervalMilliseconds, $remaining))
        }
    } while ([DateTime]::UtcNow -lt $deadline)
    $after = Invoke-LissDataSource { Get-LissAdapterStatisticsSnapshot }
    $results = foreach ($targetName in $Target) {
        $samples = $sampleByTarget[$targetName].ToArray()
        $summary = Get-LissLatencySummary -Sample $samples -Target $targetName
        [pscustomobject]@{
            Target                   = $summary.Target
            Sent                     = $summary.Sent
            Received                 = $summary.Received
            LossPercentage           = $summary.LossPercentage
            MinimumMilliseconds      = $summary.MinimumMilliseconds
            AverageMilliseconds      = $summary.AverageMilliseconds
            Percentile95Milliseconds = $summary.Percentile95Milliseconds
            MaximumMilliseconds      = $summary.MaximumMilliseconds
            RawSamples               = if ($IncludeRawSamples) { $samples } else { @() }
        }
    }
    [pscustomobject]@{
        PSTypeName              = 'LISSTech.WindowsNetworkDiagnostics.ReliabilityResult'
        StartedAt               = $time.Timestamp
        StartedAtUtc            = $time.TimestampUtc
        CompletedAt             = Get-Date
        TimeZoneId              = $time.TimeZoneId
        DurationSeconds         = $DurationSeconds
        IntervalMilliseconds    = $IntervalMilliseconds
        TimeoutMilliseconds     = $TimeoutMilliseconds
        Results                 = @($results)
        AdapterStatisticsBefore = $before
        AdapterStatisticsAfter  = $after
    }
}