My Photo

Technical Architect with over 15 years experience in a wide range of technologies.

@TheCodeKing

rss feed

Creating a localized Windows Installer & bootstrapper: Part 4 | Sunday, August 22, 2010

Introduction

In Part 1,2 and 3 we created a localizable MSI installer using the Wix tools. We also created conditions to ensure that the application can only be installed on Dell hardware running Windows 7 and the Microsoft .NET Framework 4 Client Profile. In our original requirements we stated that this must be deployable as a single file, and should install any prerequisites specifically .NET 4. To achieve this we need to create a bootstrapper that will preform the prerequisites installations from unmanaged code. The bootstrapper will be deployable as a single self-contained EXE with the original MSI file embedded inside.

I investigated many utilities for creating a bootstrapper, including the Wix SETUPBLD tool, and the MSBuild GenerateBootStrapper task. In the end I settled on a powerful free utility called dotNetInstaller. This allows the creation of highly configurable and localizable bootstrappers which can be compiled from the command line.

DotNetInstaller

DoNetInstaller includes several utilities for authoring and compiling the bootstrapper. The bootstrapper is compiled from a single XML config file by the InstallerLinker.exe tool. To author the configuration file an editor is provided in the form of InstallerEditor.exe.

Using the editor language variations can quickly be set up through the UI and prerequisites defined by adding child nodes. Any prerequisite installers can either be embedded directly into the bootstrapper or a URL provided from which they will be downloaded and installed. In our case we embed the Web Installer for the Microsoft .NET Framework 4 Client Profile. This will launch the installation process and download the necessary components on demand to keep the size of the bootstrapper down.

Once the prerequisites are installed dotNetInstaller allows us to define a custom command to launch our own embedded MSI file using the complete_command property. We can set up different commands for different languages and therefore explicitly pass a transform to the MSI file after the prerequisite checks. If all prerequisites are found we can hide the bootstrapper interface altogether and simply launch the MSI file.

DotNetinstaller includes it's own auto-language detection based on either user or system settings. To leverage this we can refer to a #LCID token in the config file that will evaluate to the detected LCID at runtime. This allows us to pass the LCID explicitly to our MSI in order to launch a particular language variation.

Through the command line we can still inject properties into our MSI for overriding the hardware check for example. The syntax for this would be:

setup.exe /CompleteCommandArgs "SKIPCHECK=1"

The features of dotNetInstaller are too many to cover in this article but there is plenty of documentation and it's very feature rich.

Build Automation

To complete our bootstrapper we really want to integrate this into our build script and inject parameters such as product name and version. To achieve this we can use a NAnt script that performs token replacement on the dotNetInstaller config file prior to compilation. I could have equally used XML manipulation, but NAnt makes the token replacement somewhat easy.

This is implemented using a filterchain with the NAnt copy task as shown below. The original config file is copied to a new location where it is modified and then parsed to the installer linker.

<copy tofile="${installer.dir}\obj\${build.configuration}\Configuration.xml"
          file="${installer.dir}\bootstrapper\Configuration.xml" overwrite="true"
          inputencoding="utf-8"
          outputencoding="utf-8"
          >
      <filterchain>
        <replacetokens>
          <token key="APPLICATION_MANUFACTURER" value="${project.companyname}" />
          <token key="APPLICATION_NAME" value="${app.name}" />
          <token key="APPLICATION_VERSION" value="v${version}" />
          <token key="INSTALLER_VERSION" value="${installer.version}" />
          <token key="INSTALLER_COPYRIGHT" value="Copyright © ${datetime::get-year(datetime::now())} ${project.companyname}" />
          <token key="MSI_PATH" value="${installer.dir}\bin\${build.configuration}\Setup.msi" />
        </replacetokens>
        <tabstospaces />
      </filterchain>
    </copy>

Summary

In this final part we have fulfilled our original requirements and provided a build script for dynamically generating a localizable installer & self-contained bootstrapper. All of the source code and scripts are provided as a download on this page, and I hope it's useful for demonstrating the technologies in a real-world scenario. When building the demo solution don't forget to replace the GUIDs in the source Config.wsi file with your own unique ids.


No comments:

Post a Comment