Inserting Delays with Millisecond Resolution in Windows Batch (.bat) Files

hypercubeConsultant
CERTIFIED EXPERT
Published:
Updated:
Windows batch file or script in which we want to delay code execution with sub-second resolution.  An example is when we want to execute a number of PINGs in succession by using a code loop.
for /L %%a In (0 1 %number_of_pings%) do (
ping -n 1 -w 1.1.1.1 > nul
)

Select all Open in new window

Inserting Delays with Millisecond Resolution in Windows Batch (.bat) Files
 
NOTE: This article gets a lot of accesses each month.  I'd like to know what your interest is and any comments.  Please respond to murphy.henri@convergenceservice.com

Occasionally, we want to write a Windows batch file or script in which we need to delay code execution. A good example is when we want to execute a number of PINGs in succession by using a code loop.

There are a few ways that this can be done and there is apparently a lot of misunderstanding due to the lack of complete documentation. So, I conducted some experiments and wrote some code that would both augment what we know about doing this and to suggest some ways to insert delays that are adjustable in increments of milliseconds.

Modern Windows systems have a timeout command that looks like this:

timeout n  where “n” is an integer number of seconds. 
So, clearly one can’t get delays that are adjusted to some number of milliseconds. There is also a sleep command that can be added that looks like this:

sleep n where “n” is also an integer number of seconds.
The most promising approach for millisecond-resolution delays seemed to be use of the PING command.

The PING timeout method uses a PING command to a non-existent IP address so that it has to wait to the end of its built-in or specified timeout period before finishing. And, the timeout period is specified in an integer number of milliseconds. So, it would appear to be a good mechanism to use for creating a delay with millisecond resolution.

The first thing that I learned was that the PING timeout method doesn’t do what one might hope or think.
 
The General Approach using PING

The common PING command set up to create a delay looks like this;
PING –n 1 –w xxxxx [fake_IP_address] > nul

Select all Open in new window


The “xxxxx” is the timeout entered as expressed in milliseconds.  So, one might think that “what you see is what you get”.  But that’s not the case.  After seeing some unexpected results, I decided to measure the timeout times.  Here’s the result:
  • If the timeout is 500 or less, 500 is the result.  The entry is ROUNDED UP to 500.
  • If the timeout is 999 of less, 500 is the result.  The entry is ROUNDED DOWN to 500.
  • If the timeout is 1000 or more, the timeout is ROUNDED DOWN to the nearest ½ second (500 milliseconds). For example, 1333 becomes 1000, 1999 becomes 1500, 2499 becomes 2000 and 2999 becomes 2500, etc. 
So, while the times are expressed in milliseconds, they are only effected in ½ second chunks. That could well be an unfortunate limitation for some.

A Trick for Using PING for Batch File millisecond Delays

If the timeout “-w” entry is omitted altogether, then the default timeout is 4000 milliseconds or 4 seconds.  (There is some unfortunate literature around that says it’s 1000 but that seems proven to be incorrect through good old-fashioned testing.)

Whether by a typo or some other insightful method, I discovered this (likely undocumented “feature”):

If the timeout in the PING command is blank, that is, if the command is structured with -w but with no parameter accompanying “–w” then it looks like this:
PING –n 1 –w  [fake_IP_address] > nul

Select all Open in new window


Using this form of the PING command results in a very short timeout. I can only imagine that it’s machine dependent and I have to believe that it’s undocumented so one might only want to use it with some huge caveats:
  • The fake_IP_address can make  big difference and can cause much larger delays than would be useful.  Most likely using localhost or 127.0.0.1 will be much better!  This will also generate an average time that is as short as might be possible.
  • You have to determine what the delay is going to be on the machine it’s to be used on. So, it’s likely not very “portable”.
  • You may have to be concerned about what the delay is going to be on a machine with other high-demand processes running.So, it may not be very accurate or stable.

Nonetheless, I figure it’s worth documenting and to make some suggestions regarding how to make use of it:

Let’s assume that we want a delay of 333 milliseconds. So that will be the objective of the batch file we’ll develop here. First, we need to know the single-ping delay. To get that number, we’ll run a little measurement batch program:
:LOOP
     ping 10.109.199.199 -n 1 -w >nul
     ping 10.109.199.199 -n 1 -w >nul
     [repeat the above PINGs 98 more times….] <--
echo %time%
GOTO :LOOP

Select all Open in new window


After a very short time, you can pause this program by pressing CTRL+C.  And, don’t respond Y or N yet… Then compute a few of the time differences.  In my case, I got 1.63 seconds.

NOTE: the times are in hours:minutes:seconds where seconds are resolved down to centiseconds (1/100th of a second). Then multiply by 10 for the number of milliseconds (1000 milliseconds per second divided by 100 operations in the loop) that were performed in each ping.  In my case then, I get 1.63 seconds per loop or 16.3 msec. per PING. And, then divide the desired delay by this number.

More recently, I revisited this measurement program and came up with a Powershell script that eliminates the manual steps:
$stopwatch = [system.diagnostics.stopwatch]::StartNew()
$loops=10
for($i = 1; $i -le $loops; $i++) {
ping localhost -n 1
}
$stopwatch
#Start-Sleep -seconds 5
$elapsedtime = ($stopwatch.ElapsedMilliseconds/$loops)
#Start-Sleep -seconds 5
Write-Output -NoEnumerate "average ping time $elapsedtime milliseconds"

While one could use Network-Connection instead of PING here, I found that the times generated were much longer.  So that defeats the purpose of getting millisecond-level resolution.  

So, to get 333 msec, we would need 333/16.3 = 20.42.  Rounded down to 20 will give us 20*16.3 = 326 msec.  That’s the best we can do; it’s about 2% shorter than we said we wanted.

By the way, if you’re tempted to run all the pings using one command with the –n parameter like this:

ping 10.109.199.199 –n 20 –w > nul

Select all Open in new window


I found that this won’t work.  It seems if the “-w” parameter is blank then the “-n” parameter makes no difference.  It can also be blank or it can be not used at all (which would suggest that the default of 4000 would be used).  If the “-w” parameter is blank then it just doesn’t matter what you do with “-n”.
 
Implementing a Millisecond-Resolution Delay
The code one might use to test a Windows batch file to effect a delay would be:

@ECHO OFF
:DELAY
SET number_of_pings= 20
@echo Start %time%
for /L %%a In (0 1 %number_of_pings%) do (
ping -n 1 -w 1.1.1.1 > nul
)
@echo end %time%
GOTO :DELAY

Select all Open in new window

Then one can stop execution with CTRL+C and inspect the total delay to confirm that the desired total delay is being achieved.

A shorter version can be inserted into the actual batch file in which to implement a single delay:
 

REM On this machine the delay per ping is 16.3msec.
REM 333/16.3~20 pings
SET number_of_pings= 20
for /L %%a In (0 1 %number_of_pings%) do (
ping -n 1 -w 10.109.199.199 > nul
)

Select all Open in new window

A Sample Application - A PING Probe

I developed this delay code segment to adjust ping intervals in a "ping probe" that I use to check continuity of communications both intra-network and into the internet world.  Here's the code for a probe using the delay.  So you will see two uses of PING; the first one is the delay and the second one is an actual PING.  This second one should use a .bat file to run because Powershell won't accept -w [blank].  But, the measurement program
 
@ECHO OFF
:VARIABLES
SET drive_letter=%1
IF "%1"=="" (SET drive_letter=c:)
echo Drive letter = %drive_letter%
REM ***************************SETUP***********************
SET Machine=%2
REM Edit the next line:
IF "%2"=="" (SET Machine=207.108.182.1)
echo Machine = %Machine%
echo.
REM Edit the next 5 lines according to the needs:
SET testname=QWEST
SET /a faillimit=2
SET pinginterval=500
SET pingtimeout=100
SET single_ping_delay=16.3
REM ***************No changes below*****
SET /a pings=%pinginterval%/%single_ping_delay%
@Echo pings = %pings%
SET fileloc=%drive_letter%\Users\public\probes\ping
SET pinglog=%fileloc%\%testname%_pinglog.txt
SET tracelog=%fileloc%\%testname%_tracelog.txt
SET pingtemp=%fileloc%\%testname%_pingtemp.txt
SET temptxt=%fileloc%\%testname%_temptxt.txt
REM **************************END SETUP********************
echo.
cd \
%drive_letter%
cd \
md users
cd users
md public
cd public
md probes
cd probes
md ping
cd ping
echo.

REM initialize counts and limits
SET /a pingcount=0
REM Zeros the contiguous ping failure count
SET /a failcount=0
ECHO %DATE%
ECHO %TIME%
REM Initializing TRACE then return to :PING
goto :TRACE

:PING
REM echo %time%
REM Delay between pings using ping -w [blank]

for /L %%a In (0 1 %pings%) do (
ping -n 1 -w 127.0.0.1 > nul
)

REM @ECHO add ping output to %pingtemp%.  This is NOT a delay!

ping -w %pingtimeout% -n 1 %Machine% >%pingtemp%

REM @ECHO Find "reply" and reset fail counter
(find /I "reply"   %pingtemp%>%pinglog%) && (set /a failcount=0 & goto :PING)

REM @ECHO Finding "request timed out" and increment fail counter
(find /I "request" %pingtemp%>%pinglog%) && set /a failcount=%failcount%+1

REM @ECHO Finding "unreachable" and increment fail counter
(find /I "unreachable" %pingtemp%>%pinglog%) && set /a failcount=%failcount%+1

REM @ECHO Check failcount
if %failcount% geq 1 echo failcount %failcount% Pings have failed  %date% %time%
if %failcount% geq 2 echo failcount %failcount% Pings have failed  %date% %time%>>%tracelog%
if %failcount% geq %faillimit% goto :TRACE
goto :PING

:TRACE

REM @ECHO Reset failcount to zero
set /a failcount=0

ECHO.
ECHO Trace Started
ECHO %DATE%
ECHO %TIME%
@ECHO.
@ECHO.>>%tracelog%
@ECHO Trace Started>>%tracelog%
@ECHO %DATE%>>%tracelog%
@ECHO %TIME%>>%tracelog%
@ECHO.>>%tracelog%
TRACERT -d -h 30 %machine% >>%tracelog%
@ECHO.>>%tracelog%
@ECHO Trace ended >>%tracelog%
@ECHO %DATE% >>%tracelog
@ECHO %TIME% >>%tracelog
@ECHO Trace ended
@ECHO %DATE%
@ECHO %TIME%

GOTO PING
:EOF

REM Program will loop until CTRL+C is pressed or window is closed.

end

Select all Open in new window



Other than itself, this .bat file creates all the other files that are needed. It uses the directory:

C:\Users\Public\probes\ping
If PINGs are missed faillimit times in succession, a traceroute is run and recorded and the pinging continues. The command line window displays the total count of missed successive pings each time there's a missed ping. So, it's typical for it to show lines like this:
 
failcount 1 Pings have failed  Sun 02/08/2015 14:26:26.17
 failcount 1 Pings have failed  Sun 02/08/2015 14:29:06.60
 failcount 1 Pings have failed  Sun 02/08/2015 14:29:46.09

.

And, if there's a sequence of 3 missed pings in succession, and if faillimit=3, you would see:

failcount 1 Pings have failed  Sun 02/08/2015 14:26:26.17
 failcount 2 Pings have failed  Sun 02/08/2015 14:29:06.60
 failcount 3 Pings have failed  Sun 02/08/2015 14:29:46.09
 Trace Started
 Sun 02/08/2015
 14:29:14.66
 
 Trace ended
 Sun 02/08/2015
 14:29:37.30
Then, in the tracelog file you may be able to see where the communication path had failed.
1
31,856 Views
hypercubeConsultant
CERTIFIED EXPERT

Comments (6)

hypercubeConsultant
CERTIFIED EXPERT

Author

Commented:
louisfr:  You provide a reasonable explanation for why the -n option does nothing in the case mentioned.   It makes sense to me.  Nonetheless, it was only an observation from empirical tests and has no effect on the approach presented here.
CERTIFIED EXPERT
Top Expert 2014

Commented:
The difference is that it's not an "undocumented feature" and it's not "worth documenting".
It's the normal behavior of any command: if there's an error in the parameters, display an error message and do nothing else.

It has a huge effect on the approach. You thought you found a way to reduce the timeout of ping and didn't go beyond that. Did you try removing the redirection to nul to see what actually is going on? Did you try using another command instead of ping? I'm guessing echo would get you shorter intervals.
hypercubeConsultant
CERTIFIED EXPERT

Author

Commented:
Something here is worth documenting and I'd rather not argue about what that is.  It's not a research project.

Indeed, you've come up with some good suggestions as a result.
CERTIFIED EXPERT
Top Expert 2014

Commented:
I'm sorry for the way I wrote my last reply. I think I can write understandable sentences in English, but I'm really not good at conveying the right feeling.
hypercubeConsultant
CERTIFIED EXPERT

Author

Commented:
I'll try out those ideas you mentioned.  Thanks!

View More

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.