Skip to main content

How to Monitor Packet Loss Over Time in Batch Script

Packet loss is the ultimate "silent killer" of network performance. Unlike a total outage, packet loss causes stuttering Zoom calls, slow file transfers, and frequent web page timeouts. Detecting a single failed ping isn't enough; you need to know if your network is losing 1% or 20% of its data over several hours. A Batch script can perform a "stress test" by sending hundreds of pings and capturing the final summary statistics provided by the ping command, allowing you to prove network instability to your provider.

This guide will explain how to monitor and log packet loss percentages.

Method 1: The "Bulk Statistics" Scan

This method sends 100 pings to a host and captures the final summary statistics from the ping output.

@echo off
setlocal enabledelayedexpansion

set "Target=8.8.8.8"
set "Count=100"
set "LogFile=%USERPROFILE%\packet_loss_audit.csv"
set "TempFile=%TEMP%\ping_result.tmp"

echo [TEST] Monitoring %Target% for Packet Loss (%Count% packets)...
echo This will take approximately %Count% seconds.
echo.

:: Run the full ping and capture output to a temp file
ping -n %Count% %Target% > "%TempFile%" 2>&1

:: Extract the summary line containing loss statistics
:: Format varies by locale, so we parse multiple patterns
set "sent="
set "received="
set "lost="
set "pct_lost="

:: Try parsing English format: "Sent = X, Received = X, Lost = X (Y% loss)"
for /f "tokens=*" %%L in ('findstr /i "Lost" "%TempFile%" 2^>nul') do (
set "summaryLine=%%L"
)

