Rhynchosaur: WiX Practices

WiX : Descriptive Markups

Think of WiX as html markups to create the webpages, since you create installation UIs by specifying pushbuttons and texts etc.. Think of WiX a descriptive language that gives non-sequential instructions for its compiler (candle.exe), it gives the compiler the configuration of your project package and candle gives you what you want. WiX is not like C, it doesn't explicitly say which goes first and which goes after. It is an instruction to create MSI database.

GUID

Product ID: Each version will have a different ID. If set to "*", then it will be auto-generated.

UpgradeCode: GUID to be shared among the same product of different versions (to notify this is to update/downgrade)

<Product Id="*"
 UpgradeCode="E052B1DC-F7A6-4B0D-A51A-28FD79571A04" />

To perform a re-install, one can specify it with the Product Id,

msiexec /f {869A369E-6BD5-42e1-B9E9-B3543A46D5F6}
.wxi, WiX include files, serve as header files, here goes custom defined variables
.wxl, WiX localization file (File Properties: Build Action must be set to EmbeddedResource)
.wxs, WiX Source files (File Properties: Build Action set to Compile)

Formatted String

They are special formatted variables to be resolved at installation time. [3]

FormatConditionResolved to
[PropertyName] or {PropertyName}Property value if defined, otherwise left untouched
[%EnvironmentVariable]Enviornment variable value
[\[][ (backslash escapes the following character)
[~]Null
[#FileID]Full path to the file
[!FileID]in Registry or IniFile table columnFull short path to the file
otherwise[#FileID]
[$ComponentID]Install directory of the component
Registry TableAction state
[?ComponentID]Installed state of the component
[&FeatureID]Action state of the feature
[!FeatureID]Installed state of the feature

Dynamic XML Configuration

For applications that use XML files for configuration, installer can dynamically change xml file contents at run-time. WiX can do this via the extension WixUtilExtension. Here is how, first Add Reference to WixUtilExtension.dll from the solution explorer. Next in your file where components go, include util namespace.

<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

In your component where you defined your xml file, you can set an attribute to a certain node:

<Component Id="xmlconfig" Guid="{SOME-GUID}" Directory="SOMEDIR">
   <CreateFolder Directory="SOMEDIR" />
   <File Id="configuration.xml" Name="configuration.xml" ShortName="CONFIG~1.XML" Source="configuration.xml" />
   <!--XML CONFIG-->
   <util:XmlFile Id="SomeXmlEntry" File="[SOMEDIR]configuration.xml" ElementPath="rootnode/somenode[\[]@name='SomeName'[\]]" Action="setValue" Name="SomeAttribute" Value="[SomeProperty]"/>
</Component>

Notice that square brackets in ElementPath is escaped. It is to protect XPath from being evaluated as properties. The above does the following change to configuration.xml (if [SomeProperty] evaluates to false at run-time):

<rootnode>
    <somenode name="SomeName" SomeAttribute="false"/>
</rootnode>

VB.NET Custom Actions (Managed Code)

To involve customized custom actions other than standard custom actions, one can create a VB custom action project within in your solution. And in your main setup project, add a reference to the newly created VB custom action project.

Public Class CustomActions
    <CustomAction()> _    
    Public Shared Function CustomActionFoo(ByVal session As Session) As ActionResult
        MsgBox("Hello, I am CustomActionFoo!")
        Return ActionResult.Success
    End Function
End Class

To note that <CustomAction()> _ tells the public function to be a custom action entry point.

In order to use custom actions, you need to declare there is CustomActionFoo entry in your CustomActionDLL file. And your CustomActionDLL file is refered as a binary entry, where CustomActions is your custom action project name.

<Binary Id="CustomActionDLL" SourceFile="$(var.CustomActions.TargetDir)CustomActions.CA.dll" />
<CustomAction Id="CustomActionFoo" DllEntry="CustomActionFoo" BinaryKey="CustomActionDLL" Execute="deferred" />

To put declared custom action to work, you need to put it inside InstallExecuteSequence or InstallUISequence.

<InstallExecuteSequence>
    ...
    <Custom Action="CustomActionFoo" Sequence="6632"/>
    <InstallFinalize Sequence="6700"/>
    ...
</InstallExecuteSequence>

Run Windows console commands

It is possible to run windows commands like in other form of VB project. It is done through WScript.Shell. For instance, we can create a function StartNotepad to be called by custom actions.

Public Shared Sub StartNotepad()
    Dim ShellObj As Object = CreateObject("WScript.Shell")
    ShellObj.Run("notepad", 1, True)
End Sub

UI

Your typical UI element will be filled with these children. Each of them has an important role in presenting a live UI.

  • UIText : information text for installation events
  • ProgressText : information text to show what action is being processed
  • TextStyle (Property to define DefaultUIFont) : defines font and size
  • InstallUISequence : defines the sequence of dialogs and actions
  • RadioButtonGroup (Property attached to it) : Radio buttons to be used in Dialog
  • Dialog (Control, Subscribe inside) : defines how a dialog interface looks like
  • Publish : defines events attached to buttons (switch from dialog to dialog or do some action etc.)
  • Error : messages to display upon some error code

According to ICE20 validation, the following dialogs FilesInUse and FatalError (yes, with exact same Ids) are required and must have such form:

<Dialog Id="FilesInUse" Width="374" Height="266" Title="!(loc.ProductName)">
 <Control Id="Exit" Type="PushButton" X="301" Y="243" Width="66" Height="17" Text="Exit" />
 <Control Id="Ignore" Type="PushButton" X="230" Y="243" Width="66" Height="17" Text="Ignore" />
 <Control Id="Retry" Type="PushButton" X="164" Y="243" Width="66" Height="17" Text="Retry" />
 <Control Id="List" Type="ListBox" X="21" Y="87" Width="331" Height="135" Property="FileInUseProcess" />
</Dialog>
<Dialog Id="FatalError" Width="270" Height="110" Title="!(loc.ErrorDlg_Title)" ErrorDialog="yes">
 <Control Id="ErrorText" Type="Text" TabSkip="no" Transparent="yes" X="50" Y="15" Width="200" Height="50" Text="!(loc.IDS__IsErrorDlg_ErrorText)" />
 <Control Id="ErrorIcon" Type="Icon" FixedSize="yes" X="15" Y="15" Width="24" Height="24" Text="IconBinary" />
 <Control Id="A" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="Abort" />
 <Control Id="C" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="Cancel" />                
 <Control Id="I" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="Ignore" />
 <Control Id="N" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="No" />
 <Control Id="O" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="OK" />
 <Control Id="R" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="Retry" />
 <Control Id="Y" Type="PushButton" TabSkip="yes" X="192" Y="80" Width="66" Height="17" Text="Yes" />
</Dialog>

Notice that I used some localization variables, just some substitutions of text. Further, three dialogs (Ids are free to choose) have to be defined to provide an interface in three necessary situations.

<InstallUISequence>
 <Show Dialog="SetupError" OnExit="error"/>
 <Show Dialog="SetupSuccess" OnExit="success"/>
 <Show Dialog="SetupInterrupted" OnExit="suspend"/>
</InstallUISequence>

Windows Installer Technology

Any .msi package goes installed with Windows Installer, whose command line tool is msiexec.exe. The following installs the package and logs verbosely about most of the installation information to a text file.

msiexec /i MyApplication.msi /l*v MyLogFile.txt

If you have Windows SDK installed, you will get a set of package debugging tools, so called "Windows Installer Development Tools". Although Orca.exe has to be installed manually. [1] To read log files, Wilogutl.exe is the recommended way.

Each .msi file is a database, that is, tables of information to be used by msiexec. Orca is the tool to spy into this database.

Keypath is a tag within a component, it can be a file, a directory, a registry key, or an ODBC data source. It is need to verify if the component is healthy or broken. If the the keypath resource is missing, then the whole component will be re-installed during a repair operation.

InstallUISequence: dialogues to gather information from user (interactive installation) InstallExecuteSequence: copying files and updating registry

Windows environment variables (check the output of set command)

C:\Users\Foo> set

Some notable environment variables:

ALLUSERSPROFILE
APPDATA
CLASSPATH
HOMEPATH
OS
Path
PATHEXT
SystemDrive
SystemRoot
TEMP
TMP
windir

Properties can be passed in the command line, it can be a value or a (even empty) string,

msiexec /i myInstaller.msi PROPERTY1=100 PROPERTY2="my value" PROPERTY3=""

Properties have scope, if all uppercase, then it is a public property. One can view the full list of predefined properties by Windows Installer at MSDN.[2]

Transform files end with .mst, it is used along with .msi main package. It comes into play when the main package is compiled and you still want to alter some database tables/rows. It is applied as

msiexec/i MyApplication.msi TRANSFORMS=custom.mst

Install from command line with verbose logging:

msiexec /i MyApplication.msi /l*v log.txt

Uninstall from command line with verbose logging:

msiexec /x MyApplication.msi /l*v log.txt

Preprocessor Variables

Prepare a variables.wxi file where your predefined variables are written.

<?xml version="1.0" encoding="utf-8"?>
<Include>
    <?define ProductName = "Foobar"?>
    <?define ProductManufacturer = "Foobar Company"?>
</Include>

In Product.wxs, include the above file inside Wix Element.

<?include variables.wxi?>

It then can be used as

$(var.ProductName)

Localization

Prepare a file en-us.wxl in your project, change the project property: cultures to build: en-us.

<?xml version="1.0" encoding="utf-8"?>
<WixLocalization Culture="en-us" xmlns="http://schemas.microsoft.com/wix/2006/localization">
  <String Id="COMPANY_NAME"><![CDATA[your company name]]></String>
</WixLocalization>

To refer this string, use the following in your wxs source code.

!(loc.COMPANY_NAME)

Troubleshooting

Disallowing uninstallation of component

Uninstallation doesn't remove registy entries, components etc. Log file says:

Disallowing uninstallation of component: {COMPONENT-SOME GUID} since another client exists

This is mostly due to a previous unsuccessful installation/uninstallation, so that the components with the same GUID are still in the system. It is really rare that some other package might use the same GUID as yours.

To resolve the issue, one can restore the system to an older rollback point. Normally, package developers use VMWare of HyperV for testing so that a disastrous install/uninstall will not be fatal.

Footnotes

  1. For the version of Windows SDK for Windows 7 and .NET Framework 4.
  2. Property Reference (Windows)
  3. Formatted (Windows)

No comments:

Post a Comment

Myriad

Visitors