2016-11-28

Network lag & packet loss checker script

Why I created the script

At the time I had experienced a lot of strange packet loss and lag on my Internet connection at home. And as you all know, when you call the ISP support they just blame EVERYTHING on your home router. I needed a way to monitor and log the packet loss and I started to write the script networkcheck.cmd. To begin with I used my own routers IP, the gateway of the ISP and two other stable web sites. That way I could prove to them that I didnt have packet loss "on my side".

What is does

The script detects network lag and packet loss. Its not very advanced but it have helped me to detect network anomalies at several occasions.  Make sure you read through the script before you run it. I have flagged some parts with Check_Before_Run so you can find them easily.

If everything is fine you wont see anything but if things are not ok...
The first check is to see if the gateway is responding. If not you will get an output that looks like this:

The next check is a nslookup of the pre-set domains to see that domain can be resolved:
 And if the request times out:
The text should say PING error, it was a typo.
If any of the above problems are detected, the script will halt.
When gateway and DNS is working the script will ping all the hosts to scan for packet loss:
 If no packet loss is detected but the average response time is too high:
 

How I run it

I run the script as a scheduled task in Windows as the current logged in user. (you probably want to use invisible.vbs to launch the cmd script to avoid cmd black screen popups every time it runs)

Make sure you don't run the script as the SYSTEM or it will halt. This is because the MSG cannot send to a user if run as SYSTEM.


The script must be run as the logged on user. Add the "Users" group to the "use the following user account".

Add a schedule of your choice. I make mine rune every hour with a 30 min random delay.
When I detect problems I change the schedule to run every 15 minutes.

Add the wscript.exe to the program path

Then add the path to the invisible.vbs followed by the path to the networkcheck.cmd as optional arguments.

The script

:: networkcheck.cmd
:: Network check and alert thingy by david.djerf@liu.se 2013-10-10 11:11
:: Part of script copy pasted from scripts found on the Internet
:: It have been very tricky to test the script since network problems are hard to simulate. Please let me know if somthing isnt working properly!
:: 2013-10-01 Added check for games to avoid unwanted alt+tab to desktop while playing
:: 2014-03-26 Fixed broken DNS check when DNS is failing
:: 2014-07-10 Adding traceroute if packetloss
:: 2016-11-27 Made some changes to work properly with Windows 10
::      Made script halt if DNS or PING errors detected
::     New way to set current date and time to work with different date formats
::     Added checks for the log folder to make sure it exists and is writable
::     Added check to see if script is already running (needed when started by invisible.vbs as scheduled task)
::     Added a lot of comments 
:: 2017-01-16 Added some checks to detect if Internet is not reachable (to speed up error msg and abort script)


@ECHO OFF
SETLOCAL EnableDelayedExpansion
SET DEBUG=0

:: Check_Before_Run
:: Don't check if program x is running
::tasklist |findstr /i "program.exe">NUL
::IF "%ERRORLEVEL%" == "0" GOTO :EOF

:: Don't check if Cisco VPN is running
tasklist |findstr /i "vpnui.exe">NUL
IF "%ERRORLEVEL%" == "0" GOTO :EOF

:: Checkroute IP - Set this to something that is trusted to be online outside your network.
:: Check_Before_Run
SET CHECKROUTEIP=x.x.x.x

:: Hosts to ping. Make sure you choose hosts with stable latency and benchmark them before you set the LATENCY variable value below
:: Check_Before_Run
SET PINGHOST1=changeme1.com
SET PINGHOST2=changeme2.com
SET PINGHOST3=changeme3.com
SET PINGHOST4=changeme4.com
:: Number of pings - Warning Dont set this to low as the avrage values will become unpredictable
SET NUMPINGS=30
:: Acceptable average latency in ms
SET LATENCY=12


:: Reset variables used later
SET PINGHOSTLAG=0
SET LAG=0
SET LOSS=0
SET HALT=0
SET GATEWAY=
SET MSGUSER=
SET IP=
:: where to log and make sure it is writable
SET LOGPATH=%~dp0\logs
IF NOT EXIST "%LOGPATH%" MKDIR "%LOGPATH%" >NUL 2>&1
IF NOT EXIST "%LOGPATH%" (
 ECHO Could not create %LOGPATH%
 SET HALT=1
)

IF EXIST "%LOGPATH%\writetest.tmp" DEL /F "%LOGPATH%\writetest.tmp"
IF EXIST "%LOGPATH%" ECHO Write test>"%LOGPATH%\writetest.tmp" 2>NUL
IF NOT EXIST "%LOGPATH%\writetest.tmp" (
 ECHO Could not write to %LOGPATH%
 SET HALT=1
)

for /F "usebackq tokens=1,2 delims==" %%i in (`wmic os get LocalDateTime /VALUE 2^>NUL`) do if '.%%i.'=='.LocalDateTime.' SET ldt=%%j
SET YEAR=%ldt:~0,4%
SET MONTH=%ldt:~4,2%
SET DAY=%ldt:~6,2%
SET HOUR=%ldt:~8,2%
SET MINUTE=%ldt:~10,2%
SET SECOND=%ldt:~12,2%


:: Make sure script is not running already, reset if lock file is old
IF EXIST "%LOGPATH%\_lock.tmp" (
 FINDSTR /i "%YEAR%-%MONTH%-%DAY%" "%LOGPATH%\_lock.tmp">NUL 2>&1
 IF NOT "%ERRORLEVEL%" == "0" ( 
  DEL /F "%LOGPATH%\_lock.tmp"
 ) ELSE (
  TYPE "%LOGPATH%\_lock.tmp"
  GOTO :EOF
 )
)

ECHO Script is running %YEAR%-%MONTH%-%DAY% %HOUR%:%MINUTE%:%SECOND%>"%LOGPATH%\_lock.tmp"
IF "%HALT%" == "1" CALL :CLEANUP && EXIT /B 911

:: Automatically find  gateway
FOR /f "tokens=1-2 delims=:" %%A IN ('IPCONFIG^|findstr /i "default.gateway"') DO IF NOT DEFINED GATEWAY SET GATEWAY=%%B
:: Remove space
SET GATEWAY=%GATEWAY:~1%
SET MSGUSER=%USERNAME%
IF "%MSGUSER%" == "%COMPUTERNAME%$" (
 ECHO Script cannot send MSG if run as SYSTEM
 CALL :CLEANUP
 EXIT /B 99
)

FOR /f "tokens=1-2 delims=:" %%A IN ('IPCONFIG^|findstr /i "IPv4.address"') DO IF NOT DEFINED IP SET IP=%%B
SET IP=%IP:~1%

:: Check_Before_Run
:: If script is to be run by scheduled task as SYSTEM user you must set MSGUSER
:: Set GATEWAY to IP or dns-name
:: local user to send warnings to, if script is run by the current user set MSGUSER=%USERNAME%
ECHO %IP%| FINDSTR /I "192.168.10" >NUL 2>&1
IF "%ERRORLEVEL%" == "0" (
 SET GATEWAY=192.168.10.1
 SET MSGUSER=artoo
)
ECHO %IP%| FINDSTR /I "130.236.168.211" >NUL 2>&1
IF "%ERRORLEVEL%" == "0" (
 SET GATEWAY=liunet-gw.ifm.liu.se
 SET MSGUSER=administrator
)
ECHO %IP%| FINDSTR /I "192.168.98" >NUL 2>&1
IF "%ERRORLEVEL%" == "0" (
 SET GATEWAY=liunet-gw.ifm.liu.se
 SET MSGUSER=administrator
)

:: Failsafe
IF "%GATEWAY%" == "" CALL :CLEANUP && EXIT /B 911
IF "%MSGUSER%" == "" CALL :CLEANUP && EXIT /B 911


:: *** BEGIN DO STUFF ***
CALL :CHECKGATEWAY
IF "%HALT%" == "1" CALL :CLEANUP && EXIT /B 911
CALL :CHECKROUTE
IF "%HALT%" == "1" CALL :CLEANUP && EXIT /B 911
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO SET PINGHOST=%%A&& CALL :CHECKDNS
IF "%HALT%" == "1" CALL :CLEANUP && EXIT /B 911
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO SET PINGHOST=%%A&& CALL :PING
IF "%HALT%" == "1" CALL :CLEANUP && EXIT /B 911
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO SET PINGHOST=%%A&& CALL :CHECKLOSS
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO SET PINGHOST=%%A&& CALL :CHECKLAG
CALL :CHECKLAGAVRAGE
CALL :SENDMSG
IF "%DEBUG%" == "0" CALL :CLEANUP


GOTO :EOF
:CHECKGATEWAY
:: Check if GATEWAY is up
ping -n 1 %GATEWAY%>NUL 2>&1
IF NOT "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because gateway %GATEWAY% could not be reached
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
 SET HALT=1
)

GOTO :EOF
:CHECKROUTE
ping -n 1 %CHECKROUTEIP%>"%TEMP%\ping-%CHECKROUTEIP%.tmp" 2>&1
FINDSTR /I "Destination.net.unreachable" "%TEMP%\ping-%CHECKROUTEIP%.tmp"
IF "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because %CHECKROUTEIP% could not be reached ^(Destination net unreachable^)
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
 SET HALT=1
)
IF "%HALT%" == "1" GOTO :EOF
tracert %CHECKROUTEIP%>"%TEMP%\tracert-%CHECKROUTEIP%.tmp" 2>&1
FINDSTR /I "Destination.net.unreachable" "%TEMP%\tracert-%CHECKROUTEIP%.tmp"
IF "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because %CHECKROUTEIP% could not be reached ^(Destination net unreachable^)
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
 SET HALT=1
)

GOTO :EOF
:CHECKDNS
:: DNS check
nslookup %PINGHOST%>"%TEMP%\nslookup-%PINGHOST%.tmp" 2>&1
FINDSTR /I "Non-existent.domain" "%TEMP%\nslookup-%PINGHOST%.tmp"
IF "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because of DNS error - %PINGHOST% could not be resolved
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 SET HALT=1
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
)
IF "%HALT%" == "1" GOTO :EOF
FINDSTR /I "DNS.request.timed.out" "%TEMP%\nslookup-%PINGHOST%.tmp"
IF "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because of DNS error - %PINGHOST% DNS request timed out
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 SET HALT=1
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
)

GOTO :EOF
:PING
PING -n %NUMPINGS% %PINGHOST%>"%TEMP%\ping-%PINGHOST%.tmp" 2>&1
FINDSTR /R /I "Request.timed.out" "%TEMP%\ping-%PINGHOST%.tmp" >NUL 2>&1
IF "%ERRORLEVEL%" == "0" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because of PING error - %PINGHOST% - Request timed out
 ECHO !ALERTMSG!>%TEMP%\msgfile.txt
 SET HALT=1
 MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt
)


GOTO :EOF
:CHECKLOSS
:: Get system time regardless of system language settings
for /F "usebackq tokens=1,2 delims==" %%i in (`wmic os get LocalDateTime /VALUE 2^>NUL`) do if '.%%i.'=='.LocalDateTime.' SET ldt=%%j
SET YEAR=%ldt:~0,4%
SET MONTH=%ldt:~4,2%
SET DAY=%ldt:~6,2%
SET HOUR=%ldt:~8,2%
SET MINUTE=%ldt:~10,2%
SET SECOND=%ldt:~12,2%

::Set the current time in a format that can go in a filename.
SET MYTIME=%HOUR%:%MINUTE%
SET FILETIME=%HOUR%-%MINUTE%
SET FILEDATE=%YEAR%-%MONTH%-%DAY%

FINDSTR /R "Lost.=.0" "%TEMP%\ping-%PINGHOST%.tmp" >NUL 2>&1
IF NOT "%ERRORLEVEL%" == "0" ( 
 SET /A LOSS=%LOSS%+1
 IF "%DEBUG%" == "1" ECHO %PINGHOST% LOSS=%LOSS%
 COPY /Y "%TEMP%\ping-%PINGHOST%.tmp" "%LOGPATH%\ping-%FILEDATE%--%FILETIME%.log"
)

GOTO :EOF
:CHECKLAG
FOR /f "tokens=10 delims=^=,ms" %%A IN ('FINDSTR /R "Average.[>=].[0-9]*ms" "%TEMP%\ping-%PINGHOST%.tmp"') DO (
 IF "%DEBUG%" == "1" ECHO %PINGHOST% "%%A" ms
 SET /A PINGHOSTLAG=%PINGHOSTLAG%+"%%A"
 IF "%DEBUG%" == "1" ECHO %PINGHOSTLAG%
)

GOTO :EOF
:CHECKLAGAVRAGE
SET /A LAGAVRAGE=%PINGHOSTLAG%/4
IF "%DEBUG%" == "1" ECHO %LAGAVRAGE%

IF %LAGAVRAGE% LEQ %LATENCY% (
 SET LAG=0
) ELSE (
 SET LAG=1
)

GOTO :EOF
:SENDMSG
SET MSG=0
IF "%LAG%" == "1" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because of high response time
)

IF "%LOSS%" == "1" (
 SET MSG=1
 SET ALERTMSG=Alert triggered because of packet loss
)

IF "%MSG%" == "0" GOTO :EOF


:: Set LAGMSG
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO (
 SET PINGHOST=%%A
 FOR /f "tokens=1-3 delims=," %%a IN ('FINDSTR "Average" "%TEMP%\PING-!PINGHOST!.tmp"') DO (
 ECHO %%a, %%b, %%c>"%TEMP%\LAGMSG-!PINGHOST!.tmp"
 )
)

:: Set LOSSMSG
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO (
 SET PINGHOST=%%A
 FOR /f "tokens=1-3 delims=," %%a IN ('FINDSTR "Packets" "%TEMP%\PING-!PINGHOST!.tmp"') DO (
 ECHO %%a, %%b, %%c>"%TEMP%\LOSSMSG-!PINGHOST!.tmp"
 )
)

:: Send the message
ECHO %ALERTMSG%>%TEMP%\msgfile.txt
ECHO. >>%TEMP%\msgfile.txt
ECHO Approximate round trip times in milli-seconds: %LAGAVRAGE%ms>>%TEMP%\msgfile.txt
ECHO. >>%TEMP%\msgfile.txt
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO (
 SET PINGHOST=%%A
 ECHO !PINGHOST!>>%TEMP%\msgfile.txt
 TYPE "%TEMP%\LAGMSG-!PINGHOST!.tmp">>%TEMP%\msgfile.txt
)
ECHO. >>%TEMP%\msgfile.txt
FOR %%A in (%PINGHOST1% %PINGHOST2% %PINGHOST3% %PINGHOST4%) DO (
 SET PINGHOST=%%A
 ECHO Ping statistics for !PINGHOST!>>%TEMP%\msgfile.txt
 TYPE "%TEMP%\LOSSMSG-!PINGHOST!.tmp">>%TEMP%\msgfile.txt
)

MSG %MSGUSER% /TIME:1440<%TEMP%\msgfile.txt

:CLEANUP
IF EXIST "%TEMP%\PING-*.tmp" DEL /F "%TEMP%\PING-*.tmp" >NUL 2>&1
IF EXIST "%TEMP%\LOSSMSG-*.tmp" DEL /F "%TEMP%\LOSSMSG-*.tmp" >NUL 2>&1
IF EXIST "%TEMP%\LAGMSG-*.tmp" DEL /F "%TEMP%\LAGMSG-*.tmp" >NUL 2>&1
IF EXIST "%TEMP%\nslookup-*.tmp" DEL /F "%TEMP%\nslookup-*.tmp" >NUL 2>&1
IF EXIST "%TEMP%\tracert-*.tmp" DEL /F "%TEMP%\tracert-*.tmp" >NUL 2>&1
IF EXIST "%TEMP%\msgfile.txt" DEL /F "%TEMP%\msgfile.txt">NUL 2>&1
IF EXIST "%LOGPATH%\writetest.tmp" DEL /F "%LOGPATH%\writetest.tmp">NUL 2>&1
IF EXIST "%LOGPATH%\_lock.tmp" DEL /F "%LOGPATH%\_lock.tmp">NUL 2>&1

invisible.vbs

Why I needed the script

When you want to run a script as the currently logged on user as a scheduled task, the CMD will popup as a black window. I searched the Internet and found this several different solutions, but this one can be used to launch any other CMD-scripts.
Source: http://superuser.com/questions/62525/run-a-batch-file-in-a-completely-hidden-way

What it does

It runs the CMD-script in the background without any popup. Please note that the task scheduler thinks the task is finished when the vbs script has finished, even if the CMD-script still runs in the background. This is something I need to look at some time, if you know how to make it wait for the CMD to finish, please let me know!

How I run it

In CMD you can run it with cscript.exe, but if launched as a scheduled task I prefer to run it with wscript.exe.



' invisible.vbs
' Use this to run batch script invisible
' wscript.exe "<PATH>\invisible.vbs" "<PATH>\MyBatchFile.cmd"
CreateObject("Wscript.Shell").Run """" & WScript.Arguments(0) & """", 0, False