From MSI to WiX, Part 12 - Customizing installation using Transitive components

The main page for the series is here.

 

Introduction

This post is from the same group as Part 6 and 7.

Normally, we use transitive components when our product has system-specific component and depending on system state we want to install one component or the other.  For example, we might have Windows XP-specific dll and Windows Vista-specific dll.  When user upgrades his/her operating system from Windows XP to Windows Vista, we want to uninstall XP version of dll and install Vista version.

For this blog I will be using same task as in Part 6 and 7 - customizing an installation depending on the environment.  Instead of using transforms or custom tables we are going to use transitive components.

Test application

As in part 6, we will be using C# console application as our test application.  Hee is the source code for it:

using System;

using System.Collections.Generic;

using System.Text;

using System.Configuration;

namespace ConsoleApp

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine("Key1={0}, Ke2={1}, Key3={2}",

                 ConfigurationManager.AppSettings["Key1"],

                 ConfigurationManager.AppSettings["Key2"],

                 ConfigurationManager.AppSettings["Key3"]);

            Console.ReadLine();

   }

    }

}

Along with executable we will be installing one of the two configuration files. Here is configuration file for Dev environment - Dev.config:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <appSettings>

    <add key="Key1" value="Dev1" />

    <add key="Key2" value="Dev2" />

    <add key="Key3" value="Dev3" />

  </appSettings>

</configuration>

and here is the Test environment version:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <appSettings>

    <add key="Key1" value="Test1" />

    <add key="Key2" value="Test2" />

    <add key="Key3" value="Test3" />

  </appSettings>

</configuration>

WiX source code is really simple:

<?xml version="1.0" encoding="UTF-8"?>

<Wix xmlns="https://schemas.microsoft.com/wix/2003/01/wi">

  <Product Id="{0BD4334D-F9FE-4B70-8070-917288A50B51}"

           Name="Minimal Windows Installer Sample"

           Language="1033"

           Codepage="1252"

           Version="1.0.0"

           Manufacturer="Acme Corporation"

           UpgradeCode="{770C5598-A538-4D44-8C2E-B2D94E15CC98}">

    <Package Id="{E1782FB0-3D87-4B13-88DC-62E11FB72552}"

             Description="Minimal Windows Installer Sample"

             Comments="This installer database contains the logic and data required to install [ProductName]."

             InstallerVersion="200"

             Languages="1033"

             SummaryCodepage="1252"

             Platforms="Intel"

             ReadOnly="no"

        Compressed="yes"

             AdminImage="no"

             Keywords="Installer"

             ShortNames ="no"

             Manufacturer="Acme Corporation" />

    <Media Id="1" Cabinet="CAB001.cab" EmbedCab="yes" />

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

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLDIR" Name="Minimal" LongName="MinimalInstallation">

          <Component Id="ConsoleApp"

                     Guid="{A77C5B06-132D-4884-8E17-EA10A83C5789}">

            <File Id="ConsoleApp" DiskId="1"

                  Name="ConsApp.exe" LongName="ConsoleApp.exe"

                  Source="ConsoleApp\ConsoleApp\bin\Debug\ConsoleApp.exe"

                  Vital="yes" KeyPath="yes" />

          </Component>

   <Component Id="Component1"

                     Guid="{A77C5B06-132D-4884-8E17-EA10A83C812D}"

                     Transitive="yes">

            <Condition>%ENVIRONMENT="Dev"</Condition>

            <File Id="DevConfig" DiskId="1"

                  Name="App.cnf" LongName="ConsoleApp.exe.config"

                  Source="Dev.config" Vital="yes" KeyPath="yes" />

          </Component>

          <Component Id="Component2"

                     Guid="{A77C5B06-132D-4884-8E17-EA10A83C812E}"

                     Transitive="yes">

            <Condition>%ENVIRONMENT="Test"</Condition>

            <File Id="TestConfig" DiskId="1"

                  Name="App.cnf" LongName="ConsoleApp.exe.config"

                  Source="Test.config" Vital="yes" KeyPath="yes" />

          </Component>

        </Directory>

      </Directory>

    </Directory>

    <Feature Id="Feature1"

             Title="Feature1 title"

             Description="Feature1 description"

             Level="1"

             ConfigurableDirectory="INSTALLDIR" >

      <ComponentRef Id="ConsoleApp" />

      <ComponentRef Id="Component1" />

      <ComponentRef Id="Component2" />

    </Feature>

  </Product>

</Wix>

What's new in here is that components Component1 and Component2 have attribute Transitive set to "yes".  Also, both components have condition on environment variable ENVIRONMENT to be set to either Dev or Test.  Based on the value of this environment variable MSI will determine which component to install.

Let's test it.  Create environment variable ENVIRONMENT and set its value to Dev.  Compile WiX source and install the application.  Test it to make sure that it shows Dev data in the console window.

Now, change the value of ENVIRONMENT variable to Test.  To uninstall Dev component and install Test component we must do a repair of the installation.  We can repair installation either using Add Remove Programs control panel applet or by issuing the following command from the command window:

msiexec /f Minimal.msi

Run Console.exe again.  Now it should show Test data.

Conclusion

As you can see, we must have type of current environment stored somewhere in the system because repair must know if transitive component needs to be reinstalled or not.  For this sample I chose to use environment variable.  Probably, better solution would be to provide the type of the environment (Dev or Test) through the public property in the command line during initial install.  Condition for transitive components should be based on a property which value is set during AppSearch.  For initial install AppSearch would fail, so we need to add custom action to set property's value to a value of the property provided in the command line.  We also need to add an element to store the value of environment property in the registry.