Skip to content

Instantly share code, notes, and snippets.

@shenanigans
Last active April 17, 2023 02:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shenanigans/36ceb87ffb75f21e2fb3 to your computer and use it in GitHub Desktop.
Save shenanigans/36ceb87ffb75f21e2fb3 to your computer and use it in GitHub Desktop.
What I think I know about WiX presented in a way that I think is more illuminating than the available free reading material so far

WiX For Drunks

The WiX Toolset, the definitive command line toolset for creating Microsoft Installer packages, is famous for being a royal pain in the ass to learn. This tutorial attempts to cut through the problems this author encountered with the existing free reading material. Mostly it will introduce from the beginning the concepts you will need to understand to avoid encountering bajillions of confusing errors the very instant you exceed the use cases available in a tutorial.

Using WiX Tools

For some baffling (probably historical) reasons WiX is broken down into a compiler and linker called candle and light. For some equally baffling reason they aren't added to your path, but their installation directory is written to %wix%.

Here is an example of the DOS commands I use in a current project. candle writes to build\ARCH.wixobj and light takes over from there to produce an .msi in build\.

"%wix%bin\candle.exe" winstaller.wxs -ext WixUtilExtension -arch x86 -dPlatform=x86 -out build\x86.wixobj
"%wix%bin\candle.exe" winstaller.wxs -ext WixUtilExtension -arch x64 -dPlatform=x64 -out build\x64.wixobj
"%wix%bin\light.exe" -ext WixUIExtension -ext WixUtilExtension build\x64.wixobj -out build\PornViewer_x64.msi -sw1076
"%wix%bin\light.exe" -ext WixUIExtension -ext WixUtilExtension build\x86.wixobj -out build\PornViewer_x86.msi -sw1076

The -sw1076 switch suppresses this irritating warning. When ICE69 is thrown at warning level, it indicates that one Component references another but they're properly dependent and that's fine. If you reference something in a Component that might not be installed you will get ICE69 at error level. You can't suppress errors so don't try. It is recommended to use this switch all the time.

Two extensions are being used. You will most likely want to use WixUIExtension as it's necessary if you want a GUI for your installer. I recently started using the WixUtilExtension because it provides RemoveFolderEx which is the easiest way to clean up a directory full of junk left outside the primary install directory. We'll demonstrate it's use here to show the gotchas of using extensions.

Boilerplate

First, go here and generate a fistfull of guids. When Windows Installer checks its records for the presence of your feature (or entire package) it searches by guid.

This example is a per-machine install that places its executables in the traditional program files directory. It packs all its files inside the .msi and uses the common WixUI_FeatureTree gui with a EULA page. Because it uses the WixUtilExtension extension it must define the util namespace. Curiously, the WixUIExtension namespace is built in.

<?xml version="1.0" encoding="UTF-8"?>
<Wix
  xmlns="http://schemas.microsoft.com/wix/2006/wi"
  xmlns:util="http://schemas.microsoft.com/wix/UtilExtension"
>
    <Product
      Id="4d5791d8-d5f5-46b7-bf16-2221771712f9"
      Name="PornViewer"
      Language="1033"
      Version="0.0.1"
      Manufacturer="Schmidty's Superior Solutions"
      UpgradeCode="8527e354-e187-4921-8af5-de92e1004b4a"
    >
        <Package
          InstallerVersion="200"
          InstallScope="perMachine"
          Compressed="yes"
          Comments="Windows
          Installer Package"
        />
        <Media
          Id="1"
          Cabinet="product.cab"
          EmbedCab="yes"
        />
        <UIRef Id="WixUI_FeatureTree" />
        <WixVariable Id="WixUILicenseRtf" Value="LICENSE.rtf" />

To see all the available options on the root elements, you are advised to peruse the schema documentation. Don't worry about Bundles, Modules or Patches yet.

Components And Features

Your installer is built of a flat list of Components. The world sees your installer as a simple heirarchy of Features. Each Feature may contain any number of ComponentRef elements which map each Feature to the Component elements on which it depends. You can and probably should reference the same Component multiple times. Together this adds up to a fairly powerful dependency management system.

To put it simply, a Component is a software resource and a Feature is how the end-user sees your application.

Declaring Features

In this example, the start menu shortcut and file type associations are specified as optional child Feature elements. Note the way that ApplicationFiles is required multiple times. This shouldn't be necessary because the enclosing feature already references it. WiX could resolve references across these Components as safe. However, it does not do that. You'll get ICE69 in both cases but without the duplicate refs you get it at error level. At minimum that's bad because you might not notice when you get a for-real ICE69 error that needs to be addressed. Your .msi will also not validate.

<Feature Id="Complete" Title="PornViewer 0.0.1" Description="The complete package." Display="expand" Level="1" ConfigurableDirectory="INSTALLDIR">
    <ComponentRef Id="ApplicationFiles" />
    <ComponentRef Id="AppDataDirectory" />
    <Feature
      Id="StartMenuFeature"
      Level="1"
      Title="Start Menu shortcuts"
      Description="Add PornViewer to the Start Menu"
      AllowAdvertise="no"
      InstallDefault="local"
    >
        <ComponentRef Id="ApplicationShortcuts" />
    </Feature>
    <Feature
      Id="FiletypesFeature"
      Level="100"
      Title="File Associations"
      Description="Open image files with PornViewer"
      Display="expand"
      AllowAdvertise="no"
      InstallDefault="local"
    >
        <Feature
          Id="JpgTypesFeature"
          Level="100"
          Title="*.jpg and *.jpeg"
          Description="Open JPEG images with PornViewer"
          AllowAdvertise="no"
          InstallDefault="local"
        >
            <ComponentRef Id="ApplicationFiles" />
            <ComponentRef Id="JpgAssociation" />
        </Feature>
        <Feature
          Id="GifTypesFeature"
          Level="100"
          Title="*.gif"
          Description="Open GIF images with PornViewer"
          AllowAdvertise="no"
          InstallDefault="local"
        >
            <ComponentRef Id="ApplicationFiles" />
            <ComponentRef Id="GifAssociation" />
        </Feature>
        <Feature
          Id="PngTypesFeature"
          Level="100"
          Title="*.png"
          Description="Open PNG images with PornViewer"
          AllowAdvertise="no"
          InstallDefault="local"
        >
            <ComponentRef Id="ApplicationFiles" />
            <ComponentRef Id="PngAssociation" />
        </Feature>
    </Feature>
</Feature>

Declaring Components

Before you can declare a Component that contains files, you must declare some Directory elements. The way WiX does this is a hack burrito with hack sauce. A Directory with its Id property set to certain magic values will be positioned automatically by Microsoft Installer during execution.

<Directory Id="TARGETDIR" Name="SourceDir">
    <Directory Id="$(var.PlatformProgramFilesFolder)">
        <Directory Id="INSTALLDIR" Name="PornViewer">
            <Component
              Id="ApplicationFiles"
              Guid="8527e354-e187-4921-8af5-de92e1004b4a"
            >

Keypath

Every Component must have a keypath. It's some sort of backup check to see whether your Component is installed already. A per-machine Component such as the traditional pattern of installing the primary executable(s) to Program Files may use a file or folder as a keypath. When the keypath is not specified the enclosing Directory will be used. You may manually specify that a File, Directory, or RegistryValue element is your keypath with KeyPath="yes".

<Component Id="ApplicationFiles" Guid="8527e354-e187-4921-8af5-de92e1004b4a">
    <CreateFolder />
    <File Id="Executable" Source="build\$(var.Platform)\nw.exe" Vital="yes" KeyPath="yes" />

Per-user Components must have a per-user keypath. For reasons this is not satisfied by the existence of a file or directory inside %appdata%. You explicitly must use a registry key under HKEY_CURRENT_USER. A quick note: in WiX you set which registry root you're using with an acronym of it's name. Root="HKCU" or HKLM or HKCR etc.

So let's look at an example. First we write a shortcut in the user's start menu. We also override the name of the Product's primary executable by setting the "FriendlyAppName" in the registry. This is because PornViewer is a node-webkit application and its executable has to be called nw.exe. So that you aren't playing Where's Waldo, the first RegistryValue is set to be the keypath by its last property.

<Directory Id="ProgramMenuFolder">
    <Directory Id="ProgramMenuSubfolder" Name="PornViewer">
        <Component Id="ApplicationShortcuts" Guid="0093168f-dbc1-430e-b63f-570a01356f3c">
            <Shortcut
              Id="ApplicationShortcut1"
              Name="PornViewer 0.0.1"
              Description="View Some Porn"
              Target="[INSTALLDIR]nw.exe"
              WorkingDirectory="INSTALLDIR"
              Icon="ProductIcon"
            />
            <RegistryValue
              Root="HKCU"
              Key="Software\Microsoft\Windows\ShellNoRoam\MUICache"
              Name="[$(var.PlatformProgramFilesFolder)]PornViewer\nw.exe.FriendlyAppName"
              Value="PornViewer"
              Type="string"
              KeyPath="yes"
            />
            <RegistryValue
              Root="HKCU"
              Key="Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache"
              Name="[$(var.PlatformProgramFilesFolder)]PornViewer\nw.exe.FriendlyAppName"
              Value="PornViewer"
              Type="string"
            />
            <RemoveFolder Id="ProgramMenuSubfolder" On="uninstall" />
        </Component>
    </Directory>
</Directory>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment