Book Image

WiX 3.6: A Developer's Guide to Windows Installer XML

Book Image

WiX 3.6: A Developer's Guide to Windows Installer XML

Overview of this book

The cryptic science of Windows Installer can seem far off from the practical task of simply getting something installed. Luckily, we have WiX to simplify the matter. WiX is an XML markup, distributed with an open-source compiler and linker, used to produce a Windows Installer package. It is used by Microsoft and by countless other companies around the world to simplify deployments. "WiX 3.6: A Developer's Guide to Windows Installer XML" promises a friendly welcome into the world of Windows Installer. Starting off with a simple, practical example and continuing on with increasingly advanced scenarios, the reader will have a well-rounded education by book's end. With the help of this book, you'll understand your installer better, create it in less time, and save money in the process. No one really wants to devote a lifetime to understanding how to create a hassle-free installer. Learn to build a sophisticated deployment solution targeting the Windows platform in no time with this hands-on practical guide. Here we speed you through the basics and zoom right into the advanced. You'll get comfortable with components, features, conditions and actions. By the end, you'll be boasting your latest deployment victories at the local pub. Once you've finished "WiX 3.6: A Developer's Guide to Windows Installer XML", you'll realize just how powerful and awesome an installer can really be.
Table of Contents (23 chapters)
WiX 3.6: A Developer's Guide to Windows Installer XML
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Your first WiX project


To get started, download the WiX toolset. It can be found at:

http://wixtoolset.org/

Once you've downloaded and installed it, open Visual Studio and select New Project | Windows Installer XML | Setup Project. This will create a project with a single .wxs (WiX source) file. Visual Studio will usually call this file Product.wxs, but the name could be anything as long as it ends with .wxs.

Even the most minimal installer must have the following XML elements:

  • An XML declaration

  • A Wix element that serves as the root element in your XML document

  • A Product element that is a child to the Wix element, but all other elements are children to it

  • A Package element

  • A Media or MediaTemplate element

  • At least one Directory element with at least one child Component element

  • A Feature element

XML declaration and Wix element

Every WiX project begins with an XML declaration and a Wix element:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

</Wix>

Tip

Downloading the example code

You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

The xmlns, or XML namespace, just brings the core WiX elements into the local scope of your document. At the bottom of the file you'll have to close the Wix element, of course. Otherwise, it's not valid XML. The Wix element is the root element of the document. It comes first and last. All other elements will be nested inside of it.

Note

For the most part, knowing only the basic rules of writing a well-formed XML document will be enough to get you up and running using WiX. The major points are as follows, as recommended by the W3C:

  • The document must begin and end with the same root element

  • All elements must have a matching closing tag or be closed themselves

  • XML tags are case sensitive

  • Elements must be properly nested, with inner elements not overlapping outer elements

  • XML attributes should be quoted

At this point, you could also add the RequiredVersion attribute to the Wix element. Given a WiX toolset version number, such as "3.6.3303.0", it won't let anyone compile the .wxs file unless they have that version or higher installed. If, on the other hand, you're the only one compiling your project, then it's no big deal.

The Product element

Next, add a Product element.

<Wix ... >
  <Product Id="3E786878-358D-43AD-82D1-1435ADF9F6EA"
           Name="Awesome Software"
           Language="1033"
           Version="1.0.0.0"
           Manufacturer="Awesome Company"
           UpgradeCode="B414C827-8D81-4B4A-B3B6-338C06DE3A11">
  </Product>
</Wix>

This is where you define the characteristics of the software you're installing: its name, language, version, and manufacturer. The end user will be able to see these properties by right-clicking on your MSI file, selecting Properties, and viewing the Summary tab. Most of the time, these values will stay the same from one build of your project to the next. The exception is when you want to increment the software's version to indicate that it's an upgrade of a previous installation. In that case, you need to only change the Version attribute, and sometimes the Id attribute. We'll talk more about upgrading previous installations later on in the book.

