Thursday, May 31, 2007

Automatically launch a silent remote MSI installation from Team Build

If you need to maintain an up-to-date test machine with the latest installation of the product you are testing, this post shows you how you can trigger the automatic installation of the latest components as a result of a successful build.

The logic has been tested using Team Build. However, since the underlying technology is MSBuild, the information described here does not require Team Build.


MSBuild, and therefore Team Build, is unable to natively build Setup projects. In order to build a Setup project from within a Build Type you need to use the Visual Studio environment via the command line. This implies you will need to have access to Visual Studio. Preferably, the Visual Studio environment should be installed on the Build Machine.

In addition, because the Build Machine will be triggering the execution of the installation remotely, the user account under which the build process executes should have remote, administrative access to the Test Machine on which the installation will occur. Alternatively, you may use another user credentials to execute the installation but this means that these credentials will need to be part of the command that will launch the remote process, and therefore part of the Build Type project file.

Required Tools

Devenv - Executable used to launch Visual Studio. It can be used to build Setup projects from the command line, amongst other things. For details on all command line switches for Devenv refer to the MSDN online documentation. For details on building Setup projects using Team Build, please refer to the article Walkthrough: Configuring Team Foundation Build to Build a Visual Studio Setup Project.

PsExec - Allows execution of processes on other systems. PsExec is part of the Windows Sysinternals download and it is hosted at Microsoft TechNet.

Msiexec - The resulting MSI from a Setup project is not an executable. In order to perform actions outside of its default association, you require the use of a tool called Msiexec. This tool provides the means to install, modify, and perform operations on Windows Installer from the command line. Silent execution of these Windows Installer files can be accomplished through Msiexec.

Combining the Tools

In order to build a Setup project you need to use an Exec task in the AfterCompile target, as the following code snippet shows.

<Target Name="AfterCompile">
  <Exec Command="$(DevEnv) &quot;$(SolutionRoot)\Setup\Setup.vdproj&quot; /Build &quot;Release|Any CPU&quot;"/>

Once you have access to the resulting MSI file, you can use the following command to execute a remote installation. Note that you will need to replace the information such as Server Name (MyServerName in the example below) and paths with yours.

<Target Name="AfterCompile">
  <Copy SourceFiles="$(SolutionRoot)\Setup\Release\Setup.msi" DestinationFolder="\\MyServerName\C$\Temp\Setup" />
  <Exec Command="$(PsExec) \\MyServerName -w &quot;C:\Temp\Setup&quot; msiexec /i &quot;C:\Temp\Setup\Setup.msi&quot; -quiet"/>

Note: There have been cases where PsExec causes MSBuild to hang in the middle of a remote installation. I have yet to figure out why this may happen. If you run into this scenario and are able to fix it please share your experiences.

Alternate Scenario

There are times when you may want to trigger the installation on a remote machine from within your Visual Studio environment. For example, I do this often when I perform integration testing of MSBuild custom tasks, where I need to install the custom task in the Build Machine. Rather than doing it manually, I make it part of the Setup project's Post-build event.

I use the following script to automate the installation of my setups. In order for you to use the script, add it to your own Setup project's Post-build event and replace all the lines in bold with your own information.

echo ======================================================================
echo POSTBUILDSTEP for setup project.
set ServerPath=\\%ServerName%
set RemoteDrive=C
set MsiPath=Temp\YourProjectName
set MsiName=YourProjectNameSetup.msi
set RemoteMsiFilePath=%RemoteDrive%:\%MsiPath%
set RemoteMsiFileName=%RemoteMsiFilePath%\%MsiName%
set UncMsiFilePath=%ServerPath%\%RemoteDrive%$\%MsiPath%
echo Verifying/Creating temporary folder at "%UncMsiFilePath%"
if not exist "%UncMsiFilePath%" mkdir "%UncMsiFilePath%"
if errorlevel 1 goto BuildEventFailed
echo Uninstalling "%RemoteMsiFileName%" from server %ServerName%
psexec %ServerPath% -w "%RemoteMsiFilePath%" msiexec /uninstall "%RemoteMsiFileName%" -quiet
if errorlevel 1619 goto BuildEventContinue
if errorlevel 1606 goto BuildEventFailed
if errorlevel 1605 goto BuildEventContinue
if errorlevel 1 goto BuildEventFailed
echo Copying "$(BuiltOuputPath)" to "%UncMsiFilePath%"
xcopy /Y /R "$(BuiltOuputPath)" "%UncMsiFilePath%"
if errorlevel 1 goto BuildEventFailed
echo Installing "%RemoteMsiFileName%" on server %ServerName%
psexec "%ServerPath%" -w "%RemoteMsiFilePath%" msiexec /i "%RemoteMsiFileName%" -quiet
if errorlevel 1 goto BuildEventFailed
goto BuildEventOK
echo POSTBUILDSTEP for setup project FAILED
exit 1
echo POSTBUILDSTEP for setup project COMPLETED OK
echo ======================================================================

With the script in place, whenever I build the Setup project the script will uninstall any previous version and re-install the new version of my custom task. This technique can be applied to pretty much any type of Setup project.


Daniel Liedke said...

Very nice article! Thanks!

Anonymous said...

Nice, but what if you have many computers??
I have found great app for this reasons