Authoring a Custom Bootstrapper Package for Visual Studio 2005

 

Chris Smith
SDET, Visual Basic

May 2006

Applies to:
   Microsoft Visual Studio 2005

Summary: This article discusses how to integrate a custom application prerequisite into the Visual Studio 2005 Bootstrapper, allowing other developers to deploy the prerequisite with their applications. (8 printed pages

Contents

Introduction
Creating a Product Manifest
Creating a Package Manifest
Appendix

Introduction

The Visual Studio 2005 Generic Bootstrapper

The Visual Studio 2005 Bootstrapper is a technology that allows you to create an integrated setup experience for application prerequisites and ClickOnce or Windows Installer installations. With the Bootstrapper you do not have to worry whether or not end users have components like MDAC or the .Net Framework 2.0 installed on their machines—simply indicate which prerequisites your application depends on and the Bootstrapper will take care of installing them along with the application itself.

Visual Studio 2005 includes out-of-the-box capability to install several common prerequisites using the Bootstrapper, including .NET Framework 2.0, SQL Server Express 1.0, and others. The Bootstrapper is completely extensible, so it can be configured to install just about any redistributable. As a redistributable owner, providing a means for developers to seamlessly install your package with their application using the Bootstrapper promotes penetration of your redistributable, and simplifies servicing by providing an alternative to privately deploying assemblies in the redistributable.

This whitepaper will show how to integrate a custom application prerequisite with the Bootstrapper. This will allow other developers to deploy the prerequisite along with their applications.

What to deploy

The first step in creating a Bootstrapper package is to get a clear sense of the nature of the prerequisite that will be deployed. Here is a list of common questions to ask before creating a Bootstrapper package:

  • What needs to be deployed? An executable, an MSI, or both?
  • What command-line parameters need to be added?
  • Does the installation require a machine reboot? Under which circumstances?
  • How can you detect whether the package is already installed on the sytem?

With a high-level understanding of how to install your custom package you can then implement that install-logic in the form of a Bootstrapper package manifest.

Overview

The Bootstrapper requires two manifests: the product manifest (Product.xml) and the package manifest (Package.xml). I will describe both packages below, but generally the Product manifest is the core package—it describes how to install your custom prerequisite. The Package manifest describes the language-specific parts of your package such as the installation of language packs and localized error strings.

Creating a Product Manifest

Once you know the composition of your custom package, go to the Bootstrapper package folder located at [VS 2005 installation directory]\SDK\v2.0\Bootstrapper\Packages and create a new folder called "CustomPackage". There you can create the first manifest, called Product.xml.

The <Product> element, which is the root element of the product manifest, describes the XML namespace and ProductCode for your package.

<Product
xmlns="https://schemas.microsoft.com/developer/2004/01/bootstrapper" 
ProductCode="Custom.Bootstrapper.Package">

The ProductCode is simply a unique identifier for the package, so that some common relationships can be expressed at build time. For example, one way in which these relationships are specified is in the RelatedProducts element.

  <RelatedProducts>
    <DependsOnProduct Code="Microsoft.Windows.Installer.3.1" />
  </RelatedProducts>

Assume that this custom package requires that Microsoft Windows Installer 3.1 be installed on the target computer before our custom package. To do this, you state that this package requires that ProductCode Microsoft.Windows.Installer.3.1 be included as well. If someone builds a Bootstrapper that includes your custom package but not the dependent package, a build warning will be issued. Also, because your package depends on Windows Installer, the Bootstrapper will make sure that it is installed before the custom package.

Package Files

Next, add all the files that are part of the Bootstrapper package (to be redistributed along with the package). Add the following XML snippet to the Product.xml under the Product node:

  <PackageFiles>
    <PackageFile Name="CorePackage.msi"/>
  </PackageFiles>

Now the Bootstrapper manifest is aware of a file called CorePackage.msi. Make sure that the file is copied into your custom package's root directory alongside Product.xml.

Commands

The next step, and perhaps the most important, is to install your package. To do this, you need to add a Command element. The Bootstrapper will detect that the package file is a Windows Installer (.msi) file and automatically add the appropriate switch to install it silently (/qn) when it calls msiexec.exe to install the PackageFile. If the file extension is not .msi, for example, .exe, then the Bootstrapper will simply shell execute the package file.

  <Commands>
    <Command PackageFile="CorePackage.msi" Arguments="">
    </Command>
  </Commands>

ExitCodes

Once a redistributable has executed, it will return an exit code, typicaly to indicate success, failure, or the need for a reboot. Use the ExitCodes element to hook those numeric exit codes to error messages and actions.

To set the appropriate exit codes for most Windows Installer packages, add the following XML snippet to the product file:

      <ExitCodes>
        <ExitCode Value="0" Result="Success"/>
        <ExitCode Value="1641" Result="SuccessReboot"/>
        <ExitCode Value="3010" Result="SuccessReboot"/>
        <DefaultExitCode Result="Fail" String="GeneralFailure"/>
      </ExitCodes>

The Result attribute indicates what the Bootstrapper should do if that exit code is received. Success indicates the package installation succeeded and to continue installing packages; SuccessReboot is like Success but indicates that a reboot is needed in the future. Fail and FailReboot both stop future custom packages from being installed by the Bootstrapper and optionally display an error message.

Error Messages

For failure cases you typically want a specific message to describe the problem. The Bootstrapper addresses this by providing the optional String attribute on the ExitCode element. If the result is a failure, or a failure that also requires a reboot, the Bootstrapper will display the indicated string.

Note, however, that the value in the String attribute is only a variable name—the localized string itself should be defined as a Strings element. The defining of these strings will be covered below, in the explanation of package manifests.

Creating a Package Manifest

Now you need to create the second type of manifest, Package.xml. This manifest contains localization-specific aspects of your package, such as strings, end-user license agreements, and the installation of localized package files such as language packs.

A Bootstrapper package can have only one Product.xml but can have many Package.xml manifests, one for each language to which the package has been localized. Shared functionality across different localized versions of the Bootstrapper package should go into the product manifest, while language-specific aspects should go into their respective package manifests.

The first step in creating a package manifest is to create a directory in your CustomPackage folder with the name of the culture it describes, for example "EN" for English. In that new "EN" directory create a file named Package.xml.

The root element contains three unique attributes: Name, Culture, and LicenseAgreement.

<Package
  xmlns="https://schemas.microsoft.com/developer/2004/01/bootstrapper"
  Name="DisplayName"
  Culture="Culture"
  LicenseAgreement="eula.txt">

Name is the string that will be displayed on the Prerequisites dialog in Visual Studio 2005, the display name of your current package. It is also the name that appears in the EULA dialog displayed by the Bootstrapper. In the example we used a variable DisplayName, which you will later define in the Strings element.

The Culture attribute specifies the culture for this localized package manifest. The example uses a variable CultureName which, like the Name element, will map to a localized string defined elsewhere in the Package file.

The final attribute, LicenseAgreement, is optional. If your custom package is to be distributed with a EULA, then this attribute will map to the name of a package file that contains a plain-text license agreement. That package file will be loaded and displayed by the Bootstrapper, and the end user must accept the EULA prior to installation of the package. Each unique EULA for all Bootstrapper packages will be displayed once. So if several packages share the same EULA, users won't be prompted twice.

Strings

The Strings element allows you to define localizable strings for your Bootstrapper package. The Name attribute refers to a placeholder identifier, used elsewhere in the manifests.

  <Strings>
    <String Name="DisplayName">Custom Bootstrapper Package</String>
    <String Name="CultureName">en</String>
    <String Name="NotAnAdmin">You must be an administrator to install 
this package.</String>
    <String Name="GeneralFailure">A general error has occurred while 
installing this package.</String>
</Strings>

Now your Bootstrapper package manifest is complete. When you restart Visual Studio, you will see your custom package on the Publish Property page Prerequisites dialog.

Install Check

Although Visual Studio now recognizes your manifest, it is not yet complete. You can now deploy your custom package, but if you run the Bootstrapper again, it will install your package over and over again. You need to add the logic to detect whether the package is installed. This way, if you run the Bootstrapper again, it will skip your custom package's installation.

The Bootstrapper does this detection by setting properties using the InstallChecks attribute, then evaluating them at the command level with InstallConditions. You use InstallChecks to gather information from the target computer, such as registry keys, and then InstallConditions checks whether the package is installed or not. For example, a custom Bootstrapper package may already be installed if a particular registry key set by the installer is greater than "1.0.1.3."

There are multiple install-check types. The most appropriate for distributing an MSI is an MsiProductCheck.

An MsiProductCheck checks the registry to see whether an MSI package with the given GUID product code is installed. If so, the property value will be positive. (The exact value depends on the install state of the .msi file, be it advertised, absent, unknown, etc.). To add this check, use the following XML snippet:

<InstallChecks>
   <MsiProductCheck 
      Property="IsMsiInstalled" 
      Product="{XXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"/>
 </InstallChecks>

Install Condition

Now that the property IsMsiInstalled is defined, we still need to add some logic to use its value to determine whether or not to install the package. This is done through an InstallCondition BypassIf element. If a BypassIf element evaluates to true, then the command will be skipped.

We will also add a check to determine whether the current user is an administrator, because our package requires heightened privileges. We will do this with a FailIf element. If a FailIf evaluates to true, then the installation fails, displaying an optional error message.

<InstallConditions>
   <BypassIf 
      Property="IsMsiInstalled" 
      Compare="ValueGreaterThan" Value="0"/>
   <FailIf Property="AdminUser" 
      Compare="ValueNotEqualTo" Value="True"
      String="NotAnAdmin"/>
</InstallConditions> 

Now you might be wondering, where was the property AdminUser defined, as we did not create it with an install check? The Bootstrapper automatically defines several properties for you: VersionNT and Version9x (the current version of the host OS), AdminUser, ProcessorArchitecture, and VersionMSI.

Conclusion

Now that you have created a Bootstrapper package that installs a package file, defines localized strings, and creates previous-install detection logic, your package is complete. You can deploy ClickOnce and Setup projects with a dependency on your custom package or even share this package with other developers.

Appendix

Product.xml

<?xml version="1.0" encoding="utf-8" ?>
<Product
  xmlns="https://schemas.microsoft.com/developer/2004/01/bootstrapper"
  ProductCode="Custom.Bootstrapper.Package">

  <RelatedProducts>
    <DependsOnProduct Code="Microsoft.Windows.Installer.3.1" />
  </RelatedProducts>

  <PackageFiles>
    <PackageFile Name="CorePackage.msi"/>
  </PackageFiles>

  <InstallChecks>
    <MsiProductCheck Product="IsMsiInstalled" 
      Property="{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}"/>
  </InstallChecks>

  <Commands>
    <Command PackageFile="CorePackage.msi" Arguments="">

      <InstallConditions>
        <BypassIf Property="IsMsiInstalled" Compare="ValueGreaterThan" Value="0"/>
 <FailIf Property="AdminUser" Compare="ValueNotEqualTo" Value="True"
         String="NotAnAdmin"/>
      </InstallConditions>

      <ExitCodes>
        <ExitCode Value="0" Result="Success"/>
        <ExitCode Value="1641" Result="SuccessReboot"/>
        <ExitCode Value="3010" Result="SuccessReboot"/>
        <DefaultExitCode Result="Fail" String="GeneralFailure"/>
      </ExitCodes>
    </Command>
  </Commands>
</Product>

Package.xml

<?xml version="1.0" encoding="utf-8" ?>
<Package
  xmlns="https://schemas.microsoft.com/developer/2004/01/bootstrapper"
  Name="DisplayName"
  Culture="Culture"
  LicenseAgreement="eula.txt">

  <PackageFiles>
    <PackageFile Name="eula.txt"/>
  </PackageFiles>

  <Strings>
    <String Name="DisplayName">Custom Bootstrapper Package</String>
    <String Name="Culture">en</String>
    <String Name="NotAnAdmin">You must be an administrator to install this package.</String>
    <String Name="GeneralFailure">A general error has occurred while 
installing this package.</String>
  </Strings>
</Package>

© Microsoft Corporation. All rights reserved.