It’s one thing to develop and test a Windows application; it’s quite another to bundle it up into a nice executable that installs correctly on all the different Windows versions that you need to support. I’d like to guide you through the process that I wish I had when I was creating my first installer.
Step 1 – Use your bare hands.
Start by taking your target directory (the one containing your compiled .exe
and a bunch of .dll
files) and plopping the whole thing onto a Windows machine. Then, manually install any prerequisites, like the particular version of the .NET framework that the app requires. It’s helpful to perform this step on an older platform that’s more likely to contain compatibility issues, e.g. Windows XP.
Now run the executable directly, and make sure it works. If there are problems, the machine may still be missing prerequisites. Double-check that the platform (x86 vs. x64) and .NET framework target of your build match up with the configuration of the machine you’re testing on. An installer won’t fix problems encountered here, so be sure to fix any issues before moving on.
Step 2 – Build it.
Next, we create an actual installer. Visual Studio lets us do this in two easy steps:
- Add a new “Visual Studio Installer/Setup Project” to your current solution.
- View the file system settings of the new project. Add a new Project Output to the Application Folder, and set it to be the Primary Output of your main project.
That alone is sufficient to build an MSI installer that will copy your project’s output onto the target machine. Other useful settings include adding shortcuts to the Start Menu/desktop, customizing registry settings, and configuring whether the installation applies to all users or not.
An alternative to Visual Studio is the highly configurable WiX Toolset. With WiX, you specify filenames, registry keys, and other installation information through XML. WiX’s candle
and light
tools then compile and link that information into an MSI file.
One difficulty I had was that WiX requires every file involved in the installation to be individually specified in the XML. It’s not possible to simply include the entire directory. This is by design. It is possible, however, to automatically generate the XML for a large number of files using another WiX tool called heat
.
Step 3 – Bundle it all together.
At this point, we have an MSI that installs our application. In order to have a seamless user experience, we need to bundle that installer along with all of its prerequisites. It’s not possible to add everything to a single MSI file, but with WiX we can write a bootstrapper that chains multiple installation packages together.
In Visual Studio, we can first install the “WiX Toolset” extension and then add a new project to our solution of type “Windows Installer XML/Bootstrapper Project”. Inside the new project, we need to add our chained packages to the auto-generated Bundle.wxs
file:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="UTF-8"?> | |
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"> | |
<Bundle Name="MyBootstrapper" Version="1.0.0.0" Manufacturer="" UpgradeCode="2d23a9d5-e503-4520-bfd7-07038bc8423b"> | |
<BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.RtfLicense" /> | |
<Chain> | |
<!– TODO: Define the list of chained packages. –> | |
<!– <MsiPackage SourceFile="path\to\your.msi" /> –> | |
</Chain> | |
</Bundle> | |
</Wix> |
The easiest way to add installation items to our chain is to download the needed .msi
/.exe
files to our project and link to them statically. The downside of this approach is that the resulting bootstrapper may be enormous.
Automatically downloading packages
The other option is to configure our bootstrapper to download and install its own packages. To do this, we need to define our own package group. WiX documents this element thoroughly, but here’s a summary of the information that we’ll need to add:
- the remote filename and download URL
- the hash and certificate details of the remote payload (This can be easily generated using the
heat
tool.) - when to install the package, e.g. for a 64-bit package, only if the machine is 64-bit
After some work, we might come up with this for .NET 2.0:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Chain> | |
<PackageGroupRef Id="NetFx20Web"/> | |
<!– more packages here –> | |
</Chain> | |
<!– . . . –> | |
<Fragment> | |
<PackageGroup Id="NetFx20Web"> | |
<ExePackage InstallCondition="NOT VersionNT64 AND VersionNT <> v6.1" | |
DetectCondition="NOT VersionNT64 AND NETFRAMEWORK20" | |
Name="NetFx20SP2_x86.exe" | |
Permanent="yes" | |
Compressed="no" | |
PerMachine="yes" | |
InstallCommand="/q /norestart /ChainingPackage "[WixBundleName]"" | |
RepairCommand="/q /norestart /repair /ChainingPackage "[WixBundleName]"" | |
UninstallCommand="/uninstall /q /norestart /ChainingPackage "[WixBundleName]"" | |
DownloadUrl="http://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x86.exe"> | |
<RemotePayload CertificatePublicKey="F321408E7C51F8544B98E517D76A8334052E26E8" | |
CertificateThumbprint="D57FAC60F1A8D34877AEB350E83F46F6EFC9E5F1" | |
Description="Microsoft .NET Framework 2.0 SP2 Setup" | |
Hash="22D776D4D204863105A5DB99E8B8888BE23C61A7" | |
ProductName="Microsoft .NET Framework 2.0 SP2" | |
Size="25001480" | |
Version="2.2.30729.1" /> | |
</ExePackage> | |
<ExePackage InstallCondition="VersionNT64 AND VersionNT <> v6.1" | |
DetectCondition="VersionNT64 AND NETFRAMEWORK20" | |
Name="NetFx20SP2_x64.exe" | |
Permanent="yes" | |
Compressed="no" | |
PerMachine="yes" | |
InstallCommand="/q /norestart /ChainingPackage "[WixBundleName]"" | |
RepairCommand="/q /norestart /repair /ChainingPackage "[WixBundleName]"" | |
UninstallCommand="/uninstall /q /norestart /ChainingPackage "[WixBundleName]"" | |
DownloadUrl="http://download.microsoft.com/download/c/6/e/c6e88215-0178-4c6c-b5f3-158ff77b1f38/NetFx20SP2_x64.exe"> | |
<RemotePayload CertificatePublicKey="F321408E7C51F8544B98E517D76A8334052E26E8" | |
CertificateThumbprint="D57FAC60F1A8D34877AEB350E83F46F6EFC9E5F1" | |
Description="Microsoft .NET Framework 2.0 SP2 Setup" | |
Hash="A7CC6C6E5A4AD9CDF3DF16A7D277EB09FEC429B7" | |
ProductName="Microsoft .NET Framework 2.0 SP2" | |
Size="48524296" | |
Version="2.2.30729.1" /> | |
</ExePackage> | |
</PackageGroup> | |
</Fragment> |