Introduction
In Parts 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 its 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.
History
- 22nd August, 2010: Initial post