Learning Wix (the hard way)

I spent few last days at work, trying to create an installer for an application I’ve been working on. There aren’t many free tools to help you with that task: Wix, NSIS or Inno Setup as far as I know. I thought Wix was a reasonable choice. I don’t think that anymore.

Wix’s documentation ranges from poor to nonexistent. Many things are done in a very awkward way. And the best (and in many cases only) way to get any help, is the discussion group wix-users.

I’ve had my share of wrestling with Wix. Here’s what I learned.

  • You can define properties, call predefined properties, and change their values using [propertyName] syntax. This however does not work everywhere. If you for example want to pass some dynamic data to your Custom Action, you need another Custom Action, that will set the parameters for you before you can use them.

For example.

    <CustomAction Id="SetParameter" Property="MyAction" Value="&quot;[#myProgramToCall.exe]&quot;  -param [A_PROPERTY]" Execute="immediate" Return="check"  />
    <CustomAction Id="MyAction" BinaryKey="WixCA" DllEntry="CAQuietExec" Execute="deferred" Return="check" Impersonate="no"/>
    <InstallExecuteSequence>
      <Custom Action="SetParameter" After="CostFinalize">
        (Not Installed) AND (NOT REMOVE)
      </Custom>
      <Custom Action="MyAction" Before="InstallFinalize">
        (Not Installed) AND (NOT REMOVE)
      </Custom>
    </InstallExecuteSequence>

You need all of that just to call myProgramToCall.exe during your installation with command line ‘-param whatever_the_value_of_A_PROPERTY_is’. You also need to set exactly those attributes on CustomAction tags, in order for that to work. This is different than in the documentation of QtExec, that sais:

<CustomAction Id="QtExecDeferredExampleWithProperty_Cmd" Property="QtExecDeferredExampleWithProperty"
              Value="&quot;[#MyExecutable.exe]&quot;" Execute="immediate" Return="ignore"/>
<CustomAction Id="QtExecDeferredExampleWithProperty" BinaryKey="WixCA" DllEntry="CAQuietExec"
              Execute="deferred" Return="check" Impersonate="no"/>
.
.
.
<InstallExecuteSequence>
    <Custom Action="QtExecDeferredExampleWithProperty_Cmd" After="CostFinalize"/>
    <Custom Action="QtExecDeferredExampleWithProperty" After="TheActionYouWantItAfter"/>
</InstallExecuteSequence>

If you set Return=”ignore” on Custom Action that sets the parameter, you’ll get an exception:

Error	1	ICE68: Invalid custom action type for action 'SetCertsBatPath'.

  • The (Not Installed) AND (NOT REMOVE)  is needed, if you want to run the action only when program is being installed, and not when it’s being removed.
  • If you have Votive project as part of your Visual Studio Solution and you want to reference files in other projects, you can use relative paths. Also use Configuration environmental variable to include the right version of files (Release/Debug). In order for this to compile, you also need to add project reference to My.Actual.Project in your Wix project.
<File Id="Microsoft.Practices.ObjectBuilder2.dll" Name="Microsoft.Practices.ObjectBuilder2.dll" DiskId="1"
Source="../My.Actual.Project/bin/$(var.My.Actual.Project.Configuration)/Microsoft.Practices.ObjectBuilder2.dll"/>
  • If you want to install a windows service as part of your installation, you use ServiceInstall tag, like this:
<ServiceInstall Id="MyService"
   Name="MyService"
   DisplayName="MyService"
   Type="ownProcess"
   Start="auto"
   ErrorControl="normal"  Vital="yes" Account="[SERVICE_USERNAME]" Password="[SERVICE_PASSWORD]"
   Description="Desc">
   <util:ServiceConfig FirstFailureActionType="restart" SecondFailureActionType="restart" 
      ThirdFailureActionType="restart" RestartServiceDelayInSeconds="10" ResetPeriodInDays="1" />
</ServiceInstall>
<ServiceControl Id="MyServiceControl"
   Name="MyService" Start="install" Stop="both" Remove="uninstall" Wait="yes"/>

Notice that there is no reference to the actual file containing the service. If you have many <File /> tags ServiceInstall will pick, and try to install as service the first one of them from the top. Alternatively you can explicitly designate a file by setting KeyPath attribute to yes.

<File KeyPath="yes"/>
  • Notice I passed username of a user that should be used to run a service, and password with [SERVICE_PASSWORD] and [SERVICE_USERNAME] properties. Where did they come from? – I customized the UI, and added a step to the installer, where user types in the username and the password.

    It requires quite a lot of work, but it’s actually quite straightforward process. I created another file in my Wix project, that holds the UI. I took one of the standard ones, and customized it as follows:

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
    <Fragment>
        <WixVariable Id="WixUIBannerBmp" Value="My.Banner.bmp" />
        <WixVariable Id="WixUILicenseRtf" Value="License.rtf" />
        <UI Id="WixUI_MyUI">
            <TextStyle Id="WixUI_Font_Normal" FaceName="Tahoma" Size="8" />
            <TextStyle Id="WixUI_Font_Bigger" FaceName="Tahoma" Size="12" />
            <TextStyle Id="WixUI_Font_Title" FaceName="Tahoma" Size="9" Bold="yes" />
            <Property Id="DefaultUIFont" Value="WixUI_Font_Normal" />
            <DialogRef Id="BrowseDlg" />
            <DialogRef Id="DiskCostDlg" />
            <DialogRef Id="ErrorDlg" />
            <DialogRef Id="FatalError" />
            <DialogRef Id="FilesInUse" />
            <DialogRef Id="MsiRMFilesInUse" />
            <DialogRef Id="PrepareDlg" />
            <DialogRef Id="ProgressDlg" />
            <DialogRef Id="ResumeDlg" />
            <DialogRef Id="UserExit" />
 
            <Publish Dialog="ExitDialog" Control="Finish" Event="EndDialog" Value="Return" Order="999">1</Publish>
            <Publish Dialog="WelcomeDlg" Control="Next" Event="NewDialog" Value="ServiceUserCredentialsDlg">1</Publish>
            <Publish Dialog="ServiceUserCredentialsDlg" Control="Back" Event="NewDialog" Value="WelcomeDlg">1</Publish>
            <Publish Dialog="ServiceUserCredentialsDlg" Control="Next" Event="NewDialog" Value="InstallDirDlg">1</Publish>
            <Publish Dialog="InstallDirDlg" Control="Back" Event="NewDialog" Value="ServiceUserCredentialsDlg">1</Publish>
            <Publish Dialog="InstallDirDlg" Control="Next" Event="SetTargetPath" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
            <Publish Dialog="InstallDirDlg" Control="Next" Event="NewDialog" Value="VerifyReadyDlg" Order="2">1</Publish>
            <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Property="_BrowseProperty" Value="[WIXUI_INSTALLDIR]" Order="1">1</Publish>
            <Publish Dialog="InstallDirDlg" Control="ChangeFolder" Event="SpawnDialog" Value="BrowseDlg" Order="2">1</Publish>
            <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="InstallDirDlg" Order="1">NOT Installed</Publish>
            <Publish Dialog="VerifyReadyDlg" Control="Back" Event="NewDialog" Value="MaintenanceTypeDlg" Order="2">Installed</Publish>
            <Publish Dialog="MaintenanceWelcomeDlg" Control="Next" Event="NewDialog" Value="MaintenanceTypeDlg">1</Publish>
            <Publish Dialog="MaintenanceTypeDlg" Control="RepairButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
            <Publish Dialog="MaintenanceTypeDlg" Control="RemoveButton" Event="NewDialog" Value="VerifyReadyDlg">1</Publish>
            <Publish Dialog="MaintenanceTypeDlg" Control="Back" Event="NewDialog" Value="MaintenanceWelcomeDlg">1</Publish>
            <Property Id="ARPNOMODIFY" Value="1" />
 
            <Dialog Id="ServiceUserCredentialsDlg" Width="370" Height="270" Title="[ProductName] Setup">
                <Control Id="Title" Type="Text" X="15" Y="6" Width="200" Height="15" Transparent="yes" NoPrefix="yes" Text="{\WixUI_Font_Title}User Information" />
                <Control Id="Description" Type="Text" X="25" Y="23" Width="280" Height="15" Transparent="yes" NoPrefix="yes" Text="Description" />
                <Control Id="BannerBitmap" Type="Bitmap" X="0" Y="0" Width="370" Height="44" TabSkip="no" Text="!(loc.InstallDirDlgBannerBitmap)" />
                <Control Id="BannerLine" Type="Line" X="0" Y="44" Width="370" Height="0" />
                <Control Id="TextLabel1" Type="Text" NoWrap="no" X="20" Y="50" Width="290" Height="40" Text="A description" />
                <Control Id="UserNameLabel" Type="Text"   X="20" Y="110" Width="290" Height="13" Text="User Name:" />
                <Control Id="UserName" Type="Edit"        X="20" Y="122" Width="320" Height="18" Property="SERVICE_USERNAME" Text="47"/>
                <Control Id="PassLabel" Type="Text"    X="20" Y="143" Width="290" Height="13" Text="Password:" />
                <Control Id="Password" Type="Edit" Password="yes"  X="20" Y="155" Width="320" Height="18" Property="SERVICE_PASSWORD"/>
                <Control Id="BottomLine" Type="Line" X="0" Y="234" Width="370" Height="0" />
                <Control Id="Next" Type="PushButton" X="236" Y="243" Width="56" Height="17" Default="yes" Text="Next">
                    <Condition Action="disable">(SERVICE_USERNAME = "") OR (SERVICE_PASSWORD = "")</Condition>
                    <Condition Action="enable">
                        <![CDATA[(SERVICE_USERNAME <> "") AND (SERVICE_PASSWORD <> "")]]> </Condition>
                </Control>
                <Control Id="Back" Type="PushButton" X="180" Y="243" Width="56" Height="17" Text="Back" />
                <Control Id="Cancel" Type="PushButton" X="304" Y="243" Width="56" Height="17" Cancel="yes" Text="Cancel">
                    <Publish Event="SpawnDialog" Value="CancelDlg">1</Publish>
                </Control>
            </Dialog>
        </UI>
 
        <UIRef Id="WixUI_Common" />
    </Fragment>
</Wix>

The Dialog tag, defines the look of the added window. It has some labels, and two textboxes, where user types username and password, that get saved to their respective properties. There is also a condition set on the Next button, which disables it, unless both fields are filled out.

The series of Publish above that, defines transitions between windows (which window should be shown when you click Next/Back).

Now, to actually instruct Wix to use this particular UI, we need to add in the first file the following line within Product tag:

<UIRef Id="WixUI_MyUI"/>

Hope this helps.

Technorati Tags:

Comments

Vojta says:

Hi, I came across this post while searching for information about Wix. Glad to see that someone shares my opinion of WiX. Although documentation has got better since your post, I still think it is too complex, unlogical and clumsy.