The Product element's Id attribute represents the so-called ProductCode of your software. It's always a unique number—a GUID—that Windows will use to uniquely identify your software (and tell if it's already installed on the computer). You can either hardcode it, like here, or just put an asterisk. That way, WiX will pick a new GUID for you each time you compile the project.

<Wix ... >
  <Product Id="*"
           Name="Awesome Software"
           Language="1033"
           Version="1.0.0.0"
           Manufacturer="Awesome Company"
           UpgradeCode="B414C827-8D81-4B4A-B3B6-338C06DE3A11">
  </Product>
</Wix>

The Name attribute defines the name of the software. In addition to being displayed in the MSI file's Properties page, it will also be shown in various places throughout the user interface of your installer—that is, once you've added a user interface, which we'll touch on at the end of this chapter.

The Language attribute is used to display error messages and progress information in the specified language to the user. It's a decimal language ID (LCID). A full list can be found on Microsoft's LCID page at:

http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx

The previous example used "1033", which stands for "English-United States". If your installer uses characters not found in the ASCII character set, you'll also need to add a Codepage attribute set to the code page that contains those characters. Don't worry too much about this now. We'll cover languages and code pages later in the book when we talk about localization.

The Version attribute is used to set the version number of your software. It can accept up to four numbers separated by periods. Typically, when you make a big enough change to the existing software, you'll increment the number. Companies often use the [MajorVersion].[MinorVersion].[Build].[Revision] format, but you're free to use any numbering system you like.

Note

During upgrade scenarios, the fourth digit in the Version attribute is ignored and won't make a difference when detecting previously installed software.

The Manufacturer attribute tells the user who this software is from and usually contains the name of your company. This is another bit of information that's available via the MSI file's Properties page.

The final attribute to consider is UpgradeCode. This should be set to a GUID and will identify your product across releases. It remains constant for a product line, even among different product versions. Think: Microsoft Office 2007 and Office 2010. Both would have the same UpgradeCode. Therefore, it should stay the same even when the ProductCode and Version change.

Windows will use this number in its efforts to keep track of all the software installed on the machine. WiX has the ability to search for previously installed versions of not only your own software, but also those created by others and it uses UpgradeCode to do it. Although, technically, this is an optional attribute, you should always supply it.

The Package element

Once you've defined your Product element, the next step is to nest a Package element inside. An example is shown as follows:

<Wix ... >
  <Product ... >
    <Package InstallerVersion="301"
             Compressed="yes"   
             InstallScope="perMachine" 
             Manufacturer="Awesome Company"
             Description="Installs Awesome Software"
             Keywords="Practice,Installer,MSI"
             Comments="(c) 2012 Awesome Company" />
  </Product>
</Wix>

Of the attributes shown in this example, only Compressed is really required. By setting Compressed to yes, you're telling the installer to package all of the MSI's resources into CAB files. Later, you'll define these CAB files with Media elements or a MediaTemplate element.

Technically, an Id attribute is also required, but by omitting it you're letting WiX create one for you. You'd have to create a new one anyway since every time you change your software or the installer in any way, the package (the MSI file) has changed and so the package's ID must change. This really, in itself, emphasizes what the Package element is. Unlike the Product element, which describes the software that's in the installer, the Package element describes the installer itself. Once you've built it, you'll be able to right-click on the MSI and select Properties to see the attributes you've set here.

The InstallerVersion attribute can be set to require a specific version of msiexec.exe (the Windows Installer service that installs the MSI when you double-click on it) to be installed on the end user's computer. If they have an older version, Windows Installer will display a dialog telling them that they need to upgrade. It will also prevent you from compiling the project unless you also have this version installed on your own computer. The value can be found by multiplying the major version by 100 and adding the minor version. So, for Version 4.5 of msiexec.exe, you'd set InstallerVersion to 405.

The InstallScope attribute can be set to either perMachine or perUser. The former means that your software will be installed in the "All Users" context, meaning that all users will be able to access your application. As such, the person performing the install will need elevated privileges on a UAC enabled system such as Windows 7 to continue the installation. The latter means that it will be installed only for the current user. Behind the scenes this is setting a WiX property called ALLUSERS that we'll cover in more detail later when we discuss properties.

The rest of the attributes shown provide additional information for the MSI file's Properties window. Manufacturer is displayed in the Author text field, Description is shown as Subject, Keywords show up as Keywords, and Comments show as Comments. It's usually a good idea to provide at least some of this information, if just to help you distinguish one MSI package from another.

The MediaTemplate element

The files that you intend to install are compressed into CAB files and shipped along with the installer. You decide whether to embed them inside the MSI or provide them visibly alongside it. In WiX 3.6, a single MediaTemplate element handles all the details for you, intelligently splitting your files into the prescribed number of CAB files. Add it after the Package element, as shown in the following code snippet:

<Wix …>
  <Product … >
    <Package … />
    <MediaTemplate EmbedCab="yes" />
  </Product>
</Wix>

The EmbedCab attribute is optional and sets whether the CAB files will be embedded inside the MSI, the default being to not embed them. Either way, WiX will create up to 999 CAB files, each holding a maximum of 200 MB of data. You can change that limit with the MaximumUncompressedMediaSize attribute, set to a size in megabytes. If a single file is bigger than the maximum, it will be placed into its own CAB file with enough space to accommodate it.

If you want to split your installation up into several physical disks—conjure up images of "Please insert disk 2"—you want to use the Media element instead.

The Media element

The Media element is an older element that was replaced by MediaTemplate and if you use one you can't use the other. However, in some cases, the Media element is the only thing for the job. For each Media element that you add to your WiX markup, a new CAB file will be created.

<Wix ... >
  <Product ... >
    <Package ... />
    <Media Id="1"
           Cabinet="media1.cab"
           EmbedCab="yes" />
  </Product>
</Wix>

Each Media element gets a unique Id attribute to distinguish it in the MSI Media table. It must be a positive integer. If the files that you add to your installation package don't explicitly state which CAB file they wish to be packaged into, they'll default to using a Media element with an Id value of 1. Therefore, your first Media element should always use an Id value of 1.

The Cabinet attribute sets the name of the CAB file. You won't actually see this unless you set EmbedCab to no, in which case the file will be shipped alongside the MSI. This is atypical, but might be done to split the installation files onto several disks.

If you do choose to split the installation up into several physical disks (or even virtual ISO images), you'll want to add the DiskPrompt and VolumeLabel attributes. In the following example, I've added two Media elements instead of one. I've also added a Property element above them, which defines a variable called DiskPrompt with a value of Amazing Software - [1].

<Property Id="DiskPrompt"
          Value="Amazing Software - [1]" />

<Media Id="1"
       Cabinet="media1.cab"
       EmbedCab="no"
       DiskPrompt="Disk 1"
       VolumeLabel="Disk1" />

<Media Id="2"
       Cabinet="media2.cab"
       EmbedCab="no"
       DiskPrompt="Disk 2" 
       VolumeLabel="Disk2" />

The Property element will be used as the text in the message box the end user sees, prompting them to insert the next disk. The text in the DiskPrompt attribute is combined with the text in the property's value, switched with [1], to change the message for each subsequent disk. Make sure you give this property an Id value of DiskPrompt.

So that Windows will know when the correct disk is inserted, the VolumeLabel attribute must match the "Volume Label" of the actual disk, which you'll set with whichever CD or DVD burning program you use. Once you've built your project, include the MSI file and the first CAB file on the first disk. The second CAB file should then be written to a second disk.

Although we haven't described the File element yet, it's used to add a file to the installation package. To include one in a specific CAB file, add the DiskId attribute, set to the Id attribute of the corresponding Media element. The following example includes a text file called myFile.txt in the media2.cab file:

<File Id="fileTXT"
      Name="myFile.txt"
      Source="myFile.txt"
      KeyPath="yes"
      DiskId="2" />

We'll discuss the File element in more detail later on in the chapter. If you're only using one Media element, you won't need to specify the DiskId attribute on your File elements.

The Directory element

So, now we've defined the identity of the product, set up its package properties, and told the installer to create a CAB file to package up the things that we'll eventually install. Then, how do you decide where your product will get installed to on the end user's computer? How do we set the default install path, for example, to some folder under Program Files?

When you want to install to C:\Program Files, you can use a sort of shorthand. There are several directory properties provided by Windows Installer that will be translated to their true paths at install time. For example, ProgramFilesFolder usually translates to C:\Program Files. The following is a list of these built-in directory properties:

Directory property

Actual path

AdminToolsFolder

Full path to directory containing administrative tools

AppDataFolder

Full path to roaming folder for current user

CommonAppDataFolder

Full path to application data for all users

CommonFiles64Folder

Full path to the 64-bit Common Files folder

CommonFilesFolder

Full path to the Common Files folder for current user

DesktopFolder

Full path to the Desktop folder

FavoritesFolder

Full path to the Favorites folder for current user

FontsFolder

Full path to the Fonts folder

LocalAppDataFolder

Full path to folder containing local (non-roaming) applications

MyPicturesFolder

Full path to the Pictures folder

NetHoodFolder

Full path to the NetHood folder

PersonalFolder

Full path to the Documents folder for current user

PrintHoodFolder

Full path to the PrintHood folder

ProgramFiles64Folder

Full path to the 64-bit Program Files folder

ProgramFilesFolder

Full path to 32-bit Program Files folder

ProgramMenuFolder

Full path to Program Menu folder

RecentFolder

Full path to Recent folder

SendToFolder

Full path to the SendTo folder for current user

StartMenuFolder

Full path to the Start Menu folder

StartupFolder

Full path to the Startup folder

System16Folder

Full path to the 16-bit system DLLs folder

System64Folder

Full path to the System64 folder

SystemFolder

Full path to the System folder for current user

TempFolder

Full path to the Temp folder

TemplateFolder

Full path to the Template folder for current user

WindowsFolder

Full path to the Windows folder

The easiest way to add your own directories is to nest them inside one of the predefined ones. For example, to create a new directory called Install Practice inside the Program Files folder, you could add it as a child to ProgramFilesFolder. To define your directory structure in WiX, use Directory elements:

<Wix ... >
  <Product ... >
    <Package ... />
    <MediaTemplate ... />

    <Directory Id="TARGETDIR"
               Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="MyProgramDir"
                   Name="Install Practice" />
      </Directory>
    </Directory>

  </Product>
</Wix>

One thing to know is that you must start your Directory elements hierarchy with a Directory element with an Id attribute of TARGETDIR and a Name value of SourceDir. This sets up the "root" directory of your installation. Therefore, be sure to always create it first and nest all other Directory elements inside.

By default, Windows Installer sets TARGETDIR to the local hard drive with the most free space—in most cases, the C: drive. However, you can set TARGETDIR to another drive letter during installation. You might, for example, set it with a VolumeSelectCombo user interface control. We'll talk about setting properties and UI controls later in the book.

A Directory element always has an Id attribute that will serve as a primary key on the Directory table. If you're using a predefined name, such as ProgramFilesFolder, use that for Id. Otherwise, you can make one up yourself. The previous example creates a new directory called Install Practice, inside the Program Files folder. Id, MyProgramDir, is an arbitrary value.

When creating your own directory, you must provide the Name attribute. This sets the name of the new folder. Without it, the directory won't be created and any files that were meant to go inside it will instead be placed in the parent directory—in this case, Program Files. Note that you do not need to provide a Name attribute for predefined directories.

You can nest more subdirectories inside your folders by adding more Directory elements. The following is an example:

<Directory Id="TARGETDIR"
           Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
    <Directory Id="MyProgramDir"
               Name="Install Practice">
      <Directory Id="MyFirstSubDir"
                 Name="Subdirectory 1">
        <Directory Id="MySecondSubDir"
                   Name="Subdirectory 2" />
      </Directory>
    </Directory>
  </Directory>
</Directory>

Here, a subdirectory called Subdirectory 1 is placed inside the Install Practice folder. A second subdirectory, called Subdirectory 2, is then placed inside Subdirectory 1, giving us two levels of nested directories under Install Practice.

Note

If you've been following along using the Visual Studio Setup Project template, you'll notice that it places its boilerplate Directory elements inside of a Fragment element. We will discuss Fragment in the next chapter.

Before jumping into how to add files to your new directories, we should cover the elements that define the files themselves. The next section covers how to create components, which are the containers for the files you want to install.

The Component element

Once you've mapped out the directories that you want to target or create during the installation, the next step is to copy files into them. To really explain things, we'll need something to install. So let's create a simple text file and add it to our project's directory. We'll call it InstallMe.txt. For our purposes, it doesn't really matter what's in the text file. We just need something for testing.

Windows Installer expects every file to be wrapped up in a component before it's installed. It doesn't matter what type of file it is either. Each gets its own Component element.

Components, which always have a unique GUID, allow Windows to track every file that gets installed on the end user's computer. During an installation, this information is stored away in the registry. This lets Windows find every piece of your product during an uninstall so that your software can be completely removed. It also uses it to replace missing files during a repair, which you can trigger by right-clicking on an MSI file and selecting Repair.

Each Component element gets a unique GUID via its Guid attribute. To create a GUID in Visual Studio, go to Tools | Create GUID and copy a new GUID using the registry format. The component's Id attribute is up to you. It will serve as the primary key for the component in the MSI database, so each one must also be unique:

<Component Id="CMP_InstallMeTXT"
           Guid="E8A58B7B-F031-4548-9BDD-7A6796C8460D">

  <File Id="FILE_MyProgramDir_InstallMeTXT"
        Source="InstallMe.txt"
        KeyPath="yes" />
</Component>

In the preceding code snippet, I've created a new component called CMP_InstallMeTXT. I've started it with CMP_ to label it as a component, which is just a convention that I like to use. Although it isn't required, it helps to prefix components in this way so that it's always clear what sort of element it refers to.

The File element inside the component references the file that's going to be installed. Here, it's the InstallMe.txt file located in the current directory (which is the same directory as your WiX source file). You can specify a relative or absolute path with the Source attribute.

You should always mark a File element as the KeyPath file and you should only ever include one File inside a component. A KeyPath file will be replaced if it's missing when the user triggers a repair (Windows Installer documentation calls this resiliency). Placing more than one File element inside a single Component element, at least in most cases, is not recommended. This is because only one file can be the KeyPath file, so the other files wouldn't be covered by a repair. You would really only ever place more than one File in a component if you didn't want the extra files to be resilient.

To add a component to a directory, you have several options. The first, which is the simplest, is to add your Component elements directly inside the target Directory element, as given in the following code snippet:

<Directory Id="TARGETDIR"
           Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
    <Directory Id="MyProgramDir"
               Name="Install Practice">

      <Component Id="CMP_InstallMeTXT"
                 Guid="E8A58B7B-F031-4548-9BDD-7A6796C8460D">

        <File Id="FILE_MyProgramDir_InstallMeTXT"
              Source="InstallMe.txt"
              KeyPath="yes" />
      </Component>
    </Directory>
  </Directory>
</Directory>

In the previous code snippet, I've instructed the installer to copy the InstallMe.txt file to the %ProgramFiles%\Install Practice folder that we're creating on the end user's computer. Although this is the simplest solution, it isn't the cleanest. For one thing, if you're installing more than a handful of files, the XML file can begin to look tangled.

Another approach is to use a DirectoryRef element to reference your directories. This has the benefit of keeping the markup that defines your directories independent from the markup that adds files to those directories. The following is an example:

<Directory Id="TARGETDIR"
           Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
    <Directory Id="MyProgramDir"
               Name="Install Practice" />
  </Directory>
</Directory>

<DirectoryRef Id="MyProgramDir">
  <Component ...>
    <File ... />
  </Component>
</DirectoryRef>

A third option is to group your components inside of a ComponentGroup and use its Directory attribute to set the target directory. We will cover component groups in more detail in the next chapter, but the following snippet will give you an idea:

<ComponentGroup Id="ProductComponents"    
                Directory="MyProgramDir">
  <Component ...>
    <File ... />
  </Component>
</ComponentGroup>

The File element

As you've seen, the actual files inside components are declared with File elements. The File elements can represent everything from simple text files to complex DLLs and executables. Remember, you should only place one file into each component. The following example would add a file called SomeAssembly.dll to the installation package:

<Component ... >
  <File Id="FILE_MyProgramDir_SomeAssemblyDLL" 
        Name="Some Assembly.dll" 
        Source="SomeAssembly.dll" 
        KeyPath="yes" />
</Component>

A File element should always get the Source attribute. Source defines the path to the file during compilation. I've listed a relative path here, but you could also specify an absolute path.

Id, Name, and KeyPath are optional. The Id attribute becomes the primary key for a row in the MSI database. It should be something unique, but you might consider starting it with FILE to make it clear that it refers to a File element. If not set, the Id value will match the filename. Name gives you a chance to change the name of the file once it's been copied to the end user's computer. By default, it will use the name in the Source attribute.

To mark a file as important (and that it should be replaced if it goes missing), set it as the KeyPath file for the component. Since you should only ever place one file inside a component, in almost all cases that file should be the KeyPath file. If not set, the first file in the component will be the KeyPath file automatically.

A few other optional but useful attributes for the File element include:

  • Hidden: Set to yes to have the file's Hidden flag set. The file won't be visible unless the user sets the directory's options to show hidden files.

  • ReadOnly: Set to yes to have the file's Read-only flag set. The user will be able to read the file, but not modify it unless they change the file's properties.

  • Vital: Set to no to continue even if this file isn't installed successfully.

The Feature element

After you've defined your components and the directories that they'll be copied into, the next step is to define features. A feature is a group of components that the user can decide to install all at once. You'll often see these in an installation dialog as a list of modules, called a feature tree , where each is included or excluded from the installation. The following is an example of such a tree that has two features – Main Product and Optional Tools:

Every component must be included in a feature. Generally, you should group together components that rely on one another or that form a complete, self-sufficient unit. That way, if a feature is disabled, you won't have orphaned files (files that aren't being used) installed onto the computer. In many instances, if your product doesn't have any optional parts, you'll only want to create one feature.

