Using WiX 3.0 to create an MSI-based installer for Visual Studio project templates

A while back, I wrote a blog post where I introduced a set of Visual Studio 2005 and 2008 project templates that I created to supplement the ones that shipped in one of the earlier versions of the Windows Vista Media Center SDK.  I used WiX v3.0 to create the installer for these project templates.  The process of creating an MSI-based installer for Visual Studio project templates can be a bit tricky at first, so I wanted to walk through an example that demonstrates the steps to create an MSI to install and register Visual Studio project templates in order to hopefully make it easier for folks to create their own installers for Visual Studio project templates in the future.

I previously posted a set of instructions that describe how to use WiX v2.0 to create installers for Visual Studio project templates.  This time, I am going to post snippets of the WXS file directly in this post and explain what each of the pieces of this installer is doing in more detail.

I chose to use WiX v3.0 in this example for a couple of key reasons that will make more sense when we walk through the WXS code below:

  • The WixVSExtension contains a set of built-in detection and properties and custom actions for locating versions of Visual Studio and registering new project templates for use in the IDE.  This will save us a lot of work because we won’t have to author these properties and custom actions ourselves.
  • WiX v3.0 contains a new feature called smart cabbing (introduced by Rob Mensching in this blog post).  This feature will optimize package sizes by not including duplicate copies of the same files in the cabs created during the build process.  This is particularly useful for Visual Studio project templates because in many cases, the same project template zip file will be installed to different locations (such as the Visual Studio 2005 template location, the Visual C# 2005 Express template location, the Visual Studio 2008 template location, etc).  Smart cabbing allows us to carry only one copy of each project template zip file, even if we install it for multiple versions or editions of Visual Studio.

I have posted a downloadable copy of the example that I’m going to use in this blog post at https://cid-27e6a35d1a492af7.skydrive.live.com/self.aspx/Blog%7C_Tools/WiX%20Samples/wix%7C_sample%7C_vs%7C_project%7C_template%7C_installer.zip.  It contains the following information:

  • WiX source files that describe how to build the MSI
  • Project template zip files (the payload of the MSI)
  • A build script that calls Candle and Light (the WiX compiler and linker) to create the MSI
  • A built copy of the sample MSI

For the rest of this post, I will be walking through the syntax of the file wix_sample_vs_project_template_installer.wxs that is included in the above zip file and that I also posted as a standalone file at https://cid-27e6a35d1a492af7.skydrive.live.com/self.aspx/Blog%7C_Tools/WiX%20Samples/wix%7C_sample%7C_vs%7C_project%7C_template%7C_installer.wxs if you need to quickly reference it without downloading and extracting the entire zip file.

Import WiX Visual Studio 2005 detection properties and registration custom actions

WiX v3.0 includes a set of built-in detection properties and custom actions for the Visual Studio 2005 family of products.  When building your project template installer, you will need to decide which (if any) Visual Studio 2005 products that you plan to support, and then import the appropriate properties and custom actions.

In the example I created, the following statements are used to import the Visual C# 2005 Express Edition, Visual Basic 2005 Express Edition and Visual Studio 2005 Standard Edition and higher detection properties and registration custom actions:

<PropertyRef Id="VS2005_ROOT_FOLDER" />
<PropertyRef Id="VS2005_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED" />
<PropertyRef Id="VS2005_IDE_VB_PROJECTSYSTEM_INSTALLED" />

<CustomActionRef Id="VS2005Setup" />
<CustomActionRef Id="VCSHARP2005Setup" />
<CustomActionRef Id="VB2005Setup" />

Import WiX Visual Studio 2008 detection properties and registration custom actions

WiX v3.0 includes a set of built-in detection properties and custom actions for the Visual Studio 2008 family of products.  When building your project template installer, you will need to decide which (if any) Visual Studio 2008 products that you plan to support, and then import the appropriate properties and custom actions.

In the example I created, the following statements are used to import the Visual C# 2008 Express Edition, Visual Basic 2008 Express Edition and Visual Studio 2008 Standard Edition and higher detection properties and registration custom actions:

<PropertyRef Id="VS90_ROOT_FOLDER" />
<PropertyRef Id="VS90_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED" />
<PropertyRef Id="VS90_IDE_VB_PROJECTSYSTEM_INSTALLED" />

<CustomActionRef Id="VS90Setup" />
<CustomActionRef Id="VCSHARP90Setup" />
<CustomActionRef Id="VB90Setup" />

Define the Visual Studio 2005 and Visual Studio 2008 templates directory structure

The next step is to define the installation folder structure for the templates.  You need to define a default folder structure that matches the default installation directories for each of the versions of Visual Studio that you plan to support, and then also include logic to allow your project template installer to install the templates to the correct location if it detects that Visual Studio is installed to a non-default location.

In the example I created, the following folder structure is used for Visual Studio 2005 project templates.  The Id named VS2005_ROOT_FOLDER matches one of the built-in WiX properties defined above, and it will be set to a non-default location during installation if the user has Visual Studio 2005 installed to a non-default location on their system:

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="ProgramFilesFolder" Name="Program Files">
    <Directory Id="VS2005_ROOT_FOLDER" Name="Visual Studio 8">
      <Directory Id="Common7_2005" Name="Common7">
        <Directory Id="IDE_2005" Name="IDE">
          <Directory Id="ProjectTemplates_VS_2005" Name="ProjectTemplates">
            <Directory Id="CSharpProjectTemplates_VS_2005" Name="CSharp">
              <Directory Id="MceProjectTemplates_Csharp_VS_2005" Name="Windows Media Center"/>
            </Directory>
            <Directory Id="VBasicProjectTemplates_VS_2005" Name="VisualBasic">
              <Directory Id="MceProjectTemplates_VBasic_VS_2005" Name="Windows Media Center"/>
            </Directory>
          </Directory>
          <Directory Id="VcsExpress_2005" Name="VcsExpress">
            <Directory Id="ProjectTemplates_VCS_Express_2005" Name="ProjectTemplates"/>
          </Directory>
          <Directory Id="VbExpress_2005" Name="VbExpress">
            <Directory Id="ProjectTemplates_VB_Express_2005" Name="ProjectTemplates"/>
          </Directory>
        </Directory>
      </Directory>
    </Directory>
  </Directory>
</Directory>

In the example I created, the following folder structure is used for Visual Studio 2008 project templates.  The Id named VS90_ROOT_FOLDER matches one of the built-in WiX properties defined above, and it will be set to a non-default location during installation if the user has Visual Studio 2008 installed to a non-default location on their system:

<Directory Id="TARGETDIR" Name="SourceDir">
  <Directory Id="ProgramFilesFolder" Name="Program Files">
    <Directory Id="VS90_ROOT_FOLDER" Name="Visual Studio 9.0">
      <Directory Id="Common7_2008" Name="Common7">
        <Directory Id="IDE_2008" Name="IDE">
          <Directory Id="ProjectTemplates_VS_2008" Name="ProjectTemplates">
            <Directory Id="CSharpProjectTemplates_VS_2008" Name="CSharp">
              <Directory Id="MceProjectTemplates_Csharp_VS_2008" Name="Windows Media Center"/>
            </Directory>
            <Directory Id="VBasicProjectTemplates_VS_2008" Name="VisualBasic">
              <Directory Id="MceProjectTemplates_VBasic_VS_2008" Name="Windows Media Center"/>
            </Directory>
          </Directory>
          <Directory Id="VcsExpress_2008" Name="VcsExpress">
            <Directory Id="ProjectTemplates_VCS_Express_2008" Name="ProjectTemplates"/>
          </Directory>
          <Directory Id="VbExpress_2008" Name="VbExpress">
            <Directory Id="ProjectTemplates_VB_Express_2008" Name="ProjectTemplates"/>
          </Directory>
        </Directory>
      </Directory>
    </Directory>
  </Directory>
</Directory>

Define components to install each of the Visual Studio project templates

Now that you have defined the installation folder structure, the next step is to define Windows Installer components for each of the project templates you want to install.  There are a few considerations to keep in mind when doing this:

  1. If you plan to support multiple versions or editions of Visual Studio, it is a good idea to put installation conditions on each component so the component will only be installed if the version/edition of Visual Studio that it applies to is present on the user’s system.
  2. If you have any conditions in the component, you should set Transitive=”yes” for that component in your WiX authoring.  The transitive attribute will cause Windows Installer to re-evaluate the condition during a repair.  That is useful if the user installs your product, then adds a new Visual Studio edition and tries to repair your product.  Setting the transitive attribute will allow your product to add your project templates to the newly installed Visual Studio edition during a repair of your product.  If you don’t set the transitive attribute, the user would have to uninstall and then re-install your product to add your project templates to the newly installed Visual Studio edition.
  3. It is possible for a user to install Visual Studio but not install all of the language tools.  Therefore, if you have a C# project template, it is a best practice to not only check to see if Visual Studio is installed, but also to see if the C# project system is installed within Visual Studio.  The same holds true for project templates for other languages (VB, VC++, etc).
  4. If you plan to support both Visual Studio Standard Edition and higher and Visual Studio Express Editions, you should check for the existence of both the Visual Studio root directory (which can be created by either Visual Studio or Express Editions) and the Visual Studio executable for the exact  edition of Visual Studio that the template will be used by.  For example, in a Visual C# Express template, you would check for vcsexpress.exe, and in a Visual Studio Standard Edition or higher template, you would check for devenv.exe.

In the example I created, the following component is used to install a C# project template for use in Visual Studio 2005 Standard Edition and higher.  Note that it implements the 4 suggestions listed above by including a condition that checks for the VS 2005 root folder, the file devenv.exe and the Visual C# project system and marking the component as transitive:

<DirectoryRef Id="MceProjectTemplates_Csharp_VS_2005">
  <Component Id="MediaCenterApplicationFundamental_csharp.zip_VS_2005" Guid="{75055686-A7E2-401F-B288-2EC6067E7F7B}" DiskId="1" Transitive="yes">
    <Condition>(VS2005_ROOT_FOLDER AND VS2005DEVENV AND VS2005_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED)</Condition>
    <File Id="MediaCenterApplicationFundamental_csharp.zip_VS_2005" Name="MediaCenterApplicationFundamental.zip" KeyPath="yes" Source="MediaCenterApplicationFundamental_csharp.zip"/>
  </Component>
</DirectoryRef>

In the example I created, the following component is used to install the same project template as the one above, but to install it for use in Visual C# 2005 Express Edition instead of Visual Studio 2005 Standard Edition and higher.  Visual Studio Express Editions do not have selectable features, so there is no need to check that the Visual C# project system is installed in this component.

<DirectoryRef Id="ProjectTemplates_VCS_Express_2005">
  <Component Id="MediaCenterApplicationFundamental_csharp.zip_VCS_2005" Guid="{193E3610-90C5-442C-ACF7-D50C67B4B27C}" DiskId="1" Transitive="yes">
    <Condition>(VS2005_ROOT_FOLDER AND VCSHARP2005EXPRESS_IDE)</Condition>
    <File Id="MediaCenterApplicationFundamental_csharp.zip_VCS_2005" Name="MediaCenterApplicationFundamental.zip" KeyPath="yes" Source="MediaCenterApplicationFundamental_csharp.zip"/>
  </Component>
</DirectoryRef>

In the example I created, the following component is used to install the same project template as the one above, but to install it for use in Visual Studio 2008 Standard Edition and higher.

<DirectoryRef Id="MceProjectTemplates_Csharp_VS_2008">
  <Component Id="MediaCenterApplicationFundamental_csharp.zip_VS_2008" Guid="{70AE985D-706A-4C39-9417-20303DA605AC}" DiskId="1" Transitive="yes">
    <Condition>(VS90_ROOT_FOLDER AND VS90DEVENV AND VS90_IDE_VCSHARP_PROJECTSYSTEM_INSTALLED)</Condition>
    <File Id="MediaCenterApplicationFundamental_csharp.zip_VS_2008" Name="MediaCenterApplicationFundamental.zip" KeyPath="yes" Source="MediaCenterApplicationFundamental_csharp.zip"/>
  </Component>
</DirectoryRef>

In the example I created, the following component is used to install the same project template as the one above, but to install it for use in Visual C# 2008 Express Edition.

<DirectoryRef Id="ProjectTemplates_VCS_Express_2008">
  <Component Id="MediaCenterApplicationFundamental_csharp.zip_VCS_2008" Guid="{92C85077-C8EB-40F9-82F3-1887B9EA111E}" DiskId="1" Transitive="yes">
    <Condition>(VS90_ROOT_FOLDER AND VCSHARP90EXPRESS_IDE)</Condition>
    <File Id="MediaCenterApplicationFundamental_csharp.zip_VCS_2008" Name="MediaCenterApplicationFundamental.zip" KeyPath="yes" Source="MediaCenterApplicationFundamental_csharp.zip"/>
  </Component>
</DirectoryRef>

All 4 of the above components use the same Source value in the File element, so the WiX v3.0 smart-cabbing feature will optimize the build process to include only one copy of the project template .zip file in your installer payload.  This can save a lot of disk space when creating installers for Visual Studio project templates that target multiple versions/editions of Visual Studio.

Block installation if the necessary version of Visual Studio is not installed

In addition to including component conditions, I also suggest that you include blocking logic in your project template installer if none of the supported Visual Studio editions/versions are installed on the user’s system.  If your project template installer only supports a single edition and version of Visual Studio, this type of block is simple to author.  However, if you plan to support multiple editions and/or versions of Visual Studio, you need to carefully author the block so it won’t be overly aggressive.

In the example I created, the project templates support all of the following: Visual Studio 2005 Standard Edition and higher, Visual C# 2005 Express Edition, Visual Basic 2005 Express Edition, Visual Studio 2008 Standard Edition and higher, Visual C# 2008 Express Edition, and Visual Basic 2008 Express Edition.  The following logic will cause installation to block if none of these Visual Studio editions or versions are installed, but will allow install to proceed if at least one of them is installed (and in that case, it will rely on the component conditions described above to only integrate into Visual Studio versions/editions that are actually installed):

<CustomAction Id="CA_BlockIfVSIsNotInstalled" Error="!(loc.LaunchCondition_MissingVS)" />

<InstallExecuteSequence>
  <Custom Action="CA_BlockIfVSIsNotInstalled" Before="CostInitialize">(NOT VS2005_ROOT_FOLDER OR NOT VS2005DEVENV) AND NOT VCSHARP2005EXPRESS_IDE AND NOT VB2005EXPRESS_IDE AND (NOT VS90_ROOT_FOLDER OR NOT VS90DEVENV) AND NOT VCSHARP90EXPRESS_IDE AND NOT VB90EXPRESS_IDE AND NOT Installed</Custom>
</InstallExecuteSequence>

<InstallUISequence>
  <Custom Action="CA_BlockIfVSIsNotInstalled" Before="CostInitialize">(NOT VS2005_ROOT_FOLDER OR NOT VS2005DEVENV) AND NOT VCSHARP2005EXPRESS_IDE AND NOT VB2005EXPRESS_IDE AND (NOT VS90_ROOT_FOLDER OR NOT VS90DEVENV) AND NOT VCSHARP90EXPRESS_IDE AND NOT VB90EXPRESS_IDE AND NOT Installed</Custom>
</InstallUISequence>

The custom action is included in both the InstallExecuteSequence and the InstallUISequence so that the installation block will occur if the user runs the project template installer in UI mode or in silent mode.

Provide progress UI strings that will be displayed during installation

The Visual Studio custom actions that register new project templates can take a while to run, especially if the user has a lot of Visual Studio add-ins, packages and templates on their system.  To help improve the installation experience, I recommend defining strings that will appear in the progress UI while the Visual Studio registration custom actions are running in your project template installer.

In the example I created, the following ProgressText entries will provide descriptive text for each of the Visual Studio 2005 and Visual Studio 2008 registration custom actions.  Note that the Action names in these entries must match the names of the built-in WiX v3.0 custom actions that were imported earlier in this blog post or the progress text will not appear during installation.

<UI>
  <ProgressText Action="VS2005Setup" Template="[1]">!(loc.Devenv_Setup_VS2005_Description)</ProgressText>
  <ProgressText Action="VCSHARP2005Setup" Template="[1]">!(loc.Devenv_Setup_VCS2005_Description)</ProgressText>
  <ProgressText Action="VB2005Setup" Template="[1]">!(loc.Devenv_Setup_VB2005_Description)</ProgressText>
  <ProgressText Action="VS90Setup" Template="[1]">!(loc.Devenv_Setup_VS2008_Description)</ProgressText>
  <ProgressText Action="VCSHARP90Setup" Template="[1]">!(loc.Devenv_Setup_VCS2008_Description)</ProgressText>
  <ProgressText Action="VB90Setup" Template="[1]">!(loc.Devenv_Setup_VB2008_Description)</ProgressText>
</UI>

Add upgrade logic for future versions of the installer

This step is something I tend to add to all installers I create and is not specific to Visual Studio project template installers.  I typically use steps like the ones in this blog post to add logic to enable Windows Installer major upgrades.  This gives you more flexibility in case you decide to release a new version of your installer in the future and want it to replace older versions.