- How to deploy a scripted application installation with SCCM 2012 - Mon, Sep 23 2013
- How to deploy an MSI package with SCCM 2012 - Mon, Aug 19 2013
- SCCM 2007 - General client troubleshooting tips - Tue, Aug 6 2013
Along with the added flexibility and control scripting provides, there are also added risks. The risks are increased because you (or the script writer) are more likely taking over control of the execution sequence, as well as taking control over the passing of the final result "code" (i.e. "exit code") back to the Client agent. The exit code is what the Configuration Manager Client reports back to the Management Point for aggregate status reporting.
- Reports show “Succeeded” status for Advertisement even though they failed to run.
- Reports show "Failed" status for Advertisement even though they were successful.
- Reports show "Failed" along with "runtime exceeded" indicating the installation hung and was aborted when it reached the maximum allowed runtime.
- Reports show "Running" status for Advertisement indefinitely
Potential causes ^
- Script code is passing back an incorrect result code for the action last executed
- Script code is passing back an explicit value rather than the actual result of the action last executed (almost the same as the previous cause)
- Script contains a prompt or a statement that causes it to pause during runtime
- Script launches processes without waiting for them to finish before continuing on
- Script code logic focuses only zero (0) return values as "successful". Windows Installer return codes such as 3010 and 1605 can also be treated as "successful" in some circumstances. (be careful with using "if exitcode <> 0 then…")
- Script code neglects handling of concurrent processes which may cause it to become stuck in a holding pattern
- Check that every task or process invoked within the script is serialized, rather than asynchronous. If you expect the script to wait for an invoked process to finish, make sure the code is doing it correctly!
- If the Advertisement is launching a script, verify that the logic is handling exit codes properly. In particular: Windows Installer status codes 3010 (successful, but waiting for a reboot), and 1605 (uninstall did not find the target product to uninstall).
- If your script needs to run a process which expects an existing process to be terminated (not running), or maybe it expects another process to be running before it's launched, be sure to handle that. You can start and stop tasks and processes very easily using the TSKILL shell command, as well Sysinternal's PSKILL.exe utility. You can also do this with script code using COM and WMI interfaces such as Win32_Process and WSH objects like Wscript.Shell and Wscript.Network.
- Check the script code for debugging or testing code that should have been removed or commented to suppress it from being executed.
- If the script is using an iterative statement (loops, do-while, etc.) make sure the logic allows for a “hard out” to break out of the cycle instead of running indefinitely. This is a well-known and very common problem with programming and has its own name: "the infinite loop".
- Check the script code for explicit path references. Sometimes this is necessary, but most of the time it's preferred to use "relative" path references. If the Package will be replicated over multiple Distribution Point servers, an explicit path reference will effectively negate the entire distributed environment and point all of the remote clients back to the explicit path. It will also impact the Distribution Method settings (fast-link, slow-link), especially when a client meets a condition that results in using "Download and run locally", rather than "Run from Distribution Point".
- Compare the Advertisement's Distribution settings against the link-speed of the client computer. If the computer is on a “slow” link, verify the Advertisement is set to either “Download Content” or “Do Not Run” rather than “Run from Distribution Point” (for slow links).
- If the script is BAT or CMD shell code, especially if it uses a “CALL” statement, error codes 255 or 259 might indicate an interactive shell request. In some cases, setting the Advertisement to “Allow users to interact with program” can resolve this. You may also want to merge the script code to avoid using “CALL” to see if that helps instead.
Serial vs. asynchronous execution ^
It's possible within a script to launch one process and then launch another without waiting for the first process to complete (asynchronous). If the first process forces an immediate reboot, it could terminate the completion of subsequent processes and may interrupt event reporting (exit code returned to the Configuration Manager Client process).
In general, it's usually preferred to run each process serialized, to allow for evaluating the exit code (result status) before continuing on to the next. In most cases, if one process fails to complete successfully, you wouldn't want to continue on and launch the next one, at least not without performing some additional actions first.
My examples use VBscript since it's relatively more common with Configuration Manager 2007 packaging than PowerShell. In fact, many SCCM 2007 sites still rely on BAT/CMD scripts. You could do this much more compactly using PowerShell, but Configuration Manager 2012 provides a much more robust PowerShell deployment environment than Configuration Manager 2007.
If you do not have the luxury of owning a license of AdminStudio or InstallShield, fear not, there are workarounds. One such workaround is scripting. You can use the COM interface to monitor the Windows Process stack and effectively make your script code "wait" until a specific process terminates. This method goes by many names, such as "loop and wait", "watching the clock", or "rock and roll" (I'm sure there are many others).
Here's an example using VBscript that watches for a process named "acme_setup.exe" in the Process stack. If it finds it currently running, it uses a WMI event monitor to wait until it's terminated and then moves on:
Const strComputer = "." Const strProcName = "acme_setup.exe" Dim objWMIService, colItems Dim query, objItem, queryMonitor Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\CIMV2") query = "SELECT ProcessId FROM Win32_Process WHERE Name='" & _ strProcName & "'" Wscript.Echo "info: checking if " & strProcName & " is currently running..." Set colItems = objWMIService.ExecQuery(query,,48) For Each objItem in colItems intProcessID = objItem.ProcessId Next If intProcessID <> "" Then Wscript.Echo "info: " & strProcName & " process ID is " & intProcessID Wscript.Echo "info: waiting for " & strProcName & " (" & _ intProcessID & ") to be terminated..." queryMonitor = "SELECT * FROM " & _ "__InstanceDeletionEvent WITHIN 1 " & _ "WHERE TargetInstance ISA 'Win32_Process'" Set colMonitoredProcesses = _ objWMIService.ExecNotificationQuery( queryMonitor ) Do Until processEnded = True Set objLatestProcess = colMonitoredProcesses.NextEvent If objLatestProcess.TargetInstance.ProcessID = intProcessID Then processEnded = True End If Loop If processEnded = True Then Wscript.Echo "info: " & strProcName & " terminated." End If Else Wscript.Echo "info: " & strProcName & " is not currently running." End If
Some of you may be wondering how you would use this in a real world situation. Let's say you need to launch a vendor installer and wait for it to complete before continuing. Maybe you'd like to be sure that it actually completed successfully so that your next step is doing something to the results. For example, maybe after the installation finishes, you need to overwrite some configuration files in the installation target directory, or update a particular set of Registry key values. It wouldn't make much sense to attempt either of those tasks if the installer failed (or never ran at all).
Note: The example shown above is not intended for production use, but merely to demonstrate one possible method for monitoring a running process until it terminates before continuing on with script execution. Test thoroughly before using. Use at your own risk.
The WSH Run() method ^
Scripts that use the Windows Scripting Host Run() method (Wscript.Shell object), should specify the third parameter as True (wait for completion), rather than False (do not wait). Here's a short VBscript example:
Set objShell = CreateObject("Wscript.Shell") exitcode = objShell.Run("msiexec.exe /i myInstaller.msi /qn", 7, True)
One of the most common scenarios where I've seen this happen is where the programmer decides to use an evaluated variable for the third parameter which is derived from a condition. In cases where the condition sets the variable to False (or any "non-True" result) it will effectively run the shell process without waiting for it to complete before moving on. It's always best to use an explicit True or False (preferably True).
Command shell scripts ^
When using the START command within a BAT or CMD script, be sure to include the /WAIT parameter, but keep in mind that whatever is being launched will control the behavior of the "wait" condition. This means that using "/WAIT" is not a guarantee it will "wait" for the entire process to finish. In other words, and this is very common: If you launch an installer utility, and it in-turn launches sub-processes, but exits from the call stack while the sub-processes continue running, the START command will only see the first process (the one it called) as completed, and continue on. The best way to insure this doesn't occur in your script is to follow the two rules below:
- Write log output from within your script on EVERY step it performs, even before and after each START command is issued. Watch the log file interactively using a live query utility like Trace32.exe or CMtrace.exe.
- Test... Test... Test, and Test again!
One more note of caution ^
As with the potential "wait" issue described above for Command Shell Scripts, the same may be true for the WSH Run() method. That's right. Not because it won't actually "wait" for the process it launches to finish its job, but because that process may in-turn launch other processes asynchronously. In fact, this is not uncommon for processes that create and start Windows Services. The only way to effectively verify this condition is to monitor the process stack in a test environment.
Tools like Sysinternals' Process Explorer, Process Monitor, PSLIST and so forth can be of enormous help in identifying and monitoring such branching of processes. The first figure below shows PSLIST in action using the -T option (tree-view display). The figure below that shows Process Explorer monitoring a slightly more complicated process tree.
In the Process Explorer example above, the installation of a software product is initiated with a .CMD script. The script first checks if Microsoft .NET Framework 4.0 is installed, and then checks if a required update for .NET 4.0 is installed. Not having detected the required update, it then launches the installer (the nested process with name that begins with "NDP40-KB2468871…"), which in-turn extracts a payload of installer binaries, and then launches the installation itself using a "setup.exe" file. The "setup.exe" file in-turn runs a series of Windows Installer packages (the "msiexec.exe" processes highlighted above).
I could have scoured through the vendor's installation logs, and hours and hours of picking out which log caused which other process to launch and then find its log and continue on. But seeing the "real time" processes unfold before me not only saves time, but immediately points me in the right direction.
The point of this example is to demonstrate that without the proper tools to diagnose and catch this type of scenario, it can be extremely difficult to accurately trace what's really going on with an installation sequence. Any nested process can encounter a problem that can cause the entire sequence to fail, or worse: partially fail.
This is why it is absolutely crucial to include as much status logging as possible throughout every script. I cannot emphasize that enough.
My personal recommendation is to print out a statement of what the next step is going to do, and how it will try to do it, and then print out the results of that step if and when it is completed, whether successfully or otherwise.
As I said, script give you enormous flexibility and control, but they don't negate the need to perform due diligence.
Helpful links ^
- Troubleshooting Software Distribution Issues
- (MSDN) RUN Method (Windows Script Host)
- (MSDN) Win32_Process Class
- (TechNet) Custom Error Codes for Configuration Manager 2007
- (TechNet) START command reference
- Windows XP "Start" command reference
- Search the web for "config manager package scripting"