WiX tutorial

Lesson 1 Getting Started

As we have already mentioned in the introduction, the WiX toolset uses XML source files describing the components that make up the installation process of our application as well as additional data about shortcuts, registry or .ini file changes, services and similar. In addition to the files you want to copy to the disk of the user, the installation package can also carry helper files that take part in the installation but will not be actually installed (these can include dialogs, icons and bitmaps for the installation UI, license or readme files, or custom DLLs to carry out any programmatic task the Windows Installer doesn't support, including any kind of user registration or key checking logic you might want to implement).

All these will be described in the source files fed to the WiX compiler. The toolset consists of several parts and we will use two of them to compile our installation packages. Assuming we have prepared a Sample.wxs file, the command

candle.exe Sample.wxs

will perform the first phase of the compilation, creating Sample.wixobj, a half-digested file (this one is still XML but its internal structure is irrelevant to us; think about it as an object file in usual compiler parlance). The second command

light.exe Sample.wixobj

will turn this intermediate representation into our final package, a Sample.msi file. Much like a compiler and a linker. Actually, even more: the linker, unless we specifically tell it not to do so, also runs a verification step that checks the finished installer database for hundreds of possible issues and problems.

As is usual with compilers today, you are not limited to the command line any more. Integrated development environments like Microsoft Visual Studio or SharpDevelop also have support for WiX projects: either coming bundled with the IDE itself or as an addin. In the case of Visual Studio, the WiX v3 package installs the VS support automatically. Using this approach, you can create a separate setup solution or even include the setup as a part project of a complete solution. In this case, you don't even have to leave your programming environment in order to build the final setup package.

But, although the analogy of a compiler and linker is very straightforward and might help us understand how WiX actually builds installation packages, you shouldn't consider the WiX source we will start to write in a minute anything like a script or programming language. We won't collect steps or operations required to install our application. The .msi file we want to distribute our application with is not a setup application but an installation database. The programming logic, the knowledge about how to install an application, how to modify registry keys, how to create shortcuts, users and network shares, how to manipulate web directories or services resides in Windows Installer. Our setup file only describes what we expect Windows Installer to do and provides the files to be deployed (as well as interface elements used in the process).

This database approach means that our WiX source files are not built like regular programs. There is no notion of sequential execution in WiX, the first source line is not supposed to be executed prior to the second one. There will be no declarations that need to precede the references. Various elements might be described in different places and, wherever a link is required between them, one will refer to the other using unique identifiers we need to provide. If you need to think in terms of programming languages, consider WiX as a functional, descriptive language rather than an imperative, prescriptive one.

Also note that WiX is not an installation environment of its own. To put it simply, it is a comfortable, XML-style way to describe your installation requirements that gets translated into Windows Installer .msi databases by its compiler and linker. In this respect, WiX is a relatively thin wrapper around Windows Installer technology, and while it does provide extra features to help the setup developers, its abilities are dictated by the underlying technology and its limitations are not of its own but the limitations of Windows Installer itself.

1.1 The Software Package

Our first WiX sample will install a very simple imaginary application: it consists of an executable, a DLL library and a user manual. We expect these files to be copied to an application specific folder. We will also want two shortcuts to appear in the usual place in the Start Menu, plus we will add an icon shortcut to the user desktop to launch our application.

As simple as this install package will be, the underlying Windows Installer will offer a great deal of functionality, including automatic inclusion of our program in the Control Panel > Add or Remove Programs. To make sure that Windows Installer can keep track of our program, we have to provide some means of identification. Although all applications have human readable names, Windows will require a more unique identifier than a mere name: GUIDs (those long, hexadecimal numbers like CE26AD1E-00D5-4E03-8CBE-6DA5C23AA833) to identify every part of our package. GUIDs come for free, you can generate as many as you like any time. They are guaranteed to be unique, no matter who else asks for GUIDs on their computers, they will never collide with yours. There are many utilities to ask for GUIDs, including this very simple C program that can be compiled with any C/C++ compiler of your choice. If you use a different programming language, you can call the necessary Win32 functions (CoCreateGuid and StringFromCLSID) there, too. Alternatively, many programmers' editors and integrated development environments can insert a freshly generated GUID into the source code on request (In Microsoft's Visual Studio, you can assign this macro to a shortcut). If the tool you use generates lowercase hexadecimal letters, convert them to uppercase before you present them to WiX.

All GUIDs in this tutorial, including those in the downloadable samples, are invalid—the first section is always changed to YOURGUID. This also means that the samples cannot be built straight away, without providing your own GUIDs first (you get a “fatal error CNDL0027: The 'Id' attribute has an invalid value according to its data type.” if you try). This is a small inconvenience but it is necessary to avoid the remote chance of several people using any of the samples verbatim, forgetting to change the GUIDs, and creating a risk of collision out there in the wild. Also note that you have to replace the complete GUID: never mix parts coming from different GUIDs (in other words, don't replace YOURGUID alone, replace the whole number). GUIDs are only guaranteed to be unique if used exactly as they were generated.

To start with, you'll need two GUIDs, one for your product and one for the installation package (actually, for any real world project, you'll also need an UpgradeCode GUID; be sure to check out the other lessons before you ship anything). While the other two have to be kept on file because you will probably need to refer to them later, Package GUIDs need to be different with each package you create. To make it easier and less likely to forget to issue a new one, we can instruct WiX to autogenerate one by typing an asterisk—but remember, this only applies to package GUIDs: all other GUIDs will need to stay unique and kept recorded for times to come. This with all other textual information about the product will go into the very first part of our SampleFirst.wxs file:

<?xml version='1.0' encoding='windows-1252'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>

  <Product Name='Foobar 1.0' Id='YOURGUID-86C7-4D14-AEC0-86416A69ABDE' UpgradeCode='YOURGUID-7349-453F-94F6-BCB5110BA4FD'
    Language='1033' Codepage='1252' Version='1.0.0' Manufacturer='Acme Ltd.'>

    <Package Id='*' Keywords='Installer' Description="Acme's Foobar 1.0 Installer"
      Comments='Foobar is a registered trademark of Acme Ltd.' Manufacturer='Acme Ltd.'
      InstallerVersion='100' Languages='1033' Compressed='yes' SummaryCodepage='1252' />

Product names and descriptions depend on you, of course. For the Version attribute, use the standard major.minor.build format. Windows Installer will ignore an eventual fourth field, .revision as it provides other means to differentiate revisions.

As you know, XML is quite liberal for a format. Use indentation and empty lines as you see fit. Enclose all attribute values in quotes but you can choose from single and double quotes at will. This makes it very easy to write values containing a quote (like the Description above), just make sure you use the other kind of quote to enclose the string.

You can use both UTF-8 and ANSI to create the XML. If you don't plan to use anything but the usual ASCII characters or those accented letters available in the standard ANSI character set, the windows-1252 setting depicted in this example is quite satisfactory. If you need a larger or different character set in your user interface, change to UTF-8 and use the appropriate language and codepage numbers. For instance, for Japanese:

<?xml version='1.0' encoding='utf-8'?>
<Wix xmlns='http://schemas.microsoft.com/wix/2006/wi'>
  <Product Language='1041' Codepage='932' ...>
  <Package Languages='1041' SummaryCodepage='932' ...>

1.2 The Files Inside

In the next step, we have to specify the media we want to install from. In the days of CDs and DVDs, we'll hardly need installation files spanning media but the possibility is there (if you need to use it, you can later refer to the individual disks using the media Id supplied here. DiskPrompt can contain any textual description of the various units of installation media that makes it possible for the user to determine which one to insert, Windows Installer will use this description to prompt for it):

    <Media Id='1' Cabinet='Sample.cab' EmbedCab='yes' DiskPrompt='CD-ROM #1' />
    <Property Id='DiskPrompt' Value="Acme's Foobar 1.0 Installation [1]" />

Using the EmbedCab attribute, we can decide whether we want the cabinet (archive) of our installation files to become part of the .msi package file itself or to remain separate. Embedding is the usual decision for the final installation package (thus resulting in a single, self-contained file for download or shipment on media). If neither Cabinet nor EmbedCab is specified, the source files will be left untouched: they can then be copied directly on the distribution media, together with the installer .msi file.

As we have stressed in the introduction, Windows Installer moved from the earlier programmatic approach to a declarative, descriptive one: we describe the hierarchical structure of our source folder structure using hierarchically nested XML structures, and expect the installer to recreate this structure during installation on the user machine. Windows Installer requires us to start with an outermost folder, the root destination folder for the whole installation. It has a predefined identifier of TARGETDIR and it will be set to our root directory that contains the source cabinet file or the source file tree of the installation package, which also have a predefined name: SourceDir. This provides the basic link between where to install from and where to install to:

    <Directory Id='TARGETDIR' Name='SourceDir'>

Inside this root folder, we go on with our actual structure. According to established guidelines, installed files go into predetermined locations. For instance, applications should go under \Program Files\Company\Product. Shortcuts, desktop icons, user preferences, etc all have their own predefined target locations. For our convenience, the installer environment provides predefined names for all of them, allowing us to refer to them very easily (and this also frees us from localization issues because these folders might very well have different, localized names in non-English Windows versions). In our current example, we'll use three of these names, ProgramFilesFolder, ProgramMenuFolder and DesktopFolder. Note that these predefined names refer to full paths: even if the Desktop folder is several folders deep in C:\Documents and Settings\User\Desktop, a single Directory tag is all it takes to refer to it. With our own nested folders, we have to specify each level separately:

      <Directory Id='ProgramFilesFolder' Name='PFiles'>
        <Directory Id='Acme' Name='Acme'>
          <Directory Id='INSTALLDIR' Name='Foobar 1.0'>

Note that for each element (and this will be the case throughout the use of WiX) we have to provide an Id identifier. Most of the time, these identifiers must be unique because we will cross-reference them all across the WiX source file, so make sure you come up with a naming scheme that makes it easy to follow. In some cases (like ProgramFilesFolder) we can use predefined names. In other cases, we use property names (roughly equivalent to a string variable), like INSTALLDIR. We will later refer to this property name again.

The notion of components and the rules governing their use are probably the most important concepts of the Windows Installer technology and failing to observe these rules is the primary reason for failing setups. Thus, it is very important for us to get a good understanding of components before we can go on with our first sample.

The component is the atomic unit of things to be installed. It consists of resources—files, registry keys, shortcuts or anything else—that should always be installed as a single unit. Installing a component should never influence other components, removing one should never damage another component or leave any orphaned resource on the target machine. As a consequence, components cannot share files: the same file going to the same location must never be included in more than one component.

It's not at the component level that you have to think about what files make up your product. If the product requires an EXE, three DLLs and a couple of data files, this doesn't mean they have to go into a single component, quite the opposite. Later on, we will decide at a higher level of the hierarchy what components belong together to form a standalone part of your product.

Again, a component should only contain items that belong together so strongly that they always need to be installed or removed together. If this means a single file, then your components will contain a single file each. This is not only normal but exactly what you're expected to do. Don't be afraid, Windows Installer can efficiently handle thousands of components or more, if needed.

So, we have a component consisting of three items, a file and two shortcuts pointing to it. A component has to have its own Id identifier as well as its own, unique GUID (the WiX compiler and linker will warn you if you happen to reuse any of these two). This is very important—these GUIDs are the only means for Windows Installer to keep track of the various components. Breaking the component rules will have dire consequences: resources can be left orphaned on the machine during removal, a shared resource might be erroneously removed while another application still needs it, reinstallation of an existing product might fail to restore the functionality, installing the new version of an application can break the previous one.

            <Component Id='MainExecutable' Guid='YOURGUID-83F1-4F22-985B-FDB3C8ABD471'>

A file is specified by its name. Apart from the actual names, you can decorate the file with several other attributes. Vital, when set to no, tells the installer that installing this file is not of vital importance. Normally, if installing any file fails for any reason, the installation will be aborted, the user will not be allowed to ignore the problem. Other attributes include ReadOnly, Hidden, System, all making the file to have the appropriate attribute set when installed.

Each component needs a key path. This is the item Windows Installer can later check to see whether the component is actually installed. Although this doesn't seem very important right now when we only learn to install it in the first place, it is important to specify such a key path for every component we use in order to support the uninstallation and repair functionality of the Installer. Besides, the compiler will complain if we don't specify one...

              <File Id='FoobarEXE' Name='FoobarAppl10.exe' DiskId='1' Source='FoobarAppl10.exe' KeyPath='yes'>

Shortcuts also have names and but also provide other important items like working folder and icon specifications. Note the difference between Directory (where the shortcut will be placed such as a Start menu or the desktop) and the WorkingDirectory (the place the shortcut points to); the second is optional, if omitted, it will default, as expected, to the folder the parent file will be installed into. The Icon attribute will allow us to specify the Id of an Icon tag specified somewhere else in the source rather than the actual filename (even if the .exe extension seems to suggest otherwise, the identifier has to have the same extension as the actual file it will refer to). You can observe that we already reused the INSTALLDIR property and, as expected, it will reference to the folder we're installing into, Program Files\Acme\Foobar 1.0. Description of other folders might come later in the source code.

Shortcuts can be non-advertised (a simple link pointing to the file in the shortcut's Properties dialog) or advertised (with the link greyed out). This second form lets Windows Installer repair the installation by replacing any missing file the shortcut points to.

                <Shortcut Id="startmenuFoobar10" Directory="ProgramMenuDir" Name="Foobar 1.0"
                  WorkingDirectory='INSTALLDIR' Icon="Foobar10.exe" IconIndex="0" Advertise="yes" />
                <Shortcut Id="desktopFoobar10" Directory="DesktopFolder" Name="Foobar 1.0"
                  WorkingDirectory='INSTALLDIR' Icon="Foobar10.exe" IconIndex="0" Advertise="yes" />
              </File>
            </Component>

Here come two other components, with their uniqe Id and Guid:

            <Component Id='HelperLibrary' Guid='YOURGUID-6BE3-460D-A14F-75658D16550B'>
              <File Id='HelperDLL' Name='Helper.dll' DiskId='1' Source='Helper.dll' KeyPath='yes' />
            </Component>

            <Component Id='Manual' Guid='YOURGUID-574D-4A9A-A266-5B5EC2C022A4'>
              <File Id='Manual' Name='Manual.pdf' DiskId='1' Source='Manual.pdf' KeyPath='yes'>
                <Shortcut Id='startmenuManual' Directory='ProgramMenuDir' Name='Instruction Manual' Advertise='yes' />
              </File>
            </Component>

As you might expect, for an application with hundreds or even thousands of files, this will mean hundreds or thousands of components. Yes, this is normal, this is the expected way to do it. Don't be afraid, there will be no performance problems, the Windows Installer is prepared to handle this all right.

Typing all those hundreds or thousands of components into the WiX source file presents another challenge, of course. The toolset has a small utility that can help with this (more about it later) but the real solution is a conceptual change. Stop considering the setup program as a separate application that has to be written in a rush when the main application is already finished. As the WiX source files and the toolset itself can be integrated into your development environment easily, you should keep them in sync all the time. As soon as you start working on a new module or add a new registry reference to your program, modify the corresponding WiX source file at the same time. This way, the setup will be finished together with the application itself and there will be no need to extract all the file and other pieces of information required for the installation later. As the WiX project can be modularized (more about this later), this approach works just as well if you have a large team working on the application rather than a single developer.

And now, the closing tags for the directory elements—one less than what we started with because we're not yet finished. Remaining inside the first, TARGETDIR directory tag, we specify two more full path folders, using predefined names of the Installer: one for our Start Menu shortcuts and another one for our Desktop icons. Only then will the outermost Directory tag be closed.

As we need to remove the program folder when the product is uninstalled, we need to create a fourth component as well. The RemoveFolder tag will describe our intention; the On attribute will determine when the folder will be removed (possible values are install, uninstall and both). As already mentioned, all components must have their own key path. In this case, this will be an extra RegistryValue tag. This tag is beyond the scope of this first lesson, we will return to it later. Putting the KeyPath attribute on the component or the folder might work, too, but that would result in a linker warning. So, please, accept this solution for now as something temporarily unexplained for the sake of avoiding any messages from the compiler and linker.

          </Directory>
        </Directory>
      </Directory>

      <Directory Id="ProgramMenuFolder" Name="Programs">
        <Directory Id="ProgramMenuDir" Name="Foobar 1.0">
          <Component Id="ProgramMenuDir" Guid="YOURGUID-7E98-44CE-B049-C477CC0A2B00">
            <RemoveFolder Id='ProgramMenuDir' On='uninstall' />
            <RegistryValue Root='HKCU' Key='Software\[Manufacturer]\[ProductName]' Type='string' Value='' KeyPath='yes' />
          </Component>
        </Directory>
      </Directory>

      <Directory Id="DesktopFolder" Name="Desktop" />
    </Directory>

Note the Id identifiers we used to identify these two folders, these are the names we used in our shortcut's Directory attribute earlier to make the connection between the location of the shortcut and the actual folder.

Last but not least, we tell the installer what features we would like to install. Features are separated parts of the application that we offer the user to decide whether to install or not. The details will of course depend on your particular software package but a usual scheme might look like this:

In our first sample, we won't have such features. First, because we could hardly divide the three files we plan to install into various features. Second, to do so, we would also need a user interface that the user can use to turn these features on or off. We'll return to that in the next lesson but for now, we will have one feature (because we have to have at least one). We refer back to the components we would like to install with this feature using their Id identifiers:

    <Feature Id='Complete' Level='1'>
      <ComponentRef Id='MainExecutable' />
      <ComponentRef Id='HelperLibrary' />
      <ComponentRef Id='Manual' />
      <ComponentRef Id='ProgramMenuDir' />
    </Feature>

We also have to include the icon we want to use in the shortcuts. Note that the Id identifier has to carry the same extension as the target file, in this case, .exe:

    <Icon Id="Foobar10.exe" SourceFile="FoobarAppl10.exe" />

This will store the source file separately in the final installation package (so, if you refer to your main executable, you will end up with two copies). If the size of the file is large enough to cause concern, create a small .exe or .ico containing nothing but the icons.

    <Shortcut Id="desktopFoobar10" Directory="DesktopFolder" Name="Foobar 1.0" WorkingDirectory='INSTALLDIR' Icon="Foobar10.ico" IconIndex="0" />
    ...
    <Icon Id="Foobar10.ico" SourceFile="FoobarAppl10.ico" />

All there's left to do is to provide the closing tags for the two tags we still have open:

  </Product>
</Wix>

To summarize: first, we provided the description of our application, both the human readable texts and the required GUIDs. Second, we specified the media we want to install from. Next, we specified the folder structure of our files to be installed. These files, together with their accompanying resources, all went into the appropriate components. And finally, we described the features we would like to install, referring back to the individual components.

1.3 Putting it to Use

If you copy all these fragments together (or, to spare some elbow grease, download the SampleFirst archive), you can build the installer package with the commands:

candle.exe SampleFirst.wxs
light.exe SampleFirst.wixobj

You should get no error message from either tool, and you should find a SampleFirst.msi file in your folder upon completion. If not, check for typos everywhere. Please, also note that the required syntax of the .wxs files might undergo some minor changes as the WiX toolset develops. The tutorial always assumes the most current version of the toolset and the samples should all compile without any error message straight out of the box (provided you correct the invalid GUIDs as described earlier). If you do receive compiler errors or warnings, please, compare your toolset version to the current one available for download.

To test your first installer, simply click on it. It will not greet you or offer any options, just show a progress dialog for a few seconds. But as soon as it finishes without errors, you should be able to find your three files in \Program Files\Acme\Foobar 1.0 all right. Note that the actual files deployed are 1-byte placeholder files, so the installed “application” will, naturally, refuse to run...

To remove this huge application again, go to Control Panel > Add or Remove Programs, locate Foobar 1.0 in the list, click Remove (Change will accomplish nothing at this stage). Both the three files and the Acme\Foobar 1.0 subfolders should evaporate.

If you experience any problems—or just for fun—start the installer with logging turned on:

msiexec /i SampleFirst.msi /l* SampleFirst.log

or even

msiexec /i SampleFirst.msi /l*v SampleFirst.log

The log will be rather verbose (especially in the second case) but it will pinpoint the error that might cause the installation to fail.

During testing, you might find it more convenient to remove the package without having to mouse your way into Add or Remove Programs. Just enter the command instead:

msiexec /x SampleFirst.msi

1.4 Useful Extras

Needless to say, we can do much more than to simply copy a couple of files around. To start with, we can have launch conditions: we check for some global settings and stop the installation if our conditions are not met. Just add this line to our previous sample somewhere not within a feature (for instance, between the Package and Media tags) and it will refuse to run if you're not an administrator on your machine:

<Condition Message="You need to be an administrator to install this product.">
  Privileged
</Condition>

On Vista, use:

<Condition Message="You need to be an administrator to install this product.">
  AdminUser
</Condition>

Note that the messages will appear and the installation will be aborted if the condition between the starting and closing tags evaluates to false. In other words, don't specify the error condition, specify the case you want to proceed with the installation in:

<Condition Message="You must not be an administrator to install this product.">
  NOT Privileged
</Condition>

There are quite a few standard properties you can use in similar conditions but the most important ones would be those specifying the version of Windows the setup is running on. Version9X is true on Windows 95, 98 and ME. VersionNT is true on NT 4.0 and later. VersionNT64 signals a 64-bit operating system.

<Condition Message='This application only runs on Windows 95/98/ME.'>
  Version9X
</Condition>

Actually, these properties are not logical but integer, so they can be used to check for further variants as well:

<Condition Message='Windows 95'>Version9X = 400</Condition>
<Condition Message='Windows 95 OSR2.5'>Version9X = 400 AND WindowsBuild = 1111</Condition>
<Condition Message='Windows 98'>Version9X = 410</Condition>
<Condition Message='Windows 98 SE'>Version9X = 410 AND WindowsBuild = 2222</Condition>
<Condition Message='Windows ME'>Version9X = 490</Condition>
<Condition Message='Windows NT4'>VersionNT = 400</Condition>
<Condition Message='Windows NT4 SPn'>VersionNT = 400 AND ServicePackLevel = n</Condition>
<Condition Message='Windows 2000'>VersionNT = 500</Condition>
<Condition Message='Windows 2000 SPn'>VersionNT = 500 AND ServicePackLevel = n</Condition>
<Condition Message='Windows XP'>VersionNT = 501</Condition>
<Condition Message='Windows XP SPn'>VersionNT = 501 AND ServicePackLevel = n</Condition>
<Condition Message='Windows XP Home SPn'>VersionNT = 501 AND MsiNTSuitePersonal AND ServicePackLevel = n</Condition>
<Condition Message='Windows Server 2003'>VersionNT = 502</Condition>
<Condition Message='Windows Vista'>VersionNT = 600</Condition>
<Condition Message='Windows Vista SP1'>VersionNT = 600 AND ServicePackLevel = 1</Condition>
<Condition Message='Windows Server 2008'>VersionNT = 600 AND MsiNTProductType = 3</Condition>
<Condition Message='Windows 7'>VersionNT = 601</Condition>

There are many more properties like these to chose from. For instance, MsiNTProductType allows you to differentiate between Workstation, Domain Controller and Server. Be sure to check out the other MsiNT properties as well.

1.5 Where to Install?

Standalone applications will be installed into their own folders under Program Files—we've already seen how to accomplish this. But plugins, add-ons or similar additional products, not meant for standalone use but to accompany other programs already installed on the system (either your own or some third party) has to learn when and where to install themselves first. Asking the user to provide this information would be both inelegant and possibly dangerous in many cases, thus we need means of querying the registry, to consult .ini files already on the system or to look for actual folders and files to determine what to do.

When we look for any of those items, the result will be stored in a property (a string variable). So, we start by specifying the property (note that the Id we use is the same name we've already used in our first sample, denoting the target folder we install to). Inside the Property tag, we launch a registry search. The attributes speak for themselves:

<Property Id="INSTALLDIR">
  <RegistrySearch Id='AcmeFoobarRegistry' Type='raw'
    Root='HKLM' Key='Software\Acme\Foobar 1.0' Name='InstallDir' />
</Property>

If the registry search was successful (that is, the registry entry specified does exist), its value will be assigned to our INSTALLDIR property, ready to be used for our purposes. To check this out, add this line after the Media tag in our previous sample and save it to SampleRegistry.wxs (or, simply download it again). Compile it but before you start the installation, go into the registry and create the HKEY_LOCAL_MACHINE\SOFTWARE\Acme\Foobar 1.0 key. Create a new string value named InstallDir and set it to an empty folder you've just created anywhere on your system. Run the installer with logging enabled.

If you did everything right, our three sample files will appear in this new folder. Also note the shortcuts (in the Start Menu and on the Desktop) to point to this new location now.

Similar information can come from other sources as well. Let's assume a \Windows\SampleRegistry.ini like that (you can only read these files if they are located in the system folder):

[Sample]
InstallDir=C:\InstallHere

Replace the previous section with this new one:

<Property Id="INSTALLDIR">
  <IniFileSearch Id='AcmeFoobarIniFile' Type='directory'
    Name='SampleRegistry.ini' Section='Sample' Key='InstallDir' />
</Property>

There might be cases when simply knowing the folder is not enough: you have to look into the folder and make sure a given file exists there. Depth = n can be used to instruct the installer to look n levels deeper than the specified Path. Zero or a missing Depth attribute means only to look in the specified folder, not below it. We use square brackets in Path to tell the installer to use the value of the INSTALLDIR property—bracketed names will be looked up and if found, replaced with their actual value. If not found, the string will remain unchanged.

<Property Id="FILEEXISTS">
  <DirectorySearch Id="CheckFileDir" Path="[INSTALLDIR]" Depth="0">
    <FileSearch Id="CheckFile" Name="Lookfor.txt" />
  </DirectorySearch>
</Property>

If the file has been found, its full path will be assigned to the FILEEXISTS property, otherwise it will be left unassigned. You can check this if you build this sample (you need both the previous RegistrySearch and this fragment) and then run it with logging enabled. Observe that if you first put the file Lookfor.txt into the folder specified in the registry, the log will contain a reference to FILEEXISTS, with the full path of the file as its value.

Although we don't yet have a user interface, it's already worth noting that properties meant to receive their value from any interaction with the user, passing that value to the installation logic (eg. destination folders or features selected by the user) need to be public properties. To ensure their public status, their name has to be in all uppercase letters.

It is also worth noting that Windows Installer has better ways of upgrading your product than to look for specific registry entries of the previous installation. You should use these features whenever you can—but you need some patience until we reach them during our discussion.

1.6 Conditional Installation

We've already covered launch conditions. Using them will abort the whole installation if the specified condition is false. There are finer uses of conditions, too, when they don't disable the whole process, only help us determine what to do. To make room for that, we divide the previous single feature into two separate ones—then we can use our conditions to decide which one to install.

<Feature Id='Complete' Level='1'>
  <Feature Id='MainProgram' Level='1'>
    <ComponentRef Id='MainExecutable' />
  </Feature>

  <Feature Id='Documentation' Level='1'>
    <ComponentRef Id='Manual' />
  </Feature>
</Feature>

Building this sample as it is now wouldn't result in anything interesting, the MainProgram feature would install the EXE and the DLL, plus the associated shortcuts while the Documentation feature would do the rest, the PDF and its associated shortcut. So, in the end, the same files are installed. But if you note the Level attribute we haven't discussed so far, a non-zero value means install while a zero value means that the parent feature of the condition (the Feature tag directly enclosing the condition) will not be installed. And we can use the conditions we already discussed to change the level of a feature on the fly, namely, to disable the installation if the condition evaluates to true:

<Feature Id='Documentation' Level='1'>
  <ComponentRef Id='Manual' />
  <Condition Level="0">NOT FILEEXISTS</Condition>
</Feature>

The PDF and its shortcut will only be installed if the file Lookfor.txt can be found in the folder referenced by our registry entry. To try this out on your computer, download SampleCondition.

You can use all the conditions we have already seen as well, for instance, to disable the feature for a non-Administrator user:

<Feature Id='Documentation' Level='1'>
  <ComponentRef Id='Manual' />
  <Condition Level="0">NOT Privileged</Condition>
</Feature>

1.7 Beyond Files

In the real life out there, you'll probably need more to do than to copy files into their final repository. To create a registry keys, include them inside a RegistryKey in the component:

<RegistryKey Id='FoobarRegInstallDir' Root='HKLM' Key='Software\Acme\Foobar 1.0' Action='createAndRemoveOnUninstall'>
  <RegistryValue Type='string' Name='InstallDir' Value='[INSTALLDIR]'/>
  <RegistryValue Type='integer' Name='Flag' Value='0'/>
</RegistryKey>

The Action attribute can be either create or createAndRemoveOnUninstall. Type can be string, integer, binary, expandable and multiString. If the Name attribute is omitted, the default key will be created.

In the case of a multiString, use one or more RegistryValue children to specify the individual strings:

<RegistryKey Id='FoobarRegInstallDir' Root='HKLM' Key='Software\Acme\Foobar 1.0' Action='createAndRemoveOnUninstall'>
  <RegistryValue Type='multiString' Name='InstallDir' Value='[TARGETDIR]'/>
  <RegistryValue Type='multiString' Name='InstallDir' Value='[INSTALLDIR]' Action='append'/>
  <RegistryValue Type='multiString' Name='InstallDir' Value='[ProgramFilesFolder] Action='append'/>
</RegistryKey>

If your application handles its own file data type, you will need to register a file association for it. Put a ProgId inside your component. FileId should refer to the Id attribute of the File element describing the file meant to handle the files of this extension:

<ProgId Id='AcmeFoobar.xyzfile' Description='Acme Foobar data file'>
  <Extension Id='xyz' ContentType='application/xyz'>
    <Verb Id='open' Command='Open' TargetFile='FileId' Argument='"%1"' />
  </Extension>
</ProgId>

To assign an icon to this file type, you have to specify where the icon should come from:

<ProgId Id='AcmeFoobar.xyzfile' Description='Acme Foobar data file' Icon='Foobar.ico'>

or, if the icon comes from an executable or resource file containing several icons, you can specify which one:

<ProgId Id='AcmeFoobar.xyzfile' Description='Acme Foobar data file' Icon='Foobar.exe' IconIndex='1'>

And finally, if you want to write an .ini file—although the registry is more in vogue these days—, this is what you need in your component. The file will always be created in the system folder, not in the installation target one:

<IniFile Id="WriteIntoIniFile" Action="addLine" Key="InstallDir" Name="Foobar.ini"
  Section="Paths" Value="[INSTALLDIR]" />

In the next lesson we'll see how to implement a user interface so that the user can get a chance to decide what and where to install.

1.8 Orphaned on Removal

The application might create files during its functioning that were not present in the installation package originally (user data file, user settings, log files, etc). You might need to remove them when the product is uninstalled. To remove individual files, use RemoveFile:

<Component>
  ...
  <RemoveFile Id='LogFile' On='uninstall' Name='Foobar10User.log' />
</Component>

The On attribute will determine when the file will be removed (possible values are install, uninstall and both). The Name can contain wildcard characters, too. The file or files should be located in the same folder as the component itself. To override this folder, you can use either the Directory or the Property attribute. The second will allow us to remove files from a folder we don't yet know at the time of creating the installation package.

We have already seen how to signal the removal of folders during uninstallation. For folders created by the application rather than the installer, we will need to specify them separately:

<Component>
  ...
  <RemoveFolder Id='LogFolder' On='uninstall' />
</Component>

Again, Directory or Property can be used to specify the folder if it is not the one of the component itself.

Copyright © 2004-2010, Gábor DEÁK JAHN, Tramontána
Comments and contributions are most welcome                   Comments and contributions are most welcome
A note: some people use a very braindead spam filtering idea actually generating extra amounts of spam: asking the sender to reply to an acknowledgment message in order to be placed on a whitelist and allowed through. All those who do so, unfortunately, will never receive a reply from me: I refuse this idea both in theory and practice.