Windows Installer WiX Bootstrapper Notes

Overview

From the WiX Bundle documentation:

A bundle is a collection of installation packages that are chained together in a single user experience. Bundles are often used to install prerequisites, such as the .NET Framework or Visual C++ runtime, before an application's .MSI file. Bundles also allow very large applications or suites of applications to be broken into smaller, logical installation packages while still presenting a single product to the end-user.

To create a seamless setup experience across multiple installation packages, the WiX toolset provides an engine (often referred to as a bootstrapper or chainer) named Burn. The Burn engine is an executable that hosts a DLL called the "bootstrapper application". The bootstrapper application DLL is responsible for displaying UI to the end-user and directs the Burn engine when to carry out download, install, repair and uninstall actions. Most developers will not need to interact directly with the Burn engine because the WiX toolset provides a standard bootstrapper application and the language necessary to create bundles.

Creating bundles with the WiX toolset is directly analogous to creating Windows Installer packages (.MSI files) using the language and standard UI extension provided by the WiX toolset.

Bootstrapper command line switches

Switch
Description

-q, -quiet, -s, -silent

silent install

-passive

progress bar only install

-norestart

suppress any restarts

-forcerestart

restart no matter what (I don't know why this is still around)

-promptrestart

prompt if a restart is required (default)

-layout

create a local image of the bootstrapper (i.e. download files so they can be burned to DVD)

-l, -log

log to a specific file (default is controlled by bundle developer)

-uninstall

uninstall

-repair

repair (or install if not installed)

-package, -update

install (default if no -uninstall or -repair)

Defining your own switches

$ BootstrapperSetup.exe /i /passive MyBurnVariable1=1 MyBurnVariable2=2

Then in your Bundle.wxs:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi" xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">

    <!-- Define and set default values -->
    <Variable Name="MyBurnVariable1" bal:Overridable="yes" Type="numeric" Value="0" />
    <Variable Name="MyBurnVariable2" bal:Overridable="yes" Type="numeric" Value="0" />

    <Chain>
        <!-- Using it as a condition for installing a package-->
        <MsiPackage
            Id="MyFirstMsiPackage"
            SourceFile="first.msi"
            InstallCondition="MyBurnVariable1 = 1">
        </MsiPackage>

        <!-- Passing and setting it as a Windows Installer Property -->
        <MsiPackage
            Id="MySecondMsiPackage"
            SourceFile="second.msi">
            <MsiProperty Name="MY_PROPERTY" Value="[MyBurnVariable2]" />
        </MsiPackage>
    </Chain>

Logging

The bootstrapper will by default generate the logs for each MsiPackage you define in your bundle chain. If you use the logging switch (-l -log) and define the logging file name, the logs will be populated in the directory where it was invoked.

If the logging switch was not defined on the command line, the installation logs will reside in C:\Users\%username%\AppData\Local\Temp (this is Burn built-in variable value of $(var.TempFolder) and on Windows, corresponds to the environment variable %TEMP%). If performing multiple installs and uninstalls, the installation log files will be overwritten.

Customizing logging location

Create a Burn variable and set the MsiPackage/@LogPathVariable attribute to this variable.

<Variable
    Name="CustomLogDestination"
    Type="string"
    Value="PathToYourCustomLogDir" />

<Chain>
    <MsiPackage
        Id="Installer"
        LogPathVariable="CustomLogDestination" />
</Chain>

Adding packages to the Bootstrapper

The Bootstrapper allows both local and remote packages that need to be installed - these can be defined in any order with strict conditions.

A common method of reducing the installer size is to have the installer download any pre-requisite software. WiX supports the common .NET Framework versions to be downloaded remotely - be mindful of where the installer is installed in. If the environment has many network restrictions e.g. due to security, then it may be wise to embed the packages into the installer.

For remote packages, WiX will verify the SHA-1 hash of the downloaded file with the package in your build environment i.e. you need to have downloaded the remote package and place it in a location known to the WiX Bootstrapper project so it can calculate the hash and use it in the installation later.

<Bundle>
  <!-- Check the Registry -->
  <util:RegistrySearch
    Id="SqlReg"
    Root="HKLM"
    Key="SOFTWARE\SQL"
    Value="$(var.InstanceName)"
    Result="exists"
    Variable="SqlExists"
    Win64="yes" />

  <Chain>
      <ExePackage
        Id="Sql"
        DisplayName="SQL"
        DownloadUrl="https://somewhere/sql.exe"
        PerMachine="yes"
        Name="sql.exe"

        InstallCommand='/ACTION="Install" /FEATURES=SQL /IACCEPTSQLSERVERLICENSETERMS /INSTANCENAME="$(var.InstanceName)" /QS'
        UninstallCommand="/Action=Uninstall /INSTANCENAME=$(var.InstanceName) /FEATURES=SQL  /Q /HIDECONSOLE"
        DetectCondition="SqlExists"
        InstallCondition="NOT SqlExists">
      </ExePackage>

      <!-- MSI packages are much easier as the install/uninstall/detection are all handled -->
      <MsiPackage
        Id="SomeMSi"
        SourceFile="$(var.ProjectDir)\installer.msi"
        DownloadUrl="https://installer.msi"/>
  </Chain>
</Bundle>

Rollback boundary

The Burn RollbackBoundary allows the Bootstrapper installation to rollback to only a certain point - the boundary - if any packages fail to install.

Let's consider an example:

For your bundle, you have 3 packages in it in a chain These packages must succeed before proceeding to the next one

<Chain>
    <MsiPackage Id="FirstMsi" Vital="yes" />

    <MsiPackage Id="SecondMsi" Vital="yes" />

    <!-- Rollback boundary -->
    <RollbackBoundary />

    <MsiPackage Id="PackageFail" Vital="yes" />
</Chain>

So in this case, if MsiPackage/@Id=PackageFail installation fails - the bootstrapper will normally rollback all packages in the chain i.e. SecondMsi -> FirstMsi will be uninstalled/rolled-backed in that order.

But because the RollbackBoundary exists - it will only rollback the MsiPackage/@Id=PackageFail. This means that packages FirstMsi and SecondMsi will remain on the machine. Be mindful that this is leaving state on the machine and can only be removed manually!

Customizing the built-in Bootstrapper UI

Adding a license agreement and a custom theme to the Bootstrapper UI is possible. Although this is limited, it is usually sufficient for most purposes. See WiX Working with the standard Bootstrapper Application.

<Bundle>
    <!-- WiX built-in Bootstrapper Application -->
    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">

      <!-- Include custom theme, license and logo -->
      <bal:WixStandardBootstrapperApplication
        LogoSideFile="Resources\Themes\SideImage.png"
        ThemeFile="Resources\Themes\CustomTheme.xml"
        LocalizationFile="Resources\Themes\CustomTheme.wxl"
        ShowVersion="yes"
        ShowFilesInUse="no" />

      <!-- Localization and resource files -->
      <Payload Id="thm_en" Name="1033\thm.wxl" Compressed="yes" SourceFile="Resources\1033\CustomTheme.wxl" />
      <Payload Id="thmx_en" Name="1033\thm.xml" Compressed="yes" SourceFile="Resources\1033\CustomTheme.xml" />
      <Payload Id="thmb_en" Name="1033\logo.png" Compressed="yes" SourceFile="Resources\1033\SideImage.png" />

    </BootstrapperApplicationRef>
</Bundle>

Add/Remove Programs

You can have the Bootstrapper installer show in the ARP whilst your MSI Installer to be hidden! Make sure that you have configured this correctly because you do not want to hide the Bootstrapper installer. This allows you to not have the "same" two products in ARP and the Bootstrapper can handle removing any install pre-requisite installations.

<!-- Make the Boostrapper show up in ARP -->
<Bundle
    Name="$(var.ProductName)"
    Version="$(var.Version)"
    DisableRemove="no">

  <Chain>
    <!-- Hide the product from ARP but allow it to be removed by the bootstrapper! -->
    <MsiPackage
        Id="Installer"
        DisplayInternalUI="yes"
        Visible="no"
        Permanent="no" >
  </Chain>
</Bundle>

Signing the Bootstrapper

See WiX Insignia.

Writing your own Bootstrapper application

Instead of using or modifying the existing WiX Bootstrapper application, you could create your own - before this sounds really good, it's important to raise the question "how necessary is it to create a custom UI and will it be worth the time investment?" Most cases it will be no.

The main motivation for the Burn Engine (framework) is to have a consistent approach on how to interact with the Windows Installer APIs - before, everyone was doing their own way of things.

WiX provides a basic standard Bootstrapper Application which runs on top of the Burn Engine - but it's quite restrictive, hence why many people may want to create their own Bootstrapper Application and customize its UI (e.g. Visual Studio installer that is build with WPF and .NET).

Resources

Tips

Binding your MSI Installer version to the Bundle version

If you want your Bootstrapper version to match your MSI Installer. This is generally recommended as there is less confusion and upgrades are less prone to error.

<!-- A good way is to bind it to your Installer if desired -->
<Bundle
    Name="$(var.BundleName)"
    Version="!(bind.packageVersion.YourInstaller)">
</Bundle>

Hidden Burn variables

These won't show up in the installer logs, best not to use this with passwords...

<Variable Name="SomeSensitiveString" Type="string" Value="SomethingSecret" Hidden="yes" />

Extracting packages from your Bootstrapper

$ dark.exe BootstrapperInstaller.exe -x [OutPutFolder]

Last updated