If you've included a feature tree dialog (which we'll explain later in the book), such as the one shown, the user can simply click a feature to exclude it. However, even without this, they can select features from the command line. The following command only installs a feature called MainProduct:

msiexec /i myInstaller.msi ADDLOCAL=MainProduct

Here, we're using the msiexec program to launch an installer. The /i flag targets the MSI file to install. The ADDLOCAL property is set to the names of the features we want to include. If more than one, use commas to separate the names. To install all available features set ADDLOCAL=ALL, as shown:

msiexec /i myInstaller.msi ADDLOCAL=ALL

To create a new feature in your WiX file, add a Feature element inside the Product element. The following example installs three components under the feature MainProduct. Another feature called OptionalTools installs another component. Components are included in a feature with the ComponentRef element. The Id attribute of ComponentRef targets the Id attribute from the corresponding Component element:

<Feature Id="MainProduct"
         Title="Main Product"
         Level="1">
  <ComponentRef Id="CMP_MyAppEXE" />
  <ComponentRef Id="CMP_ReadMeTXT" />
  <ComponentRef Id="CMP_StartMenuShortcuts" />
</Feature>

<Feature Id="OptionalTools"
         Title="Optional Tools"
         Level="1">
  <ComponentRef Id="CMP_ToolsEXE" />
</Feature>

The Feature element's Id attribute uniquely identifies the feature and is what you'll reference when using the ADDLOCAL property on the command line. The Title attribute is used to set a user-friendly name that can be displayed on dialogs. Setting the Feature element's Level attribute to 1 means that that feature will be included in the installation by default. The end user will still be able to remove it through the user interface or via the command line. If, on the other hand, Level is set to 0, that feature will be removed from the feature tree and the user won't be able to install it.

If you wanted to, you could create a more complex tree with features nested inside features. You could use this to create more categories for the elements in your product and give the user more options concerning what gets installed. You would want to make sure that all possible configurations function correctly. Windows Installer makes this somewhat manageable in that if a parent feature is excluded, its child features will be too. The following is an example of a more complex feature setup:

<Feature Id="MainProduct"
         Title="Main Product"
         Level="1">
  <ComponentRef Id="CMP_MyAppEXE" />
  <ComponentRef Id="CMP_StartMenuShortcuts" />

  <Feature Id="SubFeature1"
           Title="Documentation"
           Level="1">
    <ComponentRef Id="CMP_ReadMeTXT" />
  </Feature>
</Feature>

<Feature Id="OptionalTools"
         Title="Optional Tools"
         Level="1">
  <ComponentRef Id="CMP_ToolsEXE" />
</Feature>

In the preceding code snippet, I've moved the ReadMe.txt file used in the previous examples into its own feature called Documentation that's nested inside the MainProduct feature. Disabling its parent feature (MainProduct) will also disable it. However, you could enable MainProduct and disable Documentation.

You have the ability to prevent the user from excluding a particular feature. Just set the Absent attribute to disallow. You might do this for the main part of your product where excluding it wouldn't make sense.

You might also consider adding the Description attribute, which can be set to a string that describes the feature. This could be displayed in your dialog alongside the feature tree, if you decide to use one. We'll cover feature trees and adding a user interface later in the book.

Start menu shortcuts

Having a working installer is good, but wouldn't it be nice to add some shortcuts to the Windows Start menu? First, add another Directory element that references the Start menu via the built-in ProgramMenuFolder property:

<Directory Id="TARGETDIR"
           Name="SourceDir">
  <Directory Id="ProgramFilesFolder">
    <Directory Id="MyProgramDir"
               Name="Awesome Software" />
  </Directory>
  <Directory Id="ProgramMenuFolder">
    <Directory Id="MyShortcutsDir"
               Name="Awesome Software" />
  </Directory>
</Directory>

In the previous code snippet we're adding a new folder to the Start menu called Awesome Software. Now, we can use a DirectoryRef element to reference our new shortcuts folder, as in the following code snippet:

<DirectoryRef Id="MyShortcutsDir">
  <Component Id="CMP_DocumentationShortcut" 
             Guid="33741C82-30BF-41AF-8246-44A5DCFCF953">

    <Shortcut Id="DocumentationStartMenuShortcut"
              Name="Awesome Software Documentation"
              Description="Read Awesome Software Documentation"
              Target="[MyProgramDir]InstallMe.txt" />
  </Component>
</DirectoryRef>

Each Shortcut element has a unique identifier set with the Id attribute. The Name attribute defines the user-friendly name that gets displayed. Description is set to a string that describes the shortcut and will appear when the user moves their mouse over the shortcut link.

The Target attribute defines the path on the end user's machine to the actual file being linked to. For that reason, you'll often want to use properties that update as they're changed, instead of hardcoded values. In the previous example, the main installation directory is referenced by placing the Id attribute of its corresponding Directory element in square brackets, which is then followed by the name of the file. Even if the path of MyProgramDir changes, it will still lead us to the InstallMe.txt file.

Two things that should accompany a shortcut are a RemoveFolder element and a RegistryValue element. RemoveFolder ensures that the new Start menu subdirectory will be removed during an uninstall. It uses an Id attribute to uniquely identify a row in the MSI RemoveFile table and an On attribute to specify when to remove the folder. You can set On to install, uninstall, or both. You can specify a Directory attribute as well to set to the Id attribute of a Directory element to remove. Without one, though, the element will remove the directory defined by the parent DirectoryRef or ComponentGroup element.

The RegistryValue element is needed simply because every component must have a KeyPath item. Shortcuts aren't allowed to be KeyPath items as they aren't technically files. By adding a RegistryValue, a new item is added to the registry and this is marked as KeyPath. The actual value itself serves no other purpose. We will cover writing to the registry in more detail later.

<DirectoryRef Id="MyShortcutsDir">
  <Component Id="CMP_DocumentationShortcut"
             Guid="33741C82-30BF-41AF-8246-44A5DCFCF953">

    <Shortcut Id="DocumentationStartMenuShortcut"
              Name="Awesome Software Documentation"
              Description="Read Awesome Software Documentation"
              Target="[MyProgramDir]InstallMe.txt" />

    <RemoveFolder Id="RemoveMyShortcutsDir"
                  On="uninstall" />

    <RegistryValue Root="HKCU"
                   Key="Software\Microsoft\AwesomeSoftware"
                   Name="installed"
                   Type="integer"
                   Value="1"
                   KeyPath="yes" />
  </Component>
</DirectoryRef>

There's actually another reason for using a RegistryValue element as KeyPath. The shortcut we're creating is being installed to a directory specific to the current user. Windows Installer requires that you always use a registry value as the KeyPath item when doing this in order to simplify uninstalling the product when multiple users have installed it.

Another type of shortcut to add is one that uninstalls the product. For this, add a second Shortcut element to the same component. This shortcut will be different in that it will have its Target set to the msiexec.exe program, which is located in the System folder. The following example uses the predefined System64Folder directory name because it will automatically map to either the 64-bit or 32-bit System folder, depending on the end user's operating system.

By setting Target to the path of an executable, you're telling Windows to launch that program when the user clicks the shortcut. The msiexec program can remove software by using the /x argument followed by the ProductCode attribute of the product you want to uninstall. The ProductCode attribute is the Id attribute specified in the Product element.

<DirectoryRef Id="ProgramMenuFolder">
  <Component Id="CMP_DocumentationShortcut"
             Guid="33741C82-30BF-41AF-8246-44A5DCFCF953">

    <Shortcut Id="DocumentationStartMenuShortcut"
              Name="Awesome Software Documentation"
              Description="Read Awesome Software Documentation"
              Target="[MyProgramDir]InstallMe.txt" />

    <Shortcut Id="UninstallShortcut"
              Name="Uninstall Awesome Software"
              Description=
              "Uninstalls Awesome Software and all of its components"
              Target="[System64Folder]msiexec.exe"
              Arguments="/x [ProductCode]" />

    <RemoveFolder Id="RemoveMyShortcutsDir"
                  On="uninstall" />

    <RegistryValue Root="HKCU"
                   Key="Software\Microsoft\AwesomeSoftware"
                   Name="installed"
                   Type="integer"
                   Value="1"
                   KeyPath="yes" />
  </Component>
</DirectoryRef>

Notice that we don't have to use the GUID from the Product element to get the ProductCode value. We can reference it using the built-in property called ProductCode surrounded by square brackets. If you'd like to add an icon to your shortcut, first add an Icon element as another child to the Product element. Then, reference that icon with the Icon attribute on the Shortcut element, as shown in the following code snippet:

<Icon Id="icon.ico" SourceFile="myIcon.ico"/>
<DirectoryRef ... >
  <Component ... >
    <Shortcut Id="DocumentationStartMenuShortcut" 
              Name="Awesome Software Documentation"
              Description="Read Awesome Software Documentation"
              Target="[MyProgramDir]InstallMe.txt"
              Icon="icon.ico" />

    <RemoveFolder ... />
    <RegistryValue ... />
  </Component>
</DirectoryRef>

Be sure to add the new component that contains the shortcuts to one of your features:

<Feature Id="ProductFeature"
         Title="Main Product"
         Level="1">
  <ComponentRef Id=" CMP_InstallMeTXT" />
  <ComponentRef Id="CMP_DocumentationShortcut" />
</Feature>

Putting it all together

Now that you've seen the different elements used to author an MSI package, the following is the entire .wxs file:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">

  <Product Id="3E786878-358D-43AD-82D1-1435ADF9F6EA"
           Name="Awesome Software"
           Language="1033"
           Version="1.0.0.0"
           Manufacturer="Awesome Company"
           UpgradeCode="B414C827-8D81-4B4A-B3B6-338C06DE3A11">

    <Package InstallerVersion="301"
             Compressed="yes"   
             InstallScope="perMachine" 
             Manufacturer="Awesome Company"
             Description="Installs Awesome Software"
             Keywords="Practice,Installer,MSI"
             Comments="(c) 2012 Awesome Company" />

    <MediaTemplate EmbedCab="yes" />

    <!--Directory structure-->
    <Directory Id="TARGETDIR"
               Name="SourceDir">
      <Directory Id="ProgramFilesFolder">
        <Directory Id="MyProgramDir"
                   Name="Awesome Software" />
        <Directory Id="ProgramMenuFolder">
          <Directory Id="MyShortcutsDir" 
                     Name="Awesome Software" />
        </Directory>
      </Directory>
    </Directory>

    <!--Components-->
    <DirectoryRef Id="MyProgramDir">
      <Component Id="CMP_InstallMeTXT" 
                 Guid="E8A58B7B-F031-4548-9BDD-7A6796C8460D">
        <File Id="FILE_InstallMeTXT" 
              Source="InstallMe.txt"
              KeyPath="yes" />
      </Component>
    </DirectoryRef>

    <!--Start Menu Shortcuts-->
    <DirectoryRef Id="MyShortcutsDir">
      <Component Id="CMP_DocumentationShortcut"
                 Guid="33741C82-30BF-41AF-8246-44A5DCFCF953">

        <Shortcut Id="DocumentationStartMenuShortcut"
                  Name="Awesome Software Documentation"
                  Description="Read Awesome Software Documentation"
                  Target="[MyProgramDir]InstallMe.txt" />

        <Shortcut Id="UninstallShortcut"
                  Name="Uninstall Awesome Software"
                  Description="Uninstalls Awesome Software"
                  Target="[System64Folder]msiexec.exe"
                  Arguments="/x [ProductCode]" />

        <RemoveFolder Id="RemoveMyShortcutsDir"
                      On="uninstall" />

        <RegistryValue Root="HKCU" 
                       Key="Software\Microsoft\AwesomeSoftware"
                       Name="installed"
                       Type="integer"
                       Value="1"
                       KeyPath="yes" />
      </Component>
    </DirectoryRef>

    <!--Features-->
    <Feature Id="ProductFeature"
             Title="Main Product"
             Level="1">
      <ComponentRef Id="CMP_InstallMeTXT" />
      <ComponentRef Id="CMP_DocumentationShortcut" />
    </Feature>
  </Product>
</Wix>

Compile the project in Visual Studio and you should get a new MSI file:

You can double-click on it or right-click and select Install to install the software. Doing so should create a subfolder for your program in the Start menu, as shown in the following screenshot:

You should also find a new folder under Program Files:

To uninstall the software, you have several options:

  • Use the uninstall shortcut from the Start menu

  • Right-click on the MSI file and select Uninstall

  • Uninstall it from Programs and Features

  • From a command prompt, navigate to the directory where the MSI file is and use the following command:

    msiexec /x AwesomeSoftware.msi