Commands like timeout.exe were added to Windows to make waiting in a batch file easier. Unfortunately timeout.exe still leaves much to be desired. There are several alternatives people use to simulate a delay in the Windows Command Prompt (cmd.exe).
Below you can see an analysis of the suggestions from this StackOverflow post, along with one additional suggestion I have that will increase precision (method 1 below).
The key factors I’m analyzing is the time precision of a command and I attempt to determine the compatibility with past versions of Windows. I also want this to work out-of-the-box with Windows and not require an external executable to be loaded.
My test consisted of the following methods:
- Method 1: Custom Function Command:
call :waitfor 5000>nul
(see below for implementation details) - Method 2: Ping.exe (dummy IP, timeout) Command:
ping.exe -n 1 -w 5000 1.1.1.1 >nul 2>&1
- Method 3: Ping.exe (localhost, 1 sec between pings)Command:
ping.exe -n 6 -w 1 127.0.0.1 >nul 2>&1
- Method 4: Powershell.exe Command:
powershell.exe -command "Start-Sleep -Milliseconds 5000">nul
- Method 5: Timeout.exe Command:
timeout.exe /t 5 /nobreak >nul 2>&1
- Method 6: VBScript (cscript.exe) Command:
echo WScript.Sleep^(WScript.Arguments^(0^)^) >"%temp%sleep.vbs" && cscript "%temp%sleep.vbs" 5000 >nul
Each method was executed multiple times with the durations:
- *100ms (0.1 sec)
- *300ms (0.3 sec)
- *500ms (0.5 sec)
- *700ms (0.7 sec)
- *900ms (0.9 sec)
- 1,000ms (1 sec)
- *1,200ms (1.2 sec)
- *1,400ms (1.4 sec)
- *1,600ms (1.6 sec)
- *1,800ms (1.8 sec)
- 2,000ms (2 sec)
- 5,000ms (5 sec)
- 10,000ms (10 sec)
- 20,000ms (20 sec)
- 30,000ms (30 sec)
- 45,000ms (45 sec)
* only applies if method supports partial seconds
Here is a breakdown of the commands to illustrate which has the highest difference between the expected time and the actual time it waits. Red or yellow colors are bad, and closer to 0 is good. If a command is expected to wait for 100ms, and it actually waits 100ms, the difference is 0. Likewise, if it actually takes 130 its difference is +30 – or if it’s 80, then its difference is -20.
Below are additional details on each method and my final conclusion (tl;dr; read conclusion).
Method 1: Custom function
This method requires the use of a custom bat file function. The function is quite simple, it starts a loop and checks if the duration has expired. If the duration is more than 1second away, it will call timeout.exe to wait (can easily be modified to use ping.exe for better compatibility with older Windows).
The function waitFor (plus 2 support functions):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | @echo off :waitFor setlocal enabledelayedexpansion set "sleepFor=%~1" call :currentTime startTime :: Note, dont use a for loop, the cmd prompt would try to expand each iteration, and that is a big problem :loopStart call :currentTime curTime call :getTimeDiff result !startTime! !curTime! set /A timeDiffMs=!result!*10 if !timeDiffMs! GTR !sleepFor! ( goto :return ) set /A timeRemaining=!sleepFor!-!timeDiffMs! if !timeRemaining! GTR 1200 ( timeout /nobreak /t 1 >nul 2>&1 ) goto :loopStart :return endlocal goto :eof :currentTime setlocal set "curTime=%TIME%" :: For some stupid reason if the hour < 10, it uses a blank space instead of a leading zero if [^%curTime:~0,1%]==[^ ] set "curTime=0%curTime:~1%" endlocal & set %~1=%curTime% goto :eof :getTimeDiff setlocal set "startTime=%~2" set "endTime=%~3" set /A startTime=(1%startTime:~0,2%-100)*360000 + (1%startTime:~3,2%-100)*6000 + (1%startTime:~6,2%-100)*100 + (1%startTime:~9,2%-100) set /A endTime=(1%endTime:~0,2%-100)*360000 + (1%endTime:~3,2%-100)*6000 + (1%endTime:~6,2%-100)*100 + (1%endTime:~9,2%-100) set /A duration=%endTime%-%startTime% if %endTime% LSS %startTime% set set /A duration=%startTime%-%endTime% endlocal & set "%~1=%duration%" goto :eof |
Calling the function:
1 | call :waitFor 2500 |
Pros:
| Cons:
|
Method 2: Ping (dummy IP, timeout)
This method sets the wait time argument and pings an IP that won’t respond. The IP must exist but never respond to the ping, thus causing a timeout. Valid (unused OR used but responds to pings) or invalid IPs will cause this to complete and never timeout.
1 | ping.exe -w 5000 -n 1.1.1.1 |
Pros:
| Cons:
|
Method 3: Ping (localhost)
Between each packet, ping.exe will wait 1 second. This can be used to (somewhat) reliably make your bat file sleep. This method is similar to timeout.exe in the sense that it is limited to whole seconds. The one thing better is ping.exe has been around longer than timeout.exe, so it should support more older Windows OS’s (timeout.exe was introduced with Vista).
Please note that you must specify 6 pings in order for it to wait 5 seconds, since it’s the actual time between pings that causes the delay.
1 | ping.exe -w 1 -n 6 127.0.0.1 |
Pros:
| Cons:
|
Method 4: Powershell
This method uses powershell.exe to generate a delay. Accuracy-wise, this is THE worst of all the methods I tested (at least for short durations). Compatibility-wise, it’s also not great since Powershell isn’t too old, and didn’t start shipping with the OS until Windows 7
1 | powershell.exe -command "Start-Sleep -Milliseconds 5000" |
Pros:
| Cons:
|
Method 5: Timeout
Timeout.exe is the recommended way of performing many wait operations in cmd.exe, and in many cases it’s probably the best way to go if you understand the shortcomings.
With only a 1 numeric argument, it acts like “pause” with a timeout (user can hit any key to cancel). For example:
1 2 3 | timeout /t 5 Waiting for 5 seconds, press a key to continue ... |
However, if you would like to cause a timeout without allowing the user to cancel it by pressing any key, and you want to suppress the output “Waiting for …” you can provide the /nobreak argument and redirect the output to “nul”:
1 | timeout /t 5 /nobreak >nul |
Timeout.exe has some unique quirks. It doesn’t support units smaller than seconds, and it’s precision is somewhere between -1 and 0 seconds. This can be a very important quirk to be aware of.
If you are trying to sleep for 1 second, timeout.exe will never go over 1 second, and it could actually go for 0 seconds (so no wait at all). If you specify 10 seconds, it’ll be somewhere between 9 and 10. Obviously this quirk is more significant when sleeping for 1 second, instead of 10.
Timeout.exe was introduced with Vista
Pros:
| Cons:
|
Method 6: VB Script
This method has your bat file generate a small VB Script on the fly and immediately calls it. Not surprisingly, this method is one of the most efficient.
This method has been available since at least Windows 2000, but likely earlier (as part of a redistributable package).
Pros:
| Cons:
|
Conclusion
The good
There are definitely some “righter” ways to accomplish waiting from the command line, but each one has its cons. Overall I recommend using one of the following (depending on your requirements and system limitations): custom function, timeout.exe, or vbs.
The custom function(s) I wrote seems to be more accurate on average than all other approaches, but perhaps a little bit overkill for most situations. It requires a good chunk of boilerplate code to be put in place before it can be used. Like the VBS approach, this one can sleep down to the millisecond quite accurately. The current version depends on timeout.exe which means it should work with Vista or later, but a very small tweak (converting it ping) should enhance backward-compatibility back to at least Win2k.
Timeout.exe is the recommended approach if supporting old versions of Windows (prior to Vista) isn’t too important to you, and you don’t need a high level of precision. Just be aware of the quirks.
The VBS approach is fairly light-weight, accurate, and can be used to sleep down to a millisecond. The interpreter has been included with Windows since at least Windows 2000. The only possible issue I could foresee with this approach is the possibility of anti-virus software or a security policy blocking cscript.exe.
The bad
I really dislike Ping (dummy IP, force timeout – aka Method 2). It kinda sorta works now, but it just seems too hacky and whatever dummy IP you choose, it has to exist and cause ping.exe to timeout. If the IP is owned by someone else, they could break your bat file simply by changing a network setting on their end. Not only that, it’s just downright hacky feeling.
Launching powershell from a bat file in order to run a sleep operation seems to produce very bad results. On my computer, powershell.exe took about 1 second (950ms) on average to load. That means waiting for 0.2 seconds actually took around 1.2 seconds. As your time goes up, the less significant this is. For example 0.2 seconds turning into 1.2 is pretty huge, but 500 seconds turning into 501 is pretty much nothing, but in that case you might as well use timeout.exe. Powershell started shipping with Windows 7 out of the box, so it is the least compatible option in this list.
The… eh… ok…
Unlike the previous Ping test, Ping (valid IP, wait 1ms, 1sec elapses between pings – aka Method 3) isn’t too bad. It is slightly more supported than timeout.exe (at least win2k), but has about the same limitations. It doesn’t support milliseconds. It feels a little hacky, but it typically works. This method is doing an instant ping, but relying on the 1 second wait between each ping. So to wait 5 seconds, you must do 6 pings and timeout after only 1 millisecond. I don’t think you can really count on this behavior with every version of Windows going forward, Microsoft could decide to change it to 500ms someday, but it works now.
Great post Adam. Interesting read. You’ve used 2&>1 in a couple of places instead of 2>&1
Cheers,
Jeremy
Hi Jeremy, good catch (oh wow this comment is almost a year old :-X). I just fixed those two issues. I must have messed that up when writing the post since it seems to cause a “incorrect syntax” error running it like that, but the test results should reflect the correct commands. Thanks!