To get started, download the WiX Toolset. It can be found at:
http://wix.codeplex.com/
Once you've downloaded and installed it, open Visual Studio and select New Project | WiX | WiX Project. This will create a solution 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
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.
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.
At this point, you could also add the RequiredVersion
attribute to the Wix
element. Given a WiX toolset version number, such as "3.0.5419.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.
Next, add a Product
element.
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 or indicate that it's an upgrade of a previous installation. In that case you need only change the Version
, and sometimes 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.
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 digits separated by periods, although the last digit is ignored by Windows Installer during operations such as detecting previously installed versions of your application. 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.
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.
The final attribute to consider is UpgradeCode
. This should be set to a GUID and will identify your product across version releases. Therefore, it should stay the same even when the Product ID 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.
Once you've defined your Product
element, the next step is to nest a Package
element inside. An example is shown:
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.
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 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 MessageBox
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 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 files that you intend to install are bundled up into CAB files. You have the option of splitting your package into several parts or keeping it all in one. For each Media
element that you add to your WiX markup, a new CAB file will be created. Generally, you should limit the number of files you put into a single CAB file to 64 K or less and no single file should be larger than 2 GB. You can find more information about the CAB file format at:
http://msdn.microsoft.com/en-us/library/ee177956(v=EXCHG.80).aspx
Media
elements come nested inside the Product
element alongside the Package
element.
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
of 1
. Therefore, your first Media
element should always use an Id
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 won't be bundled inside the MSI package. This is atypical, but might be done to split the installation files onto several disks. Even this is becoming rare in the age of 4 GB DVDs and Internet downloads. Setting EmbedCab
to no
will produce a visible CAB file that must be provided alongside the MSI file during an installation.
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]
.
The Property
element will be used as the text in the MessageBox
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
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 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
of the corresponding Media
element. The following example includes a text file called myFile.txt
in the media2.cab
file:
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.
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 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 installation path, such as 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 names provided by Windows Installer that will be translated to their true paths at install time. For example, ProgramFilesFolder
usually translates to C:\Program
Files
. Following is a list of these built-in directory properties:
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:
You should place your Directory
elements inside of the top-level Product
element. Other than that, there aren't any restrictions about exactly where inside Product
they have to go. One thing to know is that you must start your Directory
elements hierarchy with a Directory
with an Id
of TARGETDIR
and a Name
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. The 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. Here is an example:
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
.
To put something inside a directory, use a DirectoryRef
element. DirectoryRef
takes only a single attribute: Id
. This is your reference to the Id
set on the corresponding Directory
element. DirectoryRef
elements, like Directory
elements, are placed as children to the top-level Product
element.
Using a DirectoryRef
adds a layer of abstraction between where you define your directory structure and the files that will go into those directories. The following example adds a file (via the Component
element, which we'll cover next) to the MyProgramDir
directory.
By using a DirectoryRef
, we're able to separate the markup that adds files to directories from the markup that defines the directories. You can also add a component directly inside a Directory
element:
Placing components directly inside Directory
elements is more straightforward, but it couples your directory structure more tightly to the files that you're installing. By using DirectoryRef
elements, you're able to make the two pieces more modular and independent of one another.
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. 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 uninstallation 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. You won't get an error by placing more than one file into a single component, but it is considered bad practice.
To really explain components, 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.
The Component
element is used to uniquely identify each file that you plan to install. 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. Be sure to make all of the letters uppercase. The 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.
Here, I've created a new component called CMP_InstallMeTXT
. I've started it with CMP_
to label it as a component. 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 full 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
is, at least in most cases, not recommended. This is because only one file can be the KeyPath
, 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.
Once you've created your File
and Component
elements, you'll need to tell Windows Installer where they should be installed to. To do that, place them inside a DirectoryRef
element that references one of your Directory
elements—as shown in the following snippet:
To add more files, simply create more Component
and File
elements. Of course, they don't all have to be installed to the same place. You might install some to the MyProgramDir
folder that we're creating and others to a different folder. You always have to create a Directory
element before you can place components in that directory. For example, you can't use the AppDataFolder
property to place files in the Application
Data
directory until you've first added a Directory
element for it.
As you've seen, the actual files inside components are declared with File
elements. 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:
A File
element should always get the Id
, Source
, and KeyPath
attributes. Name
is optional and gives you a chance to set the name of the file to something user-friendly. Without it, the name will default to whatever it's called in the Source
attribute. Notice that you should set the value to the file name plus file type extension. In earlier versions of WiX, you'd have to use a separate attribute if the name was longer than eight characters or the extension longer than three. Now, this attribute can handle longer names as well.
Source defines the path to the file during compilation. I've listed a relative path here, but you could also specify an absolute path. The Id
attribute should be something unique, but you might consider starting it with FILE
to make it clear that it refers to a File
element. To mark a file as important (and that it should be replaced if it goes missing), set it as the KeyPath
for the component. Since you should only ever place one file inside a component, in almost all cases that file should be the KeyPath
.
There are quite a few optional attributes available for the File
element. A few useful ones are:
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 yes
to stop the installation if the file isn't installed successfully.
After you've defined your components and the directories that they'll be copied into, your 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. Here is an example of such a tree:
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 some 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, like 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 the Main
Product
feature:
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:
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 Main
Product
. Another feature called Optional
Tools
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.
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 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 children features will be too. Here's an example of a more complex feature setup:
Here, I've moved the ReadMe.txt
file into its own feature called Documentation
that's nested inside the Main
Product
feature. Disabling its parent feature (Main
Product
), will also disable it. However, you could enable Main
Product
and disable Documentation
.
If you're going to use a feature tree, 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. It would look something like this:
Having a working installer is good, but wouldn't it be nice to add some shortcuts for the application to the Start Menu? To do so, create a new component with a nested Shortcut
element. Shortcuts are stored in their own table in the MSI database. First, add another Directory
element that references the Start Menu via the built-in ProgramMenuFolder
name:
Here, I've added a reference to the Start Menu folder with the ProgramMenuFolder
Directory
element Id
. I've then told the installer to create a new subfolder inside it called Awesome
Software
. Now, you can use a DirectoryRef
element to reference your new shortcuts folder, as in the following code snippet:
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
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
of a Directory
element to remove. Without one, though, the element will remove the directory defined by the parent DirectoryRef
element.
Let's add the RemoveFolder
and RegistryValue
elements to our component:
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 the KeyPath. The actual value itself serves no other purpose. We will cover writing to the Registry in more detail later.
There's actually another reason for using a RegistryValue
as the 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
of the product you want to uninstall. The ProductCode
is the Id
specified in the Product
element.
Notice that we don't have to use the exact GUID from the Product
element to get the ProductCode
. 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.
Be sure to add the new component that contains the shortcuts to one of your features:
Now that you've seen the different elements used to author an MSI package, here is the entire .wxs
file:
Compile the project in Visual Studio and you should get a new MSI file.
You can double-click it or right-click and select Install to install the software. Doing so should create a subfolder for your program in the Start Menu.
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 Add/Remove Programs
From a command prompt, navigate to the directory where the MSI file is and use the command: