We wrap pretty much every software installation (or even just a static configuration setting) in an Install.cmd and an Update.cmd pair of command scripts. These ensure consistent execution whether it is managed by SCCM, Intune (via a self-extracting EXE), MDT, or manual execution.

Script Preamble

The script starts out with a common preamble that ensures quiet operation, localizes variables to the scope of the script, sets a background color, and indicates the software and version to be installed.

@echo off
rem Use your text editor to replace SoftwareName and Version
rem Also, update the A.B.C.D version check value
setlocal
color 2f
title SoftwareName Version
echo.
echo. ************************
echo. * SoftwareName Version *
echo. ************************
echo.

Determine Current State

Next, a check is performed to see if we need to "abort" because the current or a newer version is installed, if we need to uninstall any older versions, or if this is a new, virgin install. The version is checked using a VBScript file (one of the CheckVersion.vbs files) that makes it a little easier to directly determine where to obtain the location of the version information (since pulling it from the registry may be in a variable location or if you have to pull it from a file header).

echo Checking for current version of SoftwareName...
cscript //NOLOGO CheckVersion.vbs /MinVer:A.B.C.D
set DT=%ERRORLEVEL%
if %DT% GEQ 4 (
 echo. The current or a newer version is installed.
 call :MarkupRegistry
 exit /b 0
)
if %DT% EQU 3 (
 echo. An older version is installed
 goto :UninstallPrevious
)
if %DT% EQU 2 (
 echo. SoftwareName is not installed on this computer.
 goto :Install
)
echo. An error occurred during detection.
echo. Assuming that we have to uninstall prior versions.

It is here, where the only difference is between the Install.cmd and Update.cmd. Specifically, the handling of the return code of 2 is different (the case where the software is not installed). In Update.cmd, that handler is the following:

if %DT% EQU 2 (
 echo. SoftwareName is not installed on this computer.
 goto :MarkupRegistry
)
if %DT% EQU 2 (
 echo. SoftwareName is not installed on this computer.
 goto :MarkupRegistry
)

Uninstall Previous Versions

The next section of the script handles the uninstallation of any prior versions of the software. For MSI packages, you can usually call the WMIC command that I have suggested here (updating the where clause with appropriate values). For other items, you'll need to identify the uninstaller necessary and any command line switches necessary to run it silently without reboots. Fortunately, this WMIC command works great for uninstalling most software. In cases where a reboot is needed (because of services or file locks or other reasons), the reboot typically happens automatically. You can try to prevent that by killing servicese or processes just before the WMIC command, but if that doesn't work in your testing, then you'll need to author a different method to perform the uninstallation.

:UninstallPrevious
echo.
echo Uninstalling previous versions of SoftwareName...
wmic product where "Name like '%SoftwareName%'" call Uninstall /Nointeractive

An alternative could be the following. This is a bit more complex, but it allows you to provide command-line switches to the MSIEXEC command to facilitate uninstallation. Because values cannot be returned from calling internal "subroutines" in command-script files, we create a special RebootFlag.txt file in the TEMP directory to track whether the uninstallation requests a reboot be handled. That will get evaluated near the end in determining which exit code to return to the calling process (and then allow SCCM or InTune to process the reboot its own way).

This version still uses WMIC to get the list of installed products (so this works only for MSI based installs), with the results filtered by the FIND (if you don't you get an extra blank line that causes a weird run through the HandleUninstall subroutine). That check in the HandleUninstall routine for the "{" character is just a safety check to ensure that we have a GUID (which we should because of the FIND anyway). You could remove it, but it causes no real harm.

:UninstallPrevious
echo.
echo Uninstalling previous versions of SoftwareName...
if exist "%TEMP%\SoftwareName.RebootFlag.txt" del "%TEMP%\SoftwareName.RebootFlag.txt" 1>nul
for /f "tokens=1 usebackq" %%i in (`wmic product where "Name like '%%SoftwareName%%'" get IdentifyingNumber ^| find "{"`) do call :HandleUninstall %%i
goto :FinishedUninstalls
:HandleUninstall
set ID=%1
if NOT .%ID:~0,1%. EQU .{. goto :EOF
msiexec /X %ID% /qb!- /norestart
if %ERRORLEVEL% EQU 3010 echo NeedToRestart > "%TEMP%\SoftwareName.RebootFlag.txt"
goto :EOF
:FinishedUninstalls

Install the Software

Now that we have a "clean" system, we are ready to perform the installation of software. Generally, this is a single piece of software. If this needs to be a chain of software, you might need to update this section to install the first part, check the result (ERRORLEVEL) and, if okay, continue on with the next part. You also need to retain that exit code so that if a reboot is required by any single installation, you can determine that and return a reboot result code to the calling process (SCCM or InTune) for them to handle the actual reboot.

:Install
echo.
echo Installing SoftwareName...
start /wait "" msiexec /i "%~dp0SoftwareName.msi" /qb!- /norestart
set EC=%ERRORLEVEL%
echo.  Returned exit code %EC%
if %EC% EQU 3010 goto :Configure
if %EC% EQU 0 goto :Configure
goto :ErrorExit

Configure the Software

Once we know that we have the software installed, if there are any steps necessary to configure the software (registry settings, file changes, etc.), they would go in this section.

:Configure
echo.
echo Configuring SoftwareName...
rem Place any configuration of the software necessary here
rem REG ADD HKLM\SOFTWARE\SomeKey\SomeOtherKey /v CriticalSetting /t REG_SZ /d "CriticalValue" /f 1>nul
call :MarkupRegistry

Process the Exit

The next section (although, not the last) is to process the exit of the script. Here, you'd evaluate if you need to send a reboot exit code (3010) or not to the calling process and then exit out.

:ErrorExit
if exist "%TEMP%\SoftwareName.RebootFlag.txt" set EC=3010
if exist "%TEMP%\SoftwareName.RebootFlag.txt" del "%TEMP%\SoftwareName.RebootFlag.txt" 1>nul
echo.
echo Returning exit code %EC% to calling program.
color 1f
timeout /T 5
color
exit /b %EC%

Registry Markup for Intune

This last section was created specifically to support Intune and to allow Intune to determine if a piece of software is installed. Intune (as of October 2015) only allows up to a combination of three things to determine if a piece of software is installed: An MSI identifying number GUID, the existence of a particular file in a specific path, or the existence of a specific registry key. It is recommend that you use both a file existence and a registry key existence test. This handles the software whether it is an MSI based install or not. Additionally, if a user manually uninstalls the software then the test will fail (as the file will no longer exist) and it will be re-offered or re-installed. If a user upgrades the software, the test will continue to pass unless the publisher changes the name of the file you are searching for (which is not typical) so your older version wouldn't be re-pushed over and over again. If you need to ensure that an older version is what is installed, then you could probably use the MSI GUID check (which is usually unique per software version), but that would be atypical.

:MarkupRegistry
echo.
echo Updating registry data for InTune tracking...
REG DELETE "HKLM\SOFTWARE\PrivateData\SoftwareName" /F /REG:64 1>nul 2>nul
REG ADD "HKLM\SOFTWARE\PrivateData\SoftwareName\Version" /ve /d "InTune" /F /REG:64 1>nul
echo. Completed
color