I push out a number of mandatory SCCM (2007) packages to upgrade or deploy various apps. Most only run when no user is logged in to avoid problems but I’ve done some more complex ones that are designed to also be able to run when a user is logged on and using the app in question so they’ll get the upgrade faster. These pop up a notice to the user asking them to exit the app at their convenience so it can upgrade. It then watches for the process exiting before kicking off the upgrade. Occasionally though, the user acknowledges the message and then just logs off or reboots later, so that the script is interrupted and the upgrade and package as a whole fails.
I wanted a general-purpose batch file I could use in any package to wrap around the call to the package-specific upgrade script. I solved the problem by getting the batch file to register a scheduled task to run itself and then call the main upgrade script and then on return from that, delete the task. So in normal circumstances, no scheduled task is left, but if the script is interrupted by a reboot or shutdown, it will try again next time the PC is started.
Here’s the code:
@echo off cd %~dp0 for /f "delims=\" %%a in ("%cd%") do set FolderName=SCCM-%%~nxa schtasks.exe /query /TN %FolderName% >nul 2>&1 if ERRORLEVEL 1 ( schtasks.exe /create /RU System /SC onstart /TN %FolderName% /TR %0 >nul ) rem *********** rem Script Here rem *********** set RetCode=%ERRORLEVEL% schtasks.exe /delete /TN %FolderName% /F >nul exit %RetCode%
The initial cd command ensures that the current directory is set to the folder containing the script. %0 contains the full path to the script file and the ~dp modifier specifies just the drive and folder path. I always set the packages to be downloaded and run locally. More on why this cd is needed later.
The for command parses the current path %cd%, splitting it on backslashes with the result that it creates and stores a name that corresponds to the name of the folder the script is in, prefixed with “SCCM-“. The SCCM packages are stored in uniquely-named folders in c:\Windows\System32\ccm\cache so that particular foldername is useful as a meaningful but unique name for later in the script. It’s stored in the environment variable %FolderName%.
The next command uses schtasks.exe /query to see if a scheduled task already exists with this name we just generated. If the task doesn’t exist, an error is outputted so I redirect this using 2>&1 to the normal output stream (stream 1) which is in turn silently consumed with a >nul. If the task doesn’t exist, the errorlevel is set to 1 and this is detected in the subsequent if block. Inside this, a task is scheduled running under the System account (/RU System) – as are normal SCCM packages – and set to run on computer startup (/SC onstart) with the path to the script sitting in the SCCM cache passed in as the command to run (/TR %0). The name generated earlier is used as the name of the scheduled task (/TN %FolderName%). Normally when a package is run, the current directory is the folder containing the packages files in the cache. Under Windows XP, the scheduled task automatically sets the working directory to this same folder containing the command/script to be run. Under Windows 7, this does not occur and the path is set to System32. This is why there was the cd command at the top of the batch file.
At this point in the script, the actual desired package content is either inserted into the code (if a batch file) or called (if an external VBScript file) – eg CScript.exe //nologo Upgrade.vbs
After control returns to the batch file from the VBS file, (or the inserted batch file code has finished), we capture any return code (%ErrorLevel%) into an environment variable %RetCode%. Because at this point our package has completed, we can safely delete the scheduled task and exit the script, returning the captured return code to SCCM with Exit %RetCode%.
If whilst the main package script was running the user shut down, then the scheduled task will still be there to run on next startup and try it again.