Building Visual Studio Setup Projects with TFS 2010 Team Build
- by Jakob Ehn
One of the most common complaints from people starting to use Team Build is that is doesn’t support building Microsoft’s own Setup and Deployment project (*.vdproj). When creating a default build definition that compiles a solution containing a setup project, you’ll get the following warning:
The project file "MyProject.vdproj" is not supported by MSBuild and cannot be built.
This is what the problem is all about. MSBuild, that is used for compiling your projects, does not understand the proprietary vdproj format defined by Microsoft quite some time ago. Unfortunately there is no sign that this will change in the near future, in fact the setup projects has barely changed at all since they were introduced. VS 2010 brings no new features or improvements hen it comes to the setup projects.
VS 2010 does include a limited version of InstallShield which promises to be more MSBuild friendly and with more or less the same features as VS setup projects. I hope to get a closer look at this installer project type soon.
But, how do we go about to build a Visual Studio setup project and produce an MSI as part of a Team Build process? Well, since only one application known to man understands the vdproj projects, we will have to installa copy of Visual Studio on the build server. Sad but true. After doing this, we use the Visual Studio command line interface (devenv) to perform the build.
In this post I will show how to do this by using the InvokeProcess activity directly in a build workflow template. You’ll want to run build your setup projects after you have successfully compiled the projects.
Install Visual Studio 2010 on the build server(s)
Open your build process template /remember to branch or copy the xaml file before modifying it!)
Locate the Try to Compile the Project activity
Drop an instance of the InvokeProcess activity from the toolbox onto the designer, after the Run MSBuild for Project activity
Drop an instance of the WriteBuildMessage activity inside the Handle Standard Output section.
Set the Importance property to Microsoft.TeamFoundation.Build.Client.BuildMessageImportance.High
(NB: This is necessary if you want the output from devenv to show up in the build log when running the build with the default verbosity)
Set the Message property to stdOutput
Drop an instance of the WriteBuildError activity to the Handle Error Output section
Set the Message property to errOutput
Select the InvokeProcess activity and set the values of the parameters to:
The finished workflow should look like this:
This will generate the MSI files, but they won’t be copied to the drop location. This is because we are using devenv and not MSBuild, so we have to do this explicitly
Drop a Sequence activity somewhere after the Copy to Drop location activity.
Create a variable in the Sequence activity of type IEnumerable<String> and call it GeneratedInstallers
Drop a FindMatchingFiles activity in the sequence activity and set the properties to:
Drop a ForEach<String> activity after the FindMatchingFiles activity. Set the Value property to GeneratedInstallers
Drop an InvokeProcess activity inside the ForEach activity.
FileName: “xcopy.exe”
Arguments: String.Format("""{0}"" ""{1}""", item, BuildDetail.DropLocation)
The Sequence activity should look like this:
Save the build process template and check it in.
Run the build and verify that the MSI’s is built and copied to the drop location.
Note 1: One of the drawback of using devenv like this in a team build is that since all the output from the default compilations is placed in the Binaries folder, the outputs is not avaialable when devenv is invoked, which causes the whole solution to rebuild again. In TFS 2008, this was pretty simple to fix by using the CustomizableOutDir property. In TFS 2010, the same feature is not avaialble. Jim Lamb blogged about this recently, have a look at it if you have a problem with this: http://blogs.msdn.com/jimlamb/archive/2010/04/13/customizableoutdir-in-tfs-2010.aspx
Note 2: Although the above solution works, a better approach is to wrap this in a custom activity that you can use in your builds. I will come back to this in a future post.