:: Extract individual values using PowerShell for reliable parsing
:: This handles all locale formats
for /f "delims=" %%p in ('powershell -NoProfile -Command ^
"$lines = Get-Content '%TempFile%';"^
"$stats = $lines | Where-Object { $_ -match '\d+%%' } | Select-Object -Last 1;"^
"if ($stats -match '(\d+).*?(\d+).*?(\d+).*?\((\d+)%%') {"^
" Write-Output (\"$($Matches[1]),$($Matches[2]),$($Matches[3]),$($Matches[4])\")"^
"} else { Write-Output 'ERROR' }"') do set "parsed=%%p"

if "!parsed!"=="ERROR" (
echo [ERROR] Could not parse ping statistics. Raw output:
type "%TempFile%"
del "%TempFile%" >nul 2>&1
pause
endlocal
exit /b 1
)

:: Split parsed values
for /f "tokens=1-4 delims=," %%a in ("!parsed!") do (
set "sent=%%a"
set "received=%%b"
set "lost=%%c"
set "pct_lost=%%d"
)

echo ------------------------------------------
echo PACKET LOSS REPORT: !date! !time!
echo ------------------------------------------
echo Target: %Target%
echo Packets Sent: !sent!
echo Received: !received!
echo Lost: !lost! (!pct_lost!%%)
echo ------------------------------------------

:: Interpret the result
if !pct_lost! equ 0 (
echo [OK] No packet loss detected.
) else if !pct_lost! leq 2 (
echo [INFO] Minor packet loss - generally acceptable.
) else if !pct_lost! leq 10 (
echo [WARNING] Moderate packet loss - may affect calls and streaming.
) else (
echo [CRITICAL] Severe packet loss - network is unstable!
)

echo.

:: Log to CSV
if not exist "%LogFile%" echo Date,Time,Target,Sent,Received,Lost,LossPercent >> "%LogFile%"
echo !date!,!time!,%Target%,!sent!,!received!,!lost!,!pct_lost!%% >> "%LogFile%"
echo [LOGGED] Results appended to: %LogFile%

:: Clean up
del "%TempFile%" >nul 2>&1

pause
endlocal

:::note[Why use PowerShell for parsing?** The ping summary format changes between Windows languages. English shows Lost = 0 (0% loss), but German shows Verloren = 0 (0% Verlust), French shows perdus = 0 (perte 0%), etc. The regex-based approach matches the numeric pattern regardless of the surrounding text, making the script work on any locale.

Method 2: Detecting High-Latency Spikes (The Jitter Check)

Packet loss often goes hand-in-hand with "jitter" (unstable delay). This script monitors continuously and logs whenever a ping exceeds a configurable threshold.

@echo off
setlocal enabledelayedexpansion

set "Host=8.8.8.8"
set "Threshold=200"
set "LogFile=%USERPROFILE%\jitter_log.csv"
set "Interval=1"
set "SpikeCount=0"
set "TotalCount=0"

echo [MONITOR] Watching %Host% for latency spikes above %Threshold%ms...
echo Log: %LogFile%
echo Press CTRL+C to stop.
echo.

:: CSV header
if not exist "%LogFile%" echo Date,Time,Target,Latency_ms,Status >> "%LogFile%"

:Loop
set /a TotalCount+=1
set "latency=TIMEOUT"
set "status=FAIL"

:: Extract latency from ping reply
for /f "delims=" %%L in ('ping -n 1 -w 1000 %Host% 2^>nul ^| find "TTL="') do (
set "line=%%L"
set "status=OK"

:: 1. Remove everything up to "time="
set "temp=!line:*time=!"

:: 2. Handle cases where ping is <1ms (ping uses 'time<1ms' instead of 'time=1ms')
if "!temp!"=="!line!" (
set "temp=!line:*time<!"
)

:: 3. Extract only the numeric part before "ms" or the first space
for /f "tokens=1 delims=ms " %%v in ("!temp!") do (
set "latency=%%v"
)
)

:: Check for spike
if "!status!"=="OK" (
:: Ensure latency is a number before comparing
if !latency! gtr %Threshold% (
set /a SpikeCount+=1
echo [!time!] [SPIKE] !latency!ms - exceeds %Threshold%ms threshold
echo !date!,!time!,%Host%,!latency!,SPIKE >> "%LogFile%"
) else (
echo [!time!] OK: !latency!ms
)
) else (
set /a SpikeCount+=1
echo [!time!] [TIMEOUT] No response from %Host%
echo !date!,!time!,%Host%,TIMEOUT,FAIL >> "%LogFile%"
)

timeout /t %Interval% >nul
goto :Loop
What is jitter?

Jitter is the variation in latency over time. If your pings normally return in 15ms but occasionally spike to 400ms, that inconsistency causes audio glitches in VoIP calls and buffering in video streams, even though the average looks acceptable.

Method 3: Continuous Packet Loss Dashboard

A persistent monitor that runs a batch of pings at regular intervals and displays a rolling summary.

@echo off
setlocal enabledelayedexpansion

set "Host=8.8.8.8"
set "BatchSize=20"
set "Interval=60"
set "LogFile=%USERPROFILE%\packetloss_dashboard.csv"
set "Round=0"

echo [DASHBOARD] Continuous Packet Loss Monitor
echo Target: %Host%
echo Batch Size: %BatchSize% pings per round
echo Interval: %Interval% seconds between rounds
echo Press CTRL+C to stop.
echo.

:: CSV header - Removed the % from the header to keep it as a numeric column for Excel
if not exist "%LogFile%" echo Date,Time,Target,Sent,LossPercent >> "%LogFile%"

:Loop
set /a Round+=1
set "pct=UNKNOWN"

echo ------------------------------------------
echo Round !Round! - %time%

:: IMPROVED PowerShell logic:
:: 1. Runs ping
:: 2. Finds the line containing '%'
:: 3. Uses a Regular Expression to grab only the digits immediately preceding the '%' sign
for /f %%p in ('powershell -NoProfile -Command ^
"$r = ping -n %BatchSize% -w 1000 %Host%;"^
"$line = $r | Where-Object { $_ -match '\d+%%' } | Select-Object -Last 1;"^
"if ($line -match '(\d+)%%') { $Matches[1] } else { 'ERROR' }"') do set "pct=%%p"

if "!pct!"=="ERROR" (
echo [ERROR] Could not parse ping statistics. Target might be unreachable.
) else (
:: Display with severity
if !pct! equ 0 (
echo [OK] Loss: !pct!%%
) else if !pct! leq 5 (
echo [WARNING] Loss: !pct!%%
) else (
echo [CRITICAL] Loss: !pct!%%
)

:: Log to CSV (Stored as raw number for easy graphing in Excel/Sheets)
echo %date%,%time%,%Host%,%BatchSize%,!pct! >> "%LogFile%"
)

echo Next round in %Interval% seconds...
echo ------------------------------------------
echo.

timeout /t %Interval% >nul
goto :Loop

Method 4: Dual-Target Loss Comparison (Gateway vs. Internet)

To pinpoint whether packet loss is on your local network or your ISP's infrastructure.

@echo off
setlocal enabledelayedexpansion

set "Gateway=192.168.1.1"
set "Internet=8.8.8.8"
set "Count=50"
set "LogFile=%USERPROFILE%\loss_comparison.csv"

echo [AUDIT] Dual-Target Packet Loss Comparison
echo Gateway: %Gateway%
echo Internet: %Internet%
echo Packets: %Count% each
echo.

:: CSV header
if not exist "%LogFile%" echo Date,Time,Gateway,GW_Loss,Internet,INET_Loss,Diagnosis >> "%LogFile%"

:: Test Gateway
echo [1/2] Pinging gateway %Gateway%...
set "gw_loss=ERROR"
for /f %%p in ('powershell -NoProfile -Command ^
"$r = ping -n %Count% -w 1000 %Gateway%;"^
"$line = $r | Where-Object { $_ -match '\d+%%' } | Select-Object -Last 1;"^
"if ($line -match '\((\d+)%%') { $Matches[1] } else { 'ERROR' }"') do set "gw_loss=%%p"

:: Test Internet
echo [2/2] Pinging internet %Internet%...
set "inet_loss=ERROR"
for /f %%p in ('powershell -NoProfile -Command ^
"$r = ping -n %Count% -w 1000 %Internet%;"^
"$line = $r | Where-Object { $_ -match '\d+%%' } | Select-Object -Last 1;"^
"if ($line -match '\((\d+)%%') { $Matches[1] } else { 'ERROR' }"') do set "inet_loss=%%p"

echo.
echo ==========================================
echo PACKET LOSS COMPARISON REPORT
echo %date% %time%
echo ==========================================
echo.

:: Display results
if "!gw_loss!"=="ERROR" (
echo Gateway (%Gateway%): COULD NOT TEST
) else (
echo Gateway (%Gateway%): !gw_loss!%% loss
)

if "!inet_loss!"=="ERROR" (
echo Internet (%Internet%): COULD NOT TEST
) else (
echo Internet (%Internet%): !inet_loss!%% loss
)

echo.

:: Diagnosis
set "diagnosis=UNKNOWN"
if "!gw_loss!" neq "ERROR" if "!inet_loss!" neq "ERROR" (
if !gw_loss! leq 1 if !inet_loss! leq 1 (
set "diagnosis=ALL_CLEAR"
echo [OK] Network is healthy - minimal loss on both targets.
)
if !gw_loss! leq 1 if !inet_loss! gtr 5 (
set "diagnosis=ISP_PROBLEM"
echo [ALERT] Loss is on ISP side - local network is fine.
echo Contact your Internet Service Provider.
)
if !gw_loss! gtr 5 if !inet_loss! gtr 5 (
set "diagnosis=LOCAL_PROBLEM"
echo [ALERT] Loss starts at gateway - local network issue.
echo Check cables, switch, and router.
)
if !gw_loss! gtr 5 if !inet_loss! leq 1 (
set "diagnosis=GATEWAY_ANOMALY"
echo [INFO] Gateway has loss but internet is fine - unusual.
echo Gateway may be deprioritizing ICMP responses.
)
)

echo.
echo ==========================================

:: Log
echo !date!,!time!,%Gateway%,!gw_loss!%%,%Internet%,!inet_loss!%%,!diagnosis! >> "%LogFile%"
echo [LOGGED] Results saved to: %LogFile%

pause
endlocal

How to read the diagnosis:

  • ALL_CLEAR: Less than 1% loss everywhere. Network is healthy.
  • ISP_PROBLEM: Local gateway is fine but internet target has loss. The issue is upstream with your ISP.
  • LOCAL_PROBLEM: Both targets have loss, starting at the gateway. Check your cables, switch, and router.
  • GATEWAY_ANOMALY: Gateway shows loss but internet is fine. Some routers deprioritize ICMP responses under load, this may be a false positive.

How to Avoid Common Errors

Wrong Way: Using Only 4 Packets

A standard ping (4 packets) is too small a sample size. You can lose 1 packet and it shows as "25% loss," which might just be a single random collision.

Correct Way: Use at least 50 to 100 packets for a valid statistical audit. This provides enough data to distinguish between a "glitch" and a "trend."

Wrong Way: Parsing Ping Output with Fixed Token Positions

The ping summary format Lost = X (Y% loss) varies between Windows languages. Hardcoding tokens=4,7 delims=(, works in English but breaks completely on German, French, Spanish, and other localized systems.

Correct Way: Use a regex-based approach (via PowerShell) that matches the numeric pattern (\d+%) regardless of surrounding text:

powershell -NoProfile -Command ^
"$line = ping -n 100 8.8.8.8 | Where-Object { $_ -match '\d+%%' } | Select-Object -Last 1;"^
"if ($line -match '\((\d+)%%') { $Matches[1] }"

Wrong Way: Using cls in a Monitoring Loop

using cls to clear the screen every cycle will destroy all previous output: you can never scroll back to see what happened 10 minutes ago.

Correct Way: Append new results below existing output. Use separators between rounds for readability.

Wrong Way: Missing enabledelayedexpansion in Loops

Variables set inside a goto :Loop construct or FOR block need delayed expansion. Without it, !ms! and !pct_lost! show stale values from before the loop began.

Correct Way: Enable delayed expansion and use !variable! syntax:

setlocal enabledelayedexpansion

Problem: Parsing Percentage in Different Languages

The ping output (0% loss) is different in other languages (e.g., German: (0% Verlust), French: (perte 0%)).

Solution: Use the PowerShell regex approach shown in Methods 1, 3, and 4, which matches the numeric percentage pattern regardless of locale.

Best Practices and Rules

1. Identify the Location of Loss

To pinpoint where loss is happening, test two targets simultaneously (Method 4):

  1. Your local gateway (192.168.1.1)
  2. A public DNS (8.8.8.8)

If the gateway has 0% loss but the public target has 10% loss, the problem is your ISP's infrastructure.

2. Time of Day Matters

Network congestion usually peaks in evenings. Run your script for 24 hours to see if packet loss correlates with high-traffic hours. Method 3's continuous dashboard is designed for this.

3. Log as CSV

Format your log as CSV so you can share it with your ISP support team. Having a spreadsheet that shows "12% loss between 7 PM and 10 PM" is far more effective than saying "the internet is slow."

4. Packet Loss Severity Guide

Loss PercentageSeverityImpact
0%NormalNo issues
1–2%MinorBarely noticeable for browsing
3–5%ModerateVoIP calls may stutter, video may buffer
5–10%SevereSignificant degradation across all services
10%+CriticalEffectively unusable for real-time applications

5. Log File Location

Avoid using %CD% for log files, it depends on where the script is launched from. Use %USERPROFILE% or a dedicated log directory for reliable write access.

6. Always Use setlocal / endlocal

Without setlocal, every variable your script creates persists in the parent shell session, causing potential conflicts when running multiple scripts in sequence.

Conclusions

Monitoring packet loss over time provides the "smoking gun" needed to solve complex connectivity problems. By moving beyond simple "Up/Down" checks and analyzing the quality of the connection, you gain the data needed to optimize your network and hold service providers accountable. This proactive auditing ensures that your professional environment remains stable, clear, and ready for high-bandwidth tasks like VoIP and video conferencing.