Although Synchronicity is one one of the best songs ever from one of the best bands ever, this post isn’t about Gordon Sumner, Stewart Copeland or Andy Summers. This post is about two myths that folks insist on perpetuating and need stamping out: synchronous and asynchronous command execution in Windows. These come up in System Center Configuration Manager (ConfigMgr/SCCM) for two main reasons: Application-based Deployment Type Detection Methods and simple batch file usage.
Detection methods enable ConfigMgr to validate application installation at various times including at deployment. The ConfigMgr client agent evaluates the defined detection methods as soon as the given installation (or uninstallation) command-line is finished executing. The problem comes in when the process launched by this command-line doesn’t perform the actual installation but instead spawns a child process and exits without waiting for the child process to finish and leaves this new child process to perform the installation. The client agent has no knowledge of the child process or that it was spawned. All that the client agent knows is that the initially launched process has exited. Based on this, the client agent evaluates the detection methods which evaluate to false because the spawned process is still working on the installation. The client agent then sets the Application installation status to failed with the well-known error code of 0x87D00324.
Rerunning the deployment soon after the deployment will most likely result in a successful detection because, by the time you are able to rerun it, the spawned process has finished its work.
The First Myth and Wrong Answer
When this occurs, folks go searching the web (right answer) but end up finding posts that use the start command with a /wait parameter (wrong answer). The help text for the wait option of the start command sounds like it’s the solution though: “Start application and wait for it to terminate.” The problem here is that the wait option suffers from the exact same short-coming described above for the ConfigMgr client agent: it has no knowledge of a spawned child process or even that a child process was spawned. Thus, just like the ConfigMgr client agent, the start command with the wait option cannot wait on something it knows nothing about.
The Second Myth
Similar to the first myth, many folks creating batch files for use with ConfigMgr use start /wait for every one of the commands within the batch file. While not specifically harmful, this is completely unnecessary because batch files already wait for one command to complete before executing the next listed command.
Synchronous and asynchronous commands
So what does this all have to do with synchronicity or rather synchronous and asynchronous commands?
Batch files are synchronous command processors meaning that they process commands in order, one by one and wait for each command to exit before moving to the next command; aka, synchronously. This is all by design and thus using start /wait in batch files is redundant and adds zero value. Without help, batch files always execute commands synchronously.
The ConfigMgr client agent is also a synchronous command processor in that it launches a single command and waits for that command to exit. Additionally, Windows Installer processes .msi file synchronously because MSIs are just “fancy” scripts.
.exe-based installers are also just compiled scripts in many cases and thus are typically synchronous. This, however, is not always the case as .exe’s have no restriction on what they can or cannot do regardless of their purpose in life.
Spawning a child process and then not waiting for its exit is asynchronous command-execution. This is not what batch files do however unless they use another command, like start (without the /wait option) to spawn a child process asynchronously.
As noted above, asynchronous command execution is possible and not all that uncommon with some .exe-based installers. Even some .msi-based installers spawn child processes asynchronously using custom actions.
If a command called in a batch file, .exe, or .msi, asynchronously launches another process, then asynchronous execution has occurred; this, however, is outside the control or visibility of the caller. Once again, this is exactly what happens with the ConfigMgr client agent when a called command asynchronously launches another child process to perform the actual installation work. There is no direct or built-in way to know about or control asynchronous command execution from called processes in batch files or in ConfigMgr. Start /wait adds zero value here as it’s in the same boat without control or visibility into spawned processes.
To visualize a typical call chain, the following diagram depicts the asynchronous launch of a child process from a called process. The caller, be it a batch file or the ConfigMgr agent, has no control or visibility of the launched child process.
This is all easily shown with a simple, primary batch file and a couple of VBScript files (I chose VBScript because it was the easiest method in this case but I could have used many other tools as well).
The batch file (batch.bat)
@Echo "Batch start"
@cscript.exe //NoLogo "%~dp0vbs1.vbs"
@Echo "Batch middle"
@cscript.exe //NoLogo "%~dp0vbs2.vbs"
@Echo "Batch end"
VBScript #1 (vbs1.vbs)
MsgBox "VBScript 1"
VBScript #2 (vbs2.vbs)
MsgBox "VBScript 2 start"
Set shell = WScript.CreateObject("WScript.Shell")
shell.Run "%windir%\notepad.exe", 1, false
MsgBox "VBScript 2 end"
- The Batch file simply calls both VBScripts intermingled with some console output.
- VBScript #1 displays a simple message box
- VBScript #2 does the following:
- Displays a simple message box.
- Asynchronously executes Notepad.exe.
- Displays another simple message box.
VBScript #2 uses the Run method of the WScript.Shell COM Object whose third parameter defines whether the script should wait on the return of the called process — synchronous execution — or whether it should continue on without waiting — asynchronous execution.
Below is a detailed description that you can easily reproduce for yourself using the above-provided scripts or you can watch the embedded video.
- Call batch.bat from the command-line.
- Batch start is shown in the command window (from batch.bat).
- vbs1.vbs is called by batch.vbs. batch.bat does not proceed until vbs1.vbs exits.
- VBScript 1 is shown in a dialog box. (from vbs1.vbs). vbs1.vbs does not proceed and neither does batch.bat.
- User clicks OK in the VBScript 1 message box, vbs1.vbs exits and control is returned to batch.bat.
- Batch middle is shown in the command window (from batch.bat).
- vbs2.vbs is called by batch.vbs. batch.bat does not proceed until vbs2.vbs exits.
- VBScript 2 start is shown in a dialog box (from vbs2.vbs). vbs2.vbs does not proceed and neither does batch.bat.
- User clicks OK in the VBScript 2 start message box.
- notepad.exe is launched asynchronously. Because the Run method launches notepad.exe asynchronously, vbs2.vbs proceeds without waiting for notepad.exe to exit. noteapd.exe can be closed at any time or completely left alone without any impact to the execution of vbs2.vbs or batch.bat. batch.bat has no knowledge of or control over notepad.exe.
- VBScript 2 end is shown in a dialog box (from vbs2.vbs). vbs2.vbs does not proceed and neither does batch.bat.
- User clicks OK in the VBScript 2 end message box, vbs2.vbs exits and control is returned to batch.bat.
- Batch end is shown in the command window (from batch.bat).
- batch.bat ends.
- notepad.exe may or may not be still running at this point. batch.bat had no knowledge of or control over notepad.exe.
This shows the synchronous command execution of a batch file and how a called, synchronous command, may, in turn, call an asynchronous command that is not visible to or controlled by the batch file and may or may not still be executing at the time the batch file ends.
Script Execution using start /wait
So what if we modify batch.bat to use start /wait to call our VBScripts?
@Echo "Batch start"
@start /wait cscript.exe //NoLogo "%~dp0vbs1.vbs"
@Echo "Batch middle"
@start /wait cscript.exe //NoLogo "%~dp0vbs2.vbs"
@Echo "Batch end"
Nothing changes because as noted previously, start /wait has no knowledge of or control over asynchronously spawned processes (notepad.exe in this case) that were spawned by called commands. Thus, the 15-step description above for batch.bat‘s execution is exactly the same with or without start /wait and is not a solution for the spawned process detection method problem. Below is a video showing what happens when we add start /cmd — it’s pretty boring though as it’s nearly the same as the above video (without the annotations). start /wait also launches new windows (these are clipped in the video) as well so you see these pop-up, but other than that, it’s exactly the same behavior.
There are a couple of straight-forward solutions here, one is quite simple but isn’t very elegant. For simple applications though, this simple solution may be enough. The other is also fairly simple but requires a bit of investigation and testing which may not really be necessary if you are satisfied with the simple solution.
The simple solution is to insert the command-line into a batch file and then add a timeout after the command-line. Place the batch file in the source directory along with the source files and call the batch files instead of the command-line.
"%~dp0mycommand.exe" /option1:abc /option2:xyz /switch
timeout /T 120 /NOBREAK
Basically, this runs the command-line and then sits waiting for a pre-determined amount of time (120 seconds in this example). The presumption is that any asynchronously spawned processes will be finished by the time the timeout ends. In many cases, this simple solution works just fine without any overhead and only adds a small amount of time to the deployment process. Of course, we are making a big assumption that the time we wait is sufficient. There many things that could influence this so that even after this time, the asynchronously launched process may not actually be finished yet. Only testing will help you identify a suitable timeout here.
Not So Simple
This solution also involves a simple batch file but instead of waiting a predetermined amount of time, waits until a specific process ends. for this, the process to watch is the asynchronously launched process of course. You need to use a tool like Process Monitor to discover what the name of this process is though so that you can insert its name into the batch file.
"%~dp0mycommand.exe" /option1:abc /option2:xyz /switch
timeout /t 10
tasklist | find /i "AynchProcessName" && goto :WaitLoop
Clearly, this solution is much more elegant as it removes the uncertainty of a predetermined timeout value and instead waits for the exit the asynchronously spawned process. Tasklist is quite flexible and there are other ways to determine which process to wait on. Unfortunately, parent process IDs are not available in tasklist otherwise this task could become very dynamic using tasklist.
start /wait is more or less useless for system administration. I’m sure someone will come up with a good example of its usefulness somewhere, but waiting for asynchronously launched commands ain’t one them.
Batch files synchronously execute commands.
Finally, as usual, do not believe everything written on the Internet and do not blindly follow code examples on the Internet either. Learn what they do, get rid of commands, functions, subroutines, etc. that aren’t needed, and when in doubt … test it! Watch the video below for a related chuckle — one of my favorite commercials of all time. Au revoir.