Quick-Start Guide to Creating a Windows Installer

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:

  1. Add a new “Visual Studio Installer/Setup Project” to your current solution.
  2. 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.

<p>Caveat: Visual Studio versions after 2010 seem to build MSIs that choke on Windows XP. No matter the configuration, my installer failed for me on XP with an &#8220;installation interrupted&#8221; message. The <a href="http://stackoverflow.com/a/26039835" style="text-decoration: underline;">unfortunate workaround</a> involves copying a dll from a Visual Studio 2010 installation over to the newer version&#8217;s directory.</p>

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:


<?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>

view raw

Bundle.wxs

hosted with ❤ by GitHub

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.

<p>Caveat: For some reason, Windows 7 requires .NET to be installed by enabling it as a &#8220;Windows feature&#8221;. Running a .NET MSI on Windows 7 will fail. To work around the issue, we can <a href="http://stackoverflow.com/q/18126502">enable the feature by using Wix to execute dism.exe</a>.</p>

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

<p>If we need our bootstrapper to download and install .NET 4.0 or 4.5, we can save some work by using the <a href="http://wixtoolset.org/documentation/manual/v3/customactions/wixnetfxextension.html" style="text-decoration: underline;">WixNetfxExtension</a>, which contains some predefined package groups. E.g. adding <code>&lt;PackageGroupRef Id=&quot;NetFx40Web&quot;/&gt;</code> to our chain will bundle .NET 4.0.</p>

After some work, we might come up with this for .NET 2.0:


<Chain>
<PackageGroupRef Id="NetFx20Web"/>
<!– more packages here –>
</Chain>
<!– . . . –>
<Fragment>
<PackageGroup Id="NetFx20Web">
<ExePackage InstallCondition="NOT VersionNT64 AND VersionNT &lt;&gt; v6.1"
DetectCondition="NOT VersionNT64 AND NETFRAMEWORK20"
Name="NetFx20SP2_x86.exe"
Permanent="yes"
Compressed="no"
PerMachine="yes"
InstallCommand="/q /norestart /ChainingPackage &quot;[WixBundleName]&quot;"
RepairCommand="/q /norestart /repair /ChainingPackage &quot;[WixBundleName]&quot;"
UninstallCommand="/uninstall /q /norestart /ChainingPackage &quot;[WixBundleName]&quot;"
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 &lt;&gt; v6.1"
DetectCondition="VersionNT64 AND NETFRAMEWORK20"
Name="NetFx20SP2_x64.exe"
Permanent="yes"
Compressed="no"
PerMachine="yes"
InstallCommand="/q /norestart /ChainingPackage &quot;[WixBundleName]&quot;"
RepairCommand="/q /norestart /repair /ChainingPackage &quot;[WixBundleName]&quot;"
UninstallCommand="/uninstall /q /norestart /ChainingPackage &quot;[WixBundleName]&quot;"
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>