From MSI to WiX, Part 3 - Launch Conditions and Application Search

The main page for the series is here.

 

Introduction

Before we start with Launch Conditions and Application Search let's take a look at the sequence of actions Windows Installer executes during installation.  You can find the suggested sequence for InstallExecuteSequence table here.

Basically, what this table is saying is that in order to install any product, Windows Installer is executing the following actions in following order (the list here is high-level and does not include all standard actions):

  • LaunchConditions - Evaluates conditional statements from LaunchCondition table and if any of them fails, displays an error message associated with the launch condition and terminates the installation.
  • AppSearch - Looks for already installed products on the target system.  It uses AppSearch, Signature, CompLocator, DrLocator, RegLocator, and IniLocator tables to search for installed component, existing directory or file, registry entry, or Ini file entry.  If installation is dependent on previously installed software, AppSearch must be scheduled before LaunchConditions action.
  • File Costing - includes CostInitialize, FileCost, CostFinalize, and InstallValidate actions.  This process determines the total disk space required for the installation.  InstallValidate action will terminate the installation if any of the disks does not have enough of free disk space.
  • InstallInitialize - starts the sequence of actions which will change the system, i.e. install files, create directories, registry entries, etc.  Up untill InstallFinalize action, all changing system actions will be written into installation script and will not actually change the system.
  • Actions which install components on the target system and register the installed package with the system..
  • InstallFinalize - marks the end of transaction started with the InstallInitialize action.  This action runs the installation script which actually updates the system.

Looking at this sequence we can say that purpose of Launch Conditions and Application Search is just to find an answer on this question: "Can we even start the installation of our product on the target system?".

Launch Conditions

In MSI database launch conditions are represented by records in the LaunchCondition table.  This table has two columns. Condition column contains an expression which must evaluate to True for installation to continue. Description column is a localizable text, Windows Installer displays when the condition fails.

In WiX, Launch Conditions are represented as one or more <Condition> elements.  Use Message attribute for the localizable error message and inner text for the condition expression.

Important: The <Condition> element in WiX is an overloaded element.  It can have different parent elements, but only if the parent element is <Product> or <Fragment>, <Condition> element represents a row in the LaunchCondition table.

Condition Expressions

In launch conditions we can use properties and environment variables in the condition expressions.  If identifier is prefixed with the % symbol, it means environment variable.  Unprefixed identifiers are properties from the Property table.

There are two types of properties: User defined properties and Windows Installer predefined properties.  Windows Installer predefined properties are created by Windows Installer engine when installation starts up.  Not all of the predefined properties will have the same value at the time when LaunchConditions action is executed and when installation will actually starts and because of that those properties cannot be reliably used in laucn conditions.

Condition expression syntax is described in great details here.

Useful properties for launch coditions

Samples

<

Condition Message='.NET Framework 2.0 must be installed prior to installation of this product.'>
    MsiNetAssemblySupport >= "2.0.50727"
</Condition>

<

Condition Message="Web Edition of Windows Server 2003 is required.">
    MsiNTSuiteWebServer
</Condition>

<

Condition Message="NODEFAULTVALUE variable must be set in the command line">
    Installed OR NODEFAULTVALUE
</Condition>

Using WixNetFxExtension library

In order to determine if particular version of .NET Framework is installed on the target system we can use the WixNetFxExtension library. More information on how to use it can be found at wixwiki, but basic steps are:

Add property reference link to a property from the WixNetFxExtension library using <PropertyRef> element:

<PropertyRef Id="NETFRAMEWORK20"/>

Add the condition using referenced property:

<Condition Message="The .NET Framework 2.0 must be installed">
    Installed OR NETFRAMEWORK20
</Condition>

Add the extension to candle and light:

candle.exe Minimal.wxs -ext "Microsoft.Tools.WindowsInstallerXml.Extensions.NetFxCompiler, WixNetFxExtension"
light.exe -out Minimal.msi Minimal.wixobj -ext "Microsoft.Tools.WindowsInstallerXml.Extensions.NetFxCompiler, WixNetFxExtension" d:\wix\netfx.wixlib

Here is the MSBuild project file:

<Project DefaultTargets="Build" xmlns="https://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Required by WiX -->
<!-- Path and name of the output without extension -->
<OutputName>Minimal</OutputName>

    <!-- What need to be built -->
<OutputType Condition="$(OutputType)==''">package</OutputType>

    <!-- The path to the WiX installation -->
<ToolPath>d:\WIX\</ToolPath>

    <!-- Input path to source files.
If not passed, assumes the same folder where project file is located. -->
<BaseInputPath Condition="$(BaseInputPath)==''">$(MSBuildProjectDirectory)\</BaseInputPath>

    <!-- Create a compiled output in the folder where project is located -->
<OutputPath Condition="$(OutputPath)==''">$(MSBuildProjectDirectory)\</OutputPath>

    <!-- Add missing trailing slash in paths -->
<ToolPath Condition="!HasTrailingSlash('$(ToolPath)') ">$(ToolPath)\</ToolPath>
<BaseInputPath Condition="!HasTrailingSlash('$(BaseInputPath)') ">$(BaseInputPath)\</BaseInputPath>
<OutputPath Condition="!HasTrailingSlash('$(OutputPath)') ">$(OutputPath)\</OutputPath>
</PropertyGroup>

  <!-- Candle.exe command-line options -->
<ItemGroup>
<CompileExtension Include="WixNetFxExtension">
<Class>Microsoft.Tools.WindowsInstallerXml.Extensions.NetFxCompiler</Class>
</CompileExtension>
</ItemGroup>

  <!-- Light.exe command-line options -->
<ItemGroup>
<LinkExtension Include="WixNetFxExtension">
<Class>Microsoft.Tools.WindowsInstallerXml.Extensions.NetFxCompiler</Class>
</LinkExtension>
<WixLibrary Include="$(ToolPath)netfx.wixlib"></WixLibrary>
</ItemGroup>

  <Import Project="$(ToolPath)wix.targets"/>

  <!-- List of files to compile -->
<ItemGroup>
<Compile Include="$(BaseInputPath)Minimal.wxs"/>
</ItemGroup>
</Project>

Important

Launch Conditions do not guarantee the order in which conditions will be evaluated. Here is the excerpt from the Remarks section of LaunchCondition table reference:

"You cannot guarantee the order in which the launch conditions are evaluated by authoring this table. If it is necessary to control the order in which conditions are evaluated, you should do this by using Custom Action Type 19 custom actions in your installation."

This is even more important when we do Application Search before evaluating launch conditions because, for example, some registry keys can be accessed by admins only.

To do the check if user is an admin before Application Search and evaluating launch conditions add the declaration of custom action:

<CustomAction Id='IsPrivileged' Error='You must be an admin to install this product' />

Schedule custom action:

<

InstallExecuteSequence>
<Custom Action='IsPrivileged' Before='LaunchConditions'>
    Not Privileged
  </Custom>
</InstallExecuteSequence>

Application Search

As we mentioned already in the Introduction to this post, AppSearch standard action is using AppSearch, Signature, CompLocator, DrLocator, RegLocator, and IniLocator tables to search for installed component, existing directory or file, registry entry, or Ini file entry.

The idea here is that when Windows Installer finds an installed component, existing directory or file, registry key or value, or entry in the Ini file, it will set provided in the request property to the found value. If search failed, property will remain undefined or it will keep its original value.

In WiX we are using the following elements to do the application search:

  • <Property>: Property to set after successful search.
  • <ComponentSearch>: Search for installed component.  If component is installed, search continues for the file or directory which is the KeyPath of the found component.
  • <RegistrySearch>: Search for directory or file if registry value points to a directory or file, or search for a registry value itself.
  • <IniFileSearch>: Search for directory or file if Ini file entry points to a directory or file, or search for a Ini file entry itself.
  • <DirectorySearch>: Search for directory or file in that directory.
  • <FileSearch>: Search for the file.  This entry creates a record in the Signature table.

Samples

Find if .NET Framework 2.0 is installed (from NetFxExtension.wxs):

<Property Id="NETFRAMEWORK20">
<RegistrySearch Id="NetFramework20" Root="HKLM" Key="Software\Microsoft\NET Framework Setup\NDP\v2.0.50727" Name="Install" Type="raw" />
</Property>

Find if component from another application is installed (from VSExtension.wxs):

<Property Id="VS2005PROJECTAGGREGATOR2">
<ComponentSearch Id="VS2005ProjectAggregator2Search" Guid="B0BB80E0-5CCC-474E-A75E-05DC1AE073BC" />
</Property>

We can also define a default value for the property in case search is failed.  For example, this search will succeed. Installing this installation package will result in the message box with the message similar to "C:\Windows\Microsoft.NET\Framework\" and then installation will be aborted.

<

Property Id='TESTAPP'>
<RegistrySearch Id='TestAppRegSearch'
                           Root='HKLM'
                           Key='SOFTWARE\Microsoft\.NETFramework'
                           Name='InstallRoot'
                           Type='raw' />
</Property>

<

CustomAction Id='ShowTESTAPP' Error='[TESTAPP]' />

<

InstallExecuteSequence>
<Custom Action='ShowTESTAPP' Before='InstallInitialize' />
</InstallExecuteSequence>

Now, let's change the name of the registry value to point to a value which does not exist, for example, InstallRoot2. We can also add the Value attribute to the <Property> element to define the default value for the property. Without the Value attribute TESTAPP property will be undefined.

<

Property Id='TESTAPP' Value='XYZ'>
  <RegistrySearch Id='TestAppRegSearch'
                           Root='HKLM'
                           Key='SOFTWARE\Microsoft\.NETFramework'
                           Name='InstallRoot2'
                           Type='raw' />
</Property>

<

CustomAction Id='ShowTESTAPP' Error='[TESTAPP]' />

<

InstallExecuteSequence>
<Custom Action='ShowTESTAPP' Before='InstallInitialize' />
</InstallExecuteSequence>

Now message box will show "XYZ" text.

Default value can also be set to a value of another property:

<Property Id='TESTAPP' Value='[ProgramFilesFolder]XYZ'> 

Whats next

Next time we will discuss components and component rules.