Batch Command to catch all possible ping issues

I have a question regarding verifying that a ping is returning properly in a batch command. Currently I have to check a ping 3 different ways to know that the server is in fact up. I want to combine these into one single ping command. I have tried using different error levels or not errorlevel 0 etc. None of them catch all possible scenarios. Here is what I have been using:

Set /p domain=Enter IP address:
set state=up

@ping.exe -n 1 %domain% | find "unreachable" > null && set state=down
if "%state%" == "down" goto :NoServer

@ping.exe -n 1 %domain% | find "TTL expired" > null && set state=down
if "%state%" == "down" goto :NoServer

@ping.exe -n 1 %domain%
if errorlevel 1 goto :NoServer

Solution 1:

There are two ways to check the sucess or failure of the ping command: test the errorlevel after execution or test the output of the command. For both cases, it is not the same to check a ipv4 or a ipv6 address.

The questions are: how does ping behave?, what is its output? when is errorlevel set?

The errorlevel

If we are working with ipv6, the rules are

  • errorlevel is set when there is no reply for all of the sent packets (all packets are lost)

  • errorlevel is not set if there is a reply to any of the sent packets

ipv6 has a consistent behaviour and checking the errorlevel is a reliable way to know if the machine is online.

In ipv4 the rules are different

  • errorlevel is set when there is no reply to at least one of the sent packets

  • errorlevel is not set when there is a reply to all of the sent packets (no packet lost)

But, in ipv4, pinging an non available machine on the same subnet does no set the errorlevel, you get an "unreachable" answer, with n packets sent, n packed received, 0 packets lost, all the packets get a reply from the same machine sending the packets.

This behaviour in ipv4 when the machine is in the same subnet makes the errorlevel check fail.

How to solve the problem in ipv4? Output check

The output of the ping command can be checked, if the string TTL= is present in the output, the target machine is online.

ping -n 1 10.0.0.1 | find "TTL=" >nul 
if errorlevel 1 ( 
    echo offline 
) else (
    echo online
)

But this approach that works in ipv4 will fail with ipv6 as this field has not been included in the ping output (and renamed, in ipv6 it is called hop limit)


For a "general" solution, this (adapted from a previous answer) can be used (seems a lot of code, but almost all are comments). The ping operation and output handling are wrapped inside a subroutine that is called with the address/host name passed as the first argument to the batch file.

@echo off

    setlocal enableextensions disabledelayedexpansion

    if "%~1"=="" goto :eof

    call :isOnline "%~1"
    if not errorlevel 1 ( echo ONLINE ) else ( echo OFFLINE )

    endlocal
    exit /b

:isOnline address pingCount
    setlocal enableextensions disabledelayedexpansion

    :: send only one ping packed unless it is indicated to send more than one
    set /a "pingCount=0", "pingCount+=%~2" >nul 2>nul 
    if %pingCount% lss 1 set "pingCount=1"

    :: a temporary file is needed to capture ping output for later processing
    set "tempFile=%temp%\%~nx0.%random%.tmp"

    :: ping the indicated address getting command output and errorlevel
    ping -w 1000 -n %pingCount% "%~1" > "%tempFile%"  && set "pingError=" || set "pingError=1"

    ::
    :: When pinging, the behaviours of ipv4 and ipv6 are different
    ::
    :: we get errorlevel = 1 when
    ::    ipv4 - when at least one packet is lost. When sending more than one packet
    ::           the easiest way to check for reply is search the string "TTL=" in 
    ::           the output of the command.
    ::    ipv6 - when all packet are lost.
    ::
    :: we get errorlevel = 0 when
    ::    ipv4 - all packets are received. BUT pinging a inactive host on the same  
    ::           subnet result in no packet lost. It is necessary to check for "TTL=" 
    ::           string in the output of the ping command
    ::    ipv6 - at least one packet reaches the host
    ::
    :: We can try to determine if the input address (or host name) will result in 
    :: ipv4 or ipv6 pinging, but it is easier to check the result of the command
    ::
    ::                          +--------------+-------------+
    ::                          | TTL= present |    No TTL   | 
    ::  +-----------------------+--------------+-------------+
    ::  | ipv4    errorlevel 0  |      OK      |    ERROR    |
    ::  |         errorlevel 1  |      OK      |    ERROR    | 
    ::  +-----------------------+--------------+-------------+ 
    ::  | ipv6    errorlevel 0  |              |      OK     |
    ::  |         errorlevel 1  |              |    ERROR    |
    ::  +-----------------------+----------------------------+
    ::
    :: So, if TTL= is present in output, host is online. If TTL= is not present,  
    :: errorlevel is 0 and the address is ipv6 then host is online. In the rest 
    :: of the cases host is offline.
    ::
    :: To determine the ip version, a regular expresion to match a ipv6 address is 
    :: used with findstr. As it will be only tested in the case of no errorlevel, 
    :: the ip address will be present in ping command output.

    set "exitCode=1"
    >nul 2>nul (
        find "TTL=" "%tempFile%" && ( set "exitCode=0" ) || (
            if not defined pingError (
                findstr /r /c:" [a-f0-9:][a-f0-9]*:[a-f0-9:%%]*[a-f0-9]: " "%tempFile%" && set "exitCode=0"
            )
        )
        del /q "%tempFile%"
    )

    :: cleanup and return errorlevel: 0=online , 1=offline 
    endlocal & exit /b %exitCode%