Welcome to TechNet Blogs Sign in | Join | Help

From MSI to WiX, Part 25 - Installable Items - Updating XML files using XmlFile

To use XmlFile element we need to:

  • Add  reference to WixUtilExtension extension.
  • Add util namespace to <Wix> element:

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

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

     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

Here is what we can do with existing XML file using XmlFile element:

  • Set the value of an attribute
  • Set the text value of an element
  • Create new element
  • Create new attribute
  • Delete the text value of an element
  • Delete the attribute

    Preparing an installation

Here is the original content of XML file we are going to update (app.config):

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

<configuration>

  <UserName></UserName>

  <Parameters>-DeleteAll</Parameters>

  <EmptyElement>123</EmptyElement>

  <appSettings>

    <add key="NoValue" value="" />

    <add key="NoValue2" value="" />

    <add key="ExtraAttribute" value="MyValue" extra="" />

  </appSettings>

  <BulkSet1>

    <add key="key1" value="" />

    <add key="key2" value="" />

    <add key="key3" value="" />

  </BulkSet1>

  <BulkSet2>

    <add key="key1" value="" />

    <add key="key2" value="" />

    <add key="key3" value="" />

  </BulkSet2>

</configuration>

This is the wxs file to install app.config file:

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

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

     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

    <Product Id="78c586b0-3f59-405f-9f2e-a0370b7be99b"

           Name="XmlFileUpdate"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="XmlFileUpdate"

           UpgradeCode="1f711338-7bcc-4cc3-bcd9-1a47bf128409">

        <Package InstallerVersion="200" Compressed="yes" />

 

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

 

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

            <Directory Id="ProgramFilesFolder">

                <Directory Id="INSTALLLOCATION" Name="XmlFileUpdate">

                    <Component Id="ProductComponent"

                     Guid="03ade8d5-cdab-4a50-a950-e16cebb8bbae">

            <File Id="AppConfig"

                  DiskId="1"

                  Name="app.config"

                  Source="app.config"

                  Vital="yes"

                  KeyPath="yes" />

          </Component>

                </Directory>

            </Directory>

        </Directory>

 

        <Feature Id="ProductFeature" Title="XmlFileUpdate" Level="1">

            <ComponentRef Id="ProductComponent" />

        </Feature>

    </Product>

</Wix>

    Set attribute or element value 

Now, let's set the value of "NoValue" element (<add> element with key attrubute set to "NoValue) to the value of INSTALLLOCATION property.  In other words, from this:

<add key="NoValue" value="" />

we want to get this result:

<add key="NoValue" value="C:\Program Files\XmlFileUpdate\"/>

All we need to do is to add to component XmlFile element:

<Component Id="ProductComponent"

           Guid="03ade8d5-cdab-4a50-a950-e16cebb8bbae">

  <File Id="AppConfig"

        DiskId="1"

        Name="app.config"

        Source="app.config"

        Vital="yes"

        KeyPath="yes" />

 

  <util:XmlFile Id="SetKey1"

                Action="setValue"

                ElementPath="//appSettings/add[\[]@key='NoValue'[\]]/@value"

                Value="[INSTALLLOCATION]"

                File="[#AppConfig]"

                SelectionLanguage="XPath"

                Sequence="1" />

 

</Component>

 

The following attributes need to be set:

  • Action attribute is set to setValue.
  • ElementPath attribute contains an XPath or XSLTPattern expression pointing to attribute which value we want to set.
  • Value attribute contains new formatted value of the attribute.
  • File attribute points to a file where changes will be made.  This value is also formatted and that is why we can use [#FileId] notation instead of providing full path to XML file.
  • Sequence attribute defines the order in which modification will be made.  This value matters only when we need to create new element with attribute.  In this case we need to ensure that element is created before attempting to create an attribute.  As a good practice, always set this value in order in which you prefer items to be updated.

Alternative version will be to use XPath expression, pointing to an element and use Name attribute to set which attribute's value to change:

<util:XmlFile Id="SetKey2"

              Action="setValue"

              ElementPath="//appSettings/add[\[]@key='NoValue2'[\]]"

              Value="[INSTALLLOCATION]"

              Name="value"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="2" />

To set the value of existing attribute to an empty string we need to just omit the Value attribute:

<util:XmlFile Id="SetKey3"

              Action="setValue"

              ElementPath="//appSettings/add[\[]@key='ExtraAttribute'[\]]"

              Name="value"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="3" />

Tip:  To target an element, provide XPath (or XSLPattern) expression pointing to an element and do not specify Name attribute. To target an attribute - either provide XPath expression pointing to element and set the name of an attribute in the Name attribute, or provide XPath expression pointing to attribute and do not specify Name attribute.

Based on the tip above, here is how we set the text value of an element:

<util:XmlFile Id="SetKey4"

              Action="setValue"

              ElementPath="//UserName"

              Value="[%USERNAME]"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="4" />

And this is how to set element's text value to empty string:

<util:XmlFile Id="SetKey5"

              Action="setValue"

              ElementPath="//Parameters"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="5" />

    Delete attribute or element' text value

Here is how we can delete an attribute:

<util:XmlFile Id="DeleteKey6"

              Action="deleteValue"

              ElementPath="//appSettings/add[\[]@key='ExtraAttribute'[\]]"

              Name="extra"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="6" />

Here Action attribute is set to deleteValue.  The meaning of all other attributes is the same as in previous examples.  ElementPath attribute points to a node and Name attribute contains the name of the attribibute to be deleted.

There is inconsistency in the way how XmlFile element works if we will omit the Name attribute.  Instead of removing element it removes text value of the element, just like Sequence="5" in example above.  So, here is how to remove text value of an element:

<util:XmlFile Id="DeleteKey7"

              Action="deleteValue"

              ElementPath="//EmptyElement"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="7" />

     Create element and attribute

To create a new element, we need to set Action attribute to createElement:

<util:XmlFile Id="CreateElement1"

              Action="createElement"

              ElementPath="//configuration"

              Name="NewElement1"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="8" />

To create a new element and set its text value we need to add Value attribute:

<util:XmlFile Id="CreateElement2"

              Action="createElement"

              ElementPath="//configuration"

              Name="NewElement2"

              Value="New Value"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="9" />

Creating element with attributes is little bit more complicated.  First, we need to create an element and then create attributes.  That is where the value of Sequence attribute is very important.  Let's create new add element in appSettings section:

<util:XmlFile Id="CreateElement3"

              Action="createElement"

              ElementPath="//appSettings"

              Name="add"

              File="[#AppConfig]"

              Sequence="10" />

<util:XmlFile Id="CreateAttribute1"

              Action="setValue"

              ElementPath="//appSettings/add[\[]not(@key)[\]]"

              Name="key"

              Value="NewParameter"

              File="[#AppConfig]"

              Sequence="11" />

<util:XmlFile Id="CreateAttribute2"

              Action="setValue"

              ElementPath="//appSettings/add[\[]@key='NewParameter'[\]]"

              Name="value"

              Value="NewValue"

              File="[#AppConfig]"

              Sequence="12" />

First, we create a new add element (Sequence 10). Second, we create a key attribute.  XPath expression is searching for an add element which does not have a key attribute (Sequence 11).  Last XmlFile element creates a value attribute (Sequence 12).

    Bulk set

XmlFile element supports bulk update.

For example, from this:

<BulkSet1>

  <add key="key1" value=""/>

  <add key="key2" value=""/>

  <add key="key3" value=""/>

</BulkSet1>

we want to get this:

<BulkSet1>

  <add key="key1" value="C:\Program Files\XmlFileUpdate\"/>

  <add key="key2" value="C:\Program Files\XmlFileUpdate\"/>

  <add key="key3" value="C:\Program Files\XmlFileUpdate\"/>

</BulkSet1>

Basically, we want to set every value attribute to the value of INSTALLLOCATION property.  Using regular setValue as a value for Action attribute:

<util:XmlFile Id="BulkSetKey1"

              Action="setValue"

              ElementPath="//configuration/BulkSet1/add"

              Name="value"

              Value="[INSTALLLOCATION]"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="13" />

creates the following result:

<BulkSet1>

  <add key="key1" value="C:\Program Files\XmlFileUpdate\"/>

  <add key="key2" value=""/>

  <add key="key3" value=""/>

</BulkSet1>

To fix this, we need to set Action attribute to bulkSetValue:

<util:XmlFile Id="BulkSetKey2"

              Action="bulkSetValue"

              ElementPath="//configuration/BulkSet2/add"

              Name="value"

              Value="[INSTALLLOCATION]"

              File="[#AppConfig]"

              SelectionLanguage="XPath"

              Sequence="14" />

Here is the result:

<BulkSet2>

  <add key="key1" value="C:\Program Files\XmlFileUpdate\"/>

  <add key="key2" value="C:\Program Files\XmlFileUpdate\"/>

  <add key="key3" value="C:\Program Files\XmlFileUpdate\"/>

</BulkSet2>

 

This is pretty much all we can do with XmlFile element.  Source code for post is in attachment.

Posted by Alex Shevchuk | 2 Comments
Filed under: , ,

Attachment(s): XmlFileUpdate.zip

From MSI to WiX, Part 24 - DLL Custom Actions - Get property value and obtaining/changing session information

In previous post we looked at how to set propery's value.  Getting property value is more involved because we need to make sure that provided memory buffer for property value is big enough to hold the value.  Function MsiGetProperty is used to get a property value.  Description for MsiGetProperty function has a very good sample on how to get value of a property.

WiX API has a set of functions to get value of a property:

  • WcaGetProperty - returns value of a property.
  • WcaGetIntProperty - returns an integer value of a property.
  • WcaGetFormattedProperty - returns formatted value of a property.  This function is using WcaGetFormattedString function which we will mention again when we will discuss Record Functions.

Here are samples of how to use these functions:

HRESULT hr = S_OK;

LPWSTR pwzPropertyData = NULL;

 

hr = WcaGetProperty( L"MyProperty", &pwzPropertyData);

ExitOnFailure(hr, "Failed to get MyProperty");

 

int iAllUsers = 0;

 

hr = WcaGetIntProperty(L"ALLUSERS", &iAllUsers);

ExitOnFailure(hr, "Failed to get value of ALLUSERS property");

 

LPWSTR pwzFormattedPropertyData = NULL;

 

hr = WcaGetFormattedProperty( L"MyFormattedProperty", &pwzFormattedPropertyData);

ExitOnFailure(hr, "Failed to get MyFormattedProperty");

To determine the length of value property is holding, we initially pass an empty string and zero as a length of the buffer for value.  When property is set and property's value is not an empty string, MsiGetProperty function's return value is ERROR_MORE_DATA.  If property is not set or its value is an empty string, return value is ERROR_SUCCESS.  WiX API WcaIsPropertySet function returns TRUE if the length of the property's value is more than zero.  You can use this helper function to check if property is set.

if (WcaIsPropertySet("CustomActionData"))

{

}

 

    Installation session information

To get or set installation session variables we can use the following functions.

    MsiGetLanguage 

Returns the language ID of currently running installation.  This is a very simple function and does not have wrapper function in WiX API.

LANGID langid = ::MsiGetLanguage(WcaGetInstallHandle());

    MsiGetMode

Use this function to determine current mode for installation.  Most often this function will be used to determine if custom action is running as scheduled, rollback, or commit custom action.  We can also check for conditions like administartive install, product advertising install, or maintenace install in addition to other modes.  To get the full list of modes we can verify look at MsiGetMode function description.

BOOL fRollback = ::MsiGetMode(WcaGetInstallHandle(), MSIRUNMODE_ROLLBACK);

    MsiSetMode

To request immediate or after successful installation reboot, use MsiSetMode function.

DWORD er = ERROR_SUCCESS;

 

er = ::MsiSetMode(WcaGetInstallHandle(), MSIRUNMODE_REBOOTATEND, TRUE);

    MsiSetInstallLevel

To change the installation level, use MsiSetInstallLevel function.  For more information on installation level, INSTALLLEVEL property, and how it affects features to be installed, see Part 4 in this blog.

UINT er = ::MsiSetInstallLevel(WcaGetInstallHandle(), 100);

    MsiGetSourcePath

This function returns the full source path for a folder listed in the Directory table (or Directory element in WiX).

DWORD cchPath = MAX_PATH + 1;

TCHAR* szPath = new TCHAR[cchPath];

UINT err = ::MsiGetSourcePath(WcaGetInstallHandle(), TEXT("INSTALLLOCATION"), szPath, &cchPath);

if(ERROR_SUCCESS == err)

{

    WcaSetProperty(L"INSTALLFROMPATH", szPath);

}

delete [] szPath;

 

    MsiGetTargetPath

This function returns the full target path for a folder listed in the Directory table (or Directory element in WiX).

DWORD cchPath = MAX_PATH + 1;

TCHAR* szPath = new TCHAR[cchPath];

UINT err = ::MsiGetTargetPath(WcaGetInstallHandle(), TEXT("INSTALLLOCATION"), szPath, &cchPath);

if(ERROR_SUCCESS == err)

{

    WcaSetProperty(L"INSTALLTOPATH", szPath);

}

delete [] szPath;

WiX API has a helper function - WcaGetTargetPath.  This function checks first the size of the target folder and allocates enough memory to hold the target path.  Allocated string must be released by using ReleaseStr function:

LPWSTR pwzTargetPath = NULL;

HRESULT hr = WcaGetTargetPath(L"INSTALLLOCATION", &pwzTargetPath);

if(SUCCEEDED(hr))

{

    WcaSetProperty(L"INSTALLFROMPATH", pwzTargetPath);

}

ReleaseStr(pwzTargetPath);

    MsiSetTargetPath

This function sets the full target path for a folder listed in the Directory table (or Directory element in WiX).

::MsiSetTargetPath(WcaGetInstallHandle(), TEXT("INSTALLLOCATION"), TEXT("c:\\CAIntro\\"));

    MsiVerifyDiskSpace

This function checks to see if sufficient disk space is present for the current installation.

switch(::MsiVerifyDiskSpace(WcaGetInstallHandle()))

{

case ERROR_SUCCESS:

    WcaLog(LOGMSG_STANDARD, "We have enough of disk space to complete installation.");

    break;

case ERROR_DISK_FULL:

    WcaLog(LOGMSG_STANDARD, "We don't have enough of disk space to complete installation.");

    break;

default:

    WcaLog(LOGMSG_STANDARD, "Failed to call MsiVerifyDiskSpace.");

    break;

}

No attachments this time.

 

Posted by Alex Shevchuk | 0 Comments
Filed under: , ,

Documentation on WiX API

I just want to let everybody know that I am working on documenting WiX API, namely - wcautil and dutil libraries.

I don't mind if this will become part of official WiX documentation.  In fact, I hope - it will, because I personally, would like to see something like this in the WiX help file.

Any feedback and help are welcome.

I am going to update attachment(s) as often as I will have significant enough amount of changes in the content, so, if you want to have an update, check it regularly.

Thanks,

Alex

 

Posted by Alex Shevchuk | 2 Comments
Filed under:

Attachment(s): WixAPI.chm

From MSI to WiX, Part 23 - DLL Custom Actions - Logging and setting properties

Let's start with setting property value using MSI API MsiSetProperty.  This function takes three parameters:

  1. An installation handle, originally passed to custom action's function by Windows Installer.
  2. Name of the property to set or remove.
  3. New value for the property or NULL (or empty string) to remove property.

To get the installation handle we could cache it in some global variable, but WiX provides caching already in the form of WcaInitialize (to cache handle) and WcaGetInstallHandle (to get cached value) functions.

Here is how typical code to set property looks like:

LPWSTR szName = L"PROPNAME";

LPWSTR szValue = L"VALUE";

hr = HRESULT_FROM_WIN32(::MsiSetProperty(WcaGetInstallHandle(), szName, szValue));

ExitOnFailure(hr, "Failed to call PROPNAME");

Same code, but written using WiX API will look like this:

LPWSTR szName = L"PROPNAME";

LPWSTR szValue = L"VALUE";

hr = WcaSetProperty(szName, szValue);

ExitOnFailure(hr, "Failed to call PROPNAME");

Little bit less code to write, but WiX API also provides additional function WcaSetIntProperty, which takes second parameter as integer, so you don't have to do conversion of integer to string yourself.  This function we used in our sample to set the value of HWDOCKINFO property:

hr = WcaSetIntProperty(L"HWDOCKINFO", hwProfInfo.dwDockInfo);

ExitOnFailure(hr, "Failed to set HWDOCKINFO property value");

 

    Logging

To log informative message to the installation log file we need to call MsiProcessMessage function from Windows Installer API with second parameter set to INSTALLMESSAGE_INFO.  First parameter for this function is usual installation handle, which we already discussed previously.  Third parameter - is a handle to record, containing message text.  Let's see an example:

PMSIHANDLE hRecord = ::MsiCreateRecord(1);

if(NULL != hRecord)

{

    er = ::MsiRecordSetString(hRecord, 0, TEXT("GetHardwareProfile completed successfully"));

    if(ERROR_SUCCESS == er)

    {

        er = ::MsiProcessMessage(WcaGetInstallHandle(), INSTALLMESSAGE_INFO, hRecord);

    }

}

The same can be done with just one line of code if we will be using WiX API:

WcaLog(LOGMSG_STANDARD, "GetHardwareProfile completed successfully");

WcaLog even supports formatting message string with parameters:

WcaLog(LOGMSG_STANDARD, "%s completed successfully", "GetHardwareProfile");

You can find more information on WcaLog in here.

WcaLog puts a limit on total length of resulting (after formatting is done) message string.  This limit is, as of now, 2048 characters.

There is one more specialized function - WcaLogError.  Instead of LOGLEVEL, it takes HRESULT as a first parameter.  It formats the error message just like WcaLog, but it writes into log file the following message:

Error 0x<HRESULT>: <MESSAGE>

In the next post we will continue discussion of available MSI and WiX API functions.

 

Posted by Alex Shevchuk | 0 Comments
Filed under: , ,

From MSI to WiX, Part 22 - DLL Custom Actions - Introduction

Today I am starting a mini series on writing C++ custom actions.

Let's start with wizard-generated custom action project.  Start Visual Studio and select "C++ Custom Action Project".  Set "CAIntro" as the name of the project.

Before we will start discussing what is in the generated code, let's talk about what Windows Installer is expecting from dll in order to be a good behaving MSI custom action dll.

First of all, dll must provide the usual dll entry point - DllMain function.  On process attach event we want to save HINSTANCE parameter in case later we will need to call some API function which requires it.

Second, we need to add custom action function.  The signature of custom action entry point function is:

UINT __stdcall CustomActionEntryPoint(MSIHANDLE hInstall)

{

    return ERROR_SUCCESS;

}

As you already know, valid return values from DLL custom action are:

  • ERROR_FUNCTION_NOT_CALLED
  • ERROR_SUCCESS
  • ERROR_INSTALL_USER_EXIT
  • ERROR_INSTALL_FAILURE
  • ERROR_NO_MORE_ITEMS

Input parameter hInstall, passed by Windows Installer to custom action, is an installation handle.  There are few functions we can call from custom action which require this parameter and we will discuss them later in this post and in subsequent posts as well.

Third, and last, we need to export custom action function in the definition file:

LIBRARY "CAIntro"

 

EXPORTS

 

CustomActionEntryPoint

Now, let's take a look at the generated code.  Here is the content of CustomAction.cpp file:

#include "stdafx.h"

 

UINT __stdcall CustomAction1(MSIHANDLE hInstall)

{

    HRESULT hr = S_OK;

    UINT er = ERROR_SUCCESS;

 

    hr = WcaInitialize(hInstall, "CustomAction1");

    ExitOnFailure(hr, "Failed to initialize");

 

    WcaLog(LOGMSG_STANDARD, "Initialized.");

 

    // TODO: Add your custom action code here.

 

 

LExit:

    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;

    return WcaFinalize(er);

}

 

 

// DllMain - Initialize and cleanup WiX custom action utils.

extern "C" BOOL WINAPI DllMain(

    __in HINSTANCE hInst,

    __in ULONG ulReason,

    __in LPVOID

    )

{

    switch(ulReason)

    {

    case DLL_PROCESS_ATTACH:

        WcaGlobalInitialize(hInst);

        break;

 

    case DLL_PROCESS_DETACH:

        WcaGlobalFinalize();

        break;

    }

 

    return TRUE;

}

WiX comes with its own API library which provides a set of convenient wrappers for Windows Installer API.  We'll discuss them later in greater details.  For now, let's make some changes to the project.

  1. Add new source file to the project.  Right-click on "Source Files" and select Add->New Item..
  2. Click on "C++ File (.cpp)"
  3. In the Name text box type: GetHardwareProfile
  4. Click Add button

Now, cut the whole body of CustomAction1 function from CustomAction.cpp and paste it into GetHardwareProfile.cpp.  Also, copy #include statement from CustomAction.cpp to GetHardwareProfile.cpp.

In GetHardwareProfile.cpp, rename CustomAction1 to GetHardwareProfile.  Don't forget to change it in the WcaInitialize as well.  Open CustomAction.def and change CustomAction1 to GetHardwareProfile.

For this sample we will use GetCurrentHwProfile windows API to set 3 properties: HWPROFILENAME, HWPROFILEGUID, and HWDOCKINFO.  Here is the full source code for our custom action:

#include "stdafx.h"

 

UINT __stdcall GetHardwareProfile(MSIHANDLE hInstall)

{

    HRESULT hr = S_OK;

    UINT er = ERROR_SUCCESS;

 

    hr = WcaInitialize(hInstall, "GetHardwareProfile");

    ExitOnFailure(hr, "Failed to initialize");

 

    WcaLog(LOGMSG_STANDARD, "Initialized.");

 

    // TODO: Add your custom action code here.

    LPWSTR pwzPropertyValue = NULL;

    HW_PROFILE_INFO hwProfInfo;

 

    if (::GetCurrentHwProfile(&hwProfInfo))

    {

        hr = WcaSetProperty(L"HWPROFILENAME", hwProfInfo.szHwProfileName);

        ExitOnFailure(hr, "Failed to set HWPROFILENAME property value");

 

        hr = WcaSetProperty(L"HWPROFILEGUID", hwProfInfo.szHwProfileGuid);

        ExitOnFailure(hr, "Failed to set HWPROFILEGUID property value");

 

        hr = WcaSetIntProperty(L"HWDOCKINFO", hwProfInfo.dwDockInfo);

        ExitOnFailure(hr, "Failed to set HWDOCKINFO property value");

    }

    else

    {

        hr = HRESULT_FROM_WIN32(::GetLastError());

        ExitOnFailure(hr, "Failed to call GetCurrentHwProfile");

    }

 

LExit:

    er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE;

    return WcaFinalize(er);

}

 

We will discuss all functions later, but for now I'll provide just short description of what is going on in here:

  • WcaInitialize - initializes some WiX API internal variables.  It alse caches hInstall to be used later in Windows Installer API calls.
  • ExitOnFailure - If hr has an error code in it, logs message and passes control to LExit label.
  • WcaLog - logs message to the log file.
  • WcaSetProperty - set the string value to property.
  • WcaSetIntValue - set the integer value to property.
  • WcaFinalize - releases WiX API internal resources and returns final exit code.

And here is how we can call this custom action:

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

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

     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

  <Product Id="95872e2d-8175-4661-a087-5aa8ca2db230"

           Name="CAIntroInstall"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="CAIntroInstall"

           UpgradeCode="8124a77f-4991-47e6-aa63-557df0e51d26">

    <Package InstallerVersion="200" Compressed="yes" />

 

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

 

    <Property Id="HWPROFILENAME" Secure="yes" />

    <Property Id="HWPROFILEGUID" Secure="yes" />

    <Property Id="HWDOCKINFO" Secure="yes" />

 

    <Binary Id="CAIntro" SourceFile="..\bin\CAIntro.dll"/>

 

    <CustomAction Id="HardwareProfile"

                  BinaryKey="CAIntro"

                  DllEntry="GetHardwareProfile"

                  Execute="immediate"

                  Return="check"

                  HideTarget="no" />

 

    <InstallExecuteSequence>

      <Custom Action="HardwareProfile" After="AppSearch" />

    </InstallExecuteSequence>

 

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

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="CAIntroInstall">

          <Component Id="ProductComponent" Guid="6335fc54-7f5a-46ae-9309-6b7ab214a00d">

 

            <File Id="XmlFile"

                 Name="test.xml"

                 Source="test.xml"

                 KeyPath="yes" />

            <util:XmlFile Id="SetHWPROFILENAME"

                          Action="setValue"

                          ElementPath="//config/parameter[\[]@name='ProfileName'[\]]/@value"

                          Value="[HWPROFILENAME]"

                          File="[#XmlFile]" />

            <util:XmlFile Id="SetHWPROFILEGUID"

                          Action="setValue"

                          ElementPath="//config/parameter[\[]@name='ProfileGuid'[\]]/@value"

                          Value="[HWPROFILEGUID]"

                          File="[#XmlFile]" />

            <util:XmlFile Id="SetHWDOCKINFO"

                          Action="setValue"

                          ElementPath="//config/parameter[\[]@name='DockInfo'[\]]/@value"

                          Value="[HWDOCKINFO]"

                          File="[#XmlFile]" />

 

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="CAIntroInstall" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

 

    Functions which require an installation handle

     Installation session information

    Features information

    Components information

    Properties

    Logging

    Execute action

    Evaluate condition

    Database handle

    Database functions

    Obtaining a record containg all primary keys for a table

    Get the status of database

     Create view

    View functions

     Record functions

     Handle functions

 

In the next post we will take a closer look at our sample.  We will discuss how to log a message to the log file and how to set property's value using Windows Installer API and how we did it using WiX API.

 Source code for the sample is in attachment.

 

Posted by Alex Shevchuk | 1 Comments
Filed under: , ,

Attachment(s): CAIntro.zip

Можно ли использовать данные, полученные при использовании RegistrySearch в RegistryValue

English version is here.

 

Вопрос:

Можно ли использовать данные, полученные при использовании RegistrySearch в RegistryValue? Нечто вроде этого:

 

<Property Id="PROP1">

    <RegistrySearch Id="search1"

                    Root="HKLM"

                    Key="somekey"

                    Name="somename"

                    Type="raw" />

</Property>

...

<RegistryValue Type="???" Name="othervalue" Value="PROP1" />

 

Прежде всего, для того чтобы использовать свойство необходимо, чтобы атрибут элемента имел тип данных Formatted.  Документация по таблице Registry и элементу RegistryValue указывает, что как колонка Value, так и атрибут Value на самом деле являются Formatted.  Так что мы можем продолжить.  Единственное, что нам надо изменить, это значение атрибута Value:

<RegistryValue Type="???" Name="othervalue" Value="[PROP1]" /> 

Теперь, как насчет атрибута Type?  Какое значение он должен иметь?

В документации по таблице Registry говорится о том, что обычно значение в колонке Value будет интерпретировано как строка (REG_SZ) при условии, что значение не имеет в начале строки один из специальных префиксов и не содержит тильда последовательностей [~]:

Префикс Значение
#x Шестнадцатеричное число (REG_BINARY)
#% Расширяемая (еxpandable) строка (REG_EXPAND_SZ)
# Целое число (REG_DWORD)
Contains [~] Список строк разделенных друг от друга значением Null (REG_MULTI_SZ).

Это означает, что мы можем использовать Type="string" при условии, что мы дадим правильный формат значениям.  Следующий фрагмент успешно доказывает это:

<SetProperty Id="STRINGPROPCONST" After="AppSearch" Value="QWERTY" />

<SetProperty Id="DWORDPROPCONST" After="AppSearch" Value="#2" />

<SetProperty Id="EXPANDSZPROPCONST" After="AppSearch" Value="#%%SystemRoot%\System32\svchost.exe" />

<SetProperty Id="BINARYPROPCONST" After="AppSearch" Value="#x0123456789ABCDEF" />

<SetProperty Id="MULTISZPROPCONST" After="AppSearch" Value="a[~]b[~]c" />

 

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

  <Directory Id="LocalAppDataFolder">

    <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

      <Component Id="ProductComponent"

                 Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

        <CreateFolder />

        <RemoveFolder Id="RemoveMe" On="uninstall" />

        <RegistryKey Id="TestValue"

                     Root="HKCU"

                     Key="ACME Corp"

                     Action="createAndRemoveOnUninstall">

           <RegistryValue Id="StringValueConst"

                          Type="string"

                          Action="write"

                          Name="StringPropertyConst"

                          Value="[STRINGPROPCONST]" />

           <RegistryValue Id="DWordValueConst"

                          Type="string"

                          Action="write"

                          Name="DWordPropertyConst"

                          Value="[DWORDPROPCONST]" />

           <RegistryValue Id="ExpandSzValueConst"

                          Type="string"

                          Action="write"

                          Name="ExpandPropertyConst"

                          Value="[EXPANDSZPROPCONST]" />

           <RegistryValue Id="BinaryValueConst"

                          Type="string"

                          Action="write"

                          Name="BinaryPropertyConst"

                          Value="[BINARYPROPCONST]" />

           <RegistryValue Id="MultiSzValueConst"

                          Type="string"

                          Action="write"

                          Name="MultiSzPropertyConst"

                          Value="[MULTISZPROPCONST]" />

        </RegistryKey>

      </Component>

    </Directory>

  </Directory>

</Directory>

Теперь, как нам отформатировать данные, прочитанные из реестра (registry) во время AppSearch?  В документации по таблице RegLocator говорится о том, что Windows Installer сам добавит префикс, соответствующий типу данных в реестре к значению из реестра.  Здорово.  Давайте проверим, так ли это на самом деле:

<Property Id="STRINGPROP">

  <RegistrySearch Id="Search1"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="ObjectName"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="DWORDPROP">

  <RegistrySearch Id="Search2"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="Start"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="EXPANDSZPROP">

  <RegistrySearch Id="Search3"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="ImagePath"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="BINARYPROP">

  <RegistrySearch Id="Search4"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="FailureActions"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="MULTISZPROP">

  <RegistrySearch Id="Search5"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="DependOnService"

                  Type="raw"

                  Win64="no"/>

</Property>

 

<SetProperty Id="STRINGPROPRAW" After="AppSearch" Value="..[STRINGPROP]" />

<SetProperty Id="DWORDPROPRAW" After="AppSearch" Value="..[DWORDPROP]" />

<SetProperty Id="EXPANDSZPROPRAW" After="AppSearch" Value="..[EXPANDSZPROP]" />

<SetProperty Id="BINARYPROPRAW" After="AppSearch" Value="..[BINARYPROP]" />

<SetProperty Id="MULTISZPROPRAW" After="AppSearch" Value="..[MULTISZPROP]" />

 

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

  <Directory Id="LocalAppDataFolder">

    <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

      <Component Id="ProductComponent"

                 Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

        <CreateFolder />

        <RemoveFolder Id="RemoveMe" On="uninstall" />

        <RegistryKey Id="TestValue"

                     Root="HKCU"

                     Key="ACME Corp"

                     Action="createAndRemoveOnUninstall">

 

          <RegistryValue Id="StringValueRaw"

                         Type="string"

                         Action="write"

                         Name="StringPropertyRaw"

                         Value="[STRINGPROPRAW]" />

          <RegistryValue Id="DWordValueRaw"

                         Type="string"

                         Action="write"

                         Name="DWordPropertyRaw"

                         Value="[DWORDPROPRAW]" />

          <RegistryValue Id="ExpandSzValueRaw"

                         Type="string"

                         Action="write"

                         Name="ExpandPropertyRaw"

                         Value="[EXPANDSZPROPRAW]" />

          <RegistryValue Id="BinaryValueRaw"

                         Type="string"

                         Action="write"

                         Name="BinaryPropertyRaw"

                         Value="[BINARYPROPRAW]" />

          <RegistryValue Id="MultiSzValueRaw"

                         Type="string"

                         Action="write"

                         Name="MultiSzPropertyRaw"

                         Value="[MULTISZPROPRAW]" />

        </RegistryKey>

      </Component>

    </Directory>

  </Directory>

</Directory>

Как Вы видите, я сохранил значения из реестра в свойствах во время исполнения AppSearch и затем использовал Custom Action Type 51 для того, чтобы скопировать их в другие свойства, но с добавлением двух точек в начале строки.  Я сделал это для того, чтобы позже сохранить эти значения в реестре именно как строки, а не как другие типы данных.

Нет ничего удивительного в том, что мульти-строковые (multi-string) значения записаны в реестр как мульти-строковые, поскольку их интерпретация зависит от наличия [~] в строке, а не от того, с какого префикса они начинаются.  Что удивительно, это то что расширенные (expanded) строки записаны в реестр как уже преобразованные в строку, а не непреобразованное значение с префиксом #%, хотя документация по таблице RegLocator ясно говорит о том, что именно это и должно быть результатом.

Так что, за исключением expanded строк, все что нам нужно, это сохранить считанные во время AppSearch данные в реестр без каких либо изменений:

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

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

  <Product Id="30eb65de-f04a-4a79-a153-4542687b7515"

           Name="SetRegistry1"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="SetRegistry1"

           UpgradeCode="da268df3-4a01-41de-8c09-a8c11abee160">

  <Package InstallerVersion="200" Compressed="yes" InstallScope="perUser" />

 

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

 

    <Property Id="STRINGPROP">

      <RegistrySearch Id="Search1"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="ObjectName"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="DWORDPROP">

      <RegistrySearch Id="Search2"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="Start"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="EXPANDSZPROP">

      <RegistrySearch Id="Search3"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="ImagePath"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="BINARYPROP">

      <RegistrySearch Id="Search4"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="FailureActions"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="MULTISZPROP">

      <RegistrySearch Id="Search5"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="DependOnService"

                      Type="raw"

                      Win64="no"/>

    </Property>

 

    <SetProperty Id="STRINGPROPCONST" After="AppSearch" Value="QWERTY" />

    <SetProperty Id="DWORDPROPCONST" After="AppSearch" Value="#2" />

    <SetProperty Id="EXPANDSZPROPCONST" After="AppSearch" Value="#%%SystemRoot%\System32\svchost.exe" />

    <SetProperty Id="BINARYPROPCONST" After="AppSearch" Value="#x0123456789ABCDEF" />

    <SetProperty Id="MULTISZPROPCONST" After="AppSearch" Value="a[~]b[~]c" />

 

    <SetProperty Id="STRINGPROPRAW" After="AppSearch" Value="..[STRINGPROP]" />

    <SetProperty Id="DWORDPROPRAW" After="AppSearch" Value="..[DWORDPROP]" />

    <SetProperty Id="EXPANDSZPROPRAW" After="AppSearch" Value="..[EXPANDSZPROP]" />

    <SetProperty Id="BINARYPROPRAW" After="AppSearch" Value="..[BINARYPROP]" />

    <SetProperty Id="MULTISZPROPRAW" After="AppSearch" Value="..[MULTISZPROP]" />

 

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

      <Directory Id="LocalAppDataFolder">

        <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

          <Component Id="ProductComponent"

                     Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

            <CreateFolder />

            <RemoveFolder Id="RemoveMe" On="uninstall" />

            <RegistryKey Id="TestValue"

                         Root="HKCU"

                         Key="ACME Corp"

                         Action="createAndRemoveOnUninstall">

 

              <RegistryValue Id="StringValueConst"

                             Type="string"

                             Action="write"

                             Name="StringPropertyConst"

                             Value="[STRINGPROPCONST]" />

              <RegistryValue Id="DWordValueConst"

                             Type="string"

                             Action="write"

                             Name="DWordPropertyConst"

                             Value="[DWORDPROPCONST]" />

              <RegistryValue Id="ExpandSzValueConst"

                             Type="string"

                             Action="write"

                             Name="ExpandPropertyConst"

                             Value="[EXPANDSZPROPCONST]" />

              <RegistryValue Id="BinaryValueConst"

                             Type="string"

                             Action="write"

                             Name="BinaryPropertyConst"

                             Value="[BINARYPROPCONST]" />

              <RegistryValue Id="MultiSzValueConst"

                             Type="string"

                             Action="write"

                             Name="MultiSzPropertyConst"

                             Value="[MULTISZPROPCONST]" />

 

              <RegistryValue Id="StringValueRaw"

                             Type="string"

                             Action="write"

                             Name="StringPropertyRaw"

                             Value="[STRINGPROPRAW]" />

              <RegistryValue Id="DWordValueRaw"

                             Type="string"

                             Action="write"

                             Name="DWordPropertyRaw"

                             Value="[DWORDPROPRAW]" />

              <RegistryValue Id="ExpandSzValueRaw"

                             Type="string"

                             Action="write"

                             Name="ExpandPropertyRaw"

                             Value="[EXPANDSZPROPRAW]" />

              <RegistryValue Id="BinaryValueRaw"

                             Type="string"

                             Action="write"

                             Name="BinaryPropertyRaw"

                             Value="[BINARYPROPRAW]" />

              <RegistryValue Id="MultiSzValueRaw"

                             Type="string"

                             Action="write"

                             Name="MultiSzPropertyRaw"

                             Value="[MULTISZPROPRAW]" />

 

              <RegistryValue Id="StringValue"

                             Type="string"

                             Action="write"

                             Name="StringProperty"

                             Value="[STRINGPROP]" />

              <RegistryValue Id="DWordValue"

                             Type="string"

                             Action="write"

                             Name="DWordProperty"

                             Value="[DWORDPROP]" />

              <RegistryValue Id="ExpandSzValue"

                             Type="string"

                             Action="write"

                             Name="ExpandProperty"

                             Value="[EXPANDSZPROP]" />

              <RegistryValue Id="BinaryValue"

                             Type="string"

                             Action="write"

                             Name="BinaryProperty"

                             Value="[BINARYPROP]" />

              <RegistryValue Id="MultiSzValue"

                             Type="string"

                             Action="write"

                             Name="MultiSzProperty"

                             Value="[MULTISZPROP]" />

            </RegistryKey>

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="SetRegistry1" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

 

  </Product>

</Wix>

Итак, ответ на первоначальный вопрос - Да, если только мы не собираемся сохранять expandable строки.

 

Исходный код в attachement.

Posted by Alex Shevchuk | 0 Comments
Filed under: , ,

Attachment(s): SetRegistry1.zip

Как сохранить значения свойств использовавшихся при начальной установке

English version is here.

 

Один из наиболее часто задаваемых вопросов - как сохранить значение свойства, которое было присвоено свойству во время инсталлирования программы (присвоенное либо через параметер в командной строке или через пользовательский интерфейс как, например, INSTALLLOCATION).

В качестве примера возьмем одну из возможный ситуатций. Рассмотрим очень простой пример. Идея состоит в том, что пользователь передает через командную строку значение, которое необходимо записать в XML файл.  Также, необходимо чтобы это значение осталось неизменным при "починке" (repair).  Начнем с очень простого примера:

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

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

     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

 

  <?define UpgradeCode="{D1C829D8-5A5B-412C-90ED-7F92750F8152}"?>

  <?define CurrentVersion="1.0.0.0"?>

 

  <Product Id="*"

           Name="PreserveProperty"

           Language="1033"

           Version="$(var.CurrentVersion)"

           Manufacturer="PreserveProperty"

           UpgradeCode="$(var.UpgradeCode)">

    <Package InstallerVersion="200"

             Compressed="yes"

             InstallScope="perMachine"

             Languages="1033" />

 

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

 

    <!-- Make sure properties are passed in the command line -->

    <Condition Message="MYPROPERTY variable must be set in the command line">

      Installed OR MYPROPERTY

    </Condition>

   

    <!-- Installation -->

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

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="PreserveProperty">

          <Component Id="ProductComponent"

                     Guid="cf13f922-e33c-4b78-a849-46e32c000e09">

 

            <File Id="XmlFile"

                  Name="test.xml"

                  Source="test.xml"

                  KeyPath="yes" />

            <util:XmlFile Id="SetMYPROPERTY"

                          Action="setValue"

                          ElementPath="//config/parameter[\[]@name='MyProperty'[\]]/@value"

                          Value="[MYPROPERTY]"

                          File="[#XmlFile]" />

           

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="PreserveProperty" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

Для того чтобы проверить, что свойство MYPROPERTY было включено в командную строку, используется Custom Action Type 19 (элемент <Condition>).

Во время установки, инсталлятор скопирует файл install test.xml и установит значение атрибута value в значение свойства MYPROPERTY.  Файл test.xml выглядит следующим образом:

<config>

 <parameter name='MyProperty' value='' />

</config>

Если Вы скомпилируете пример и установите его, то Вы увидите, что файл test.xml создан и значение атрибута value то же, что и переданное через командную строку.  Попробуйте установить пример используя следующую командную строку:

msiexec /i PreserveProperty.msi MYPROPERTY="123"

Можно также попробовать и без свойства MYPROPERTY в командной строке.  Программа выдаст сообщение об ошибке из элемента <Condition>.

Теперь, попробуем починить инсталляцию.  Это можно сделать через Add/Remove Programs, меню Repair.  После того, как починка окончена, откройте файл test.xml.  Вы увидите, что значение атрибута value пусто.  Проблема в том, что MYPROPRTY="123" не было использовано при запуске msiexec.exe. Нашей обязанностью является сохранение значения свойства переданного msiexec.exe во время первоначальной установки.

Давайте внесем изменения в наш WiX файл:

  1. Сохраним значение свойства в реестре (registry)
  2. Восстановим значение свойства из реестра во время AppSearch (будем использовать временное свойство)
  3. Скопируем значение временного свойства в MYPROPRTY при условии, что это НЕ начальная установка

Первый пункт простой:

<RegistryKey Id="PreservePropertyInRegistry"

             Root="HKLM"

             Key="SOFTWARE\ACME Corp" Action="createAndRemoveOnUninstall">

  <RegistryValue Id="SavedProperty"

                 Action="write"

                 Type="string"

                 Name="SavedProperty"

                 Value="[MYPROPERTY]" />

</RegistryKey>

Второй пункт - обычный RegistrySearch: 

<Property Id="SAVEDMYPROPERTY" Secure="yes">

  <RegistrySearch Id="FindSavedMYPROPERTY"

                  Root="HKLM"

                  Key="SOFTWARE\ACME Corp"

                  Name="SavedProperty"

                  Type="raw"

                  Win64="no" />

</Property>

Третий пункт - элемент <SetProperty> (или Custom Action Type 51, если Вы все еще используете версию V2), но с добавленным условием, что программа уже установлена.

<SetProperty Id="MYPROPERTY" After="AppSearch" Value="[SAVEDMYPROPERTY]">

  Installed

</SetProperty>

Исходный код в attachment. 

 

Posted by Alex Shevchuk | 0 Comments
Filed under: , ,

Attachment(s): PreserveProperty.zip

Preserving properties used during install

Русская версия здесь.

 

One of the most common questions is how to preserve the value of property used during install (either passed through command line or properties like INSTALLLOCATION which can be changed in the UI) so that it can be used during maintenance or uninstall.

For example, here is one of the possible situations.  Consider this very simple example.  The idea here is that user passes the value to be written in the xml file through command line parameter.  We need to make sure that during repair that value will be preserved in the xml file.  Here is the source WiX file:

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

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

     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">

 

  <?define UpgradeCode="{D1C829D8-5A5B-412C-90ED-7F92750F8152}"?>

  <?define CurrentVersion="1.0.0.0"?>

 

  <Product Id="*"

           Name="PreserveProperty"

           Language="1033"

           Version="$(var.CurrentVersion)"

           Manufacturer="PreserveProperty"

           UpgradeCode="$(var.UpgradeCode)">

    <Package InstallerVersion="200"

             Compressed="yes"

             InstallScope="perMachine"

             Languages="1033" />

 

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

 

    <!-- Make sure properties are passed in the command line -->

    <Condition Message="MYPROPERTY variable must be set in the command line">

      Installed OR MYPROPERTY

    </Condition>

   

    <!-- Installation -->

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

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="PreserveProperty">

          <Component Id="ProductComponent"

                     Guid="cf13f922-e33c-4b78-a849-46e32c000e09">

 

            <File Id="XmlFile"

                  Name="test.xml"

                  Source="test.xml"

                  KeyPath="yes" />

            <util:XmlFile Id="SetMYPROPERTY"

                          Action="setValue"

                          ElementPath="//config/parameter[\[]@name='MyProperty'[\]]/@value"

                          Value="[MYPROPERTY]"

                          File="[#XmlFile]" />

           

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="PreserveProperty" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

To make sure MYPROPERTY is in the command line we will be using Custom Action Type 19 (<Condition> element).

Installer will install test.xml file and update value of the attribute to value of MYPROPERTY property.  Here is the content of test.xml:

<config>

 <parameter name='MyProperty' value='' />

</config>

If you will compile this sample and install it, you'll see that test.xml is created and its value is set to provided value in the command line.  Try it with the following command line:

msiexec /i PreserveProperty.msi MYPROPERTY="123"

You can also try to install it without providing MYPROPERTY.  Installation should fail with error message from <Condition> element.

Now, after installation is done, try to repair the installation.  After repair is completed, open test.xml.  You will see that the value of attribute is empty.  The problem is that during repair MYPROPRTY="123" is not passed to msiexec.exe.  It is our responsibility to preserve the value of property passed to msiexec.exe during initial install.

Let's modify our WiX file to:

  1. Save the value of property in registry during initial installation
  2. Recover saved value from registry during AppSearch
  3. Set the value of MYPROPRTY to value recovered from registry provided that this is NOT initial install

First item is straightforward:

<RegistryKey Id="PreservePropertyInRegistry"

             Root="HKLM"

             Key="SOFTWARE\ACME Corp" Action="createAndRemoveOnUninstall">

  <RegistryValue Id="SavedProperty"

                 Action="write"

                 Type="string"

                 Name="SavedProperty"

                 Value="[MYPROPERTY]" />

</RegistryKey>

Second item is just plain vanilla RegistrySearch: 

<Property Id="SAVEDMYPROPERTY" Secure="yes">

  <RegistrySearch Id="FindSavedMYPROPERTY"

                  Root="HKLM"

                  Key="SOFTWARE\ACME Corp"

                  Name="SavedProperty"

                  Type="raw"

                  Win64="no" />

</Property>

Third item is simple <SetProperty> (or Custom Action Type 51, if you still use V2), but conditioned to be executed during all types of install other than fresh install:

<SetProperty Id="MYPROPERTY" After="AppSearch" Value="[SAVEDMYPROPERTY]">

  Installed

</SetProperty>

Source code for this sample is in attachment. 

 

Posted by Alex Shevchuk | 2 Comments
Filed under: , ,

Attachment(s): PreserveProperty.zip

Is it possible to use RegistrySearch result in RegistryValue?

Русская версия здесь.

 

This post is an answer to this question sent to wix-users mailing list:

Is it possible to use RegistrySearch result in RegistryValue? Something like this:

 

<Property Id="PROP1">

    <RegistrySearch Id="search1"

                    Root="HKLM"

                    Key="somekey"

                    Name="somename"

                    Type="raw" />

</Property>

...

<RegistryValue Type="???" Name="othervalue" Value="PROP1" />

 

First of all, in order to use property, desired field must have Formatted data type.  Quick look to Registry table and RegistryValue element reveals that both Value column and Value attribute are in fact Formatted.  So, we are good to go.  The only change we need to make is to set Value attribute to:

<RegistryValue Type="???" Name="othervalue" Value="[PROP1]" /> 

Now, how about Type attribute?  What value do we need to use?

Description for Registry table is stating that normally the value of Value column is treated as string (REG_SZ) unless it is prefixed with one of the special prefixes or contains the sequence tilde [~]:

Prefix Meaning
#x Hexadecimal value (REG_BINARY)
#% Expandable string (REG_EXPAND_SZ)
# Integer (REG_DWORD)
Contains [~] Null-delimited list of strings (REG_MULTI_SZ).

That means that we should be able to always use Type="string" provided that we properly format values.  This fragment successfully proves that:

<SetProperty Id="STRINGPROPCONST" After="AppSearch" Value="QWERTY" />

<SetProperty Id="DWORDPROPCONST" After="AppSearch" Value="#2" />

<SetProperty Id="EXPANDSZPROPCONST" After="AppSearch" Value="#%%SystemRoot%\System32\svchost.exe" />

<SetProperty Id="BINARYPROPCONST" After="AppSearch" Value="#x0123456789ABCDEF" />

<SetProperty Id="MULTISZPROPCONST" After="AppSearch" Value="a[~]b[~]c" />

 

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

  <Directory Id="LocalAppDataFolder">

    <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

      <Component Id="ProductComponent"

                 Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

        <CreateFolder />

        <RemoveFolder Id="RemoveMe" On="uninstall" />

        <RegistryKey Id="TestValue"

                     Root="HKCU"

                     Key="ACME Corp"

                     Action="createAndRemoveOnUninstall">

           <RegistryValue Id="StringValueConst"

                          Type="string"

                          Action="write"

                          Name="StringPropertyConst"

                          Value="[STRINGPROPCONST]" />

           <RegistryValue Id="DWordValueConst"

                          Type="string"

                          Action="write"

                          Name="DWordPropertyConst"

                          Value="[DWORDPROPCONST]" />

           <RegistryValue Id="ExpandSzValueConst"

                          Type="string"

                          Action="write"

                          Name="ExpandPropertyConst"

                          Value="[EXPANDSZPROPCONST]" />

           <RegistryValue Id="BinaryValueConst"

                          Type="string"

                          Action="write"

                          Name="BinaryPropertyConst"

                          Value="[BINARYPROPCONST]" />

           <RegistryValue Id="MultiSzValueConst"

                          Type="string"

                          Action="write"

                          Name="MultiSzPropertyConst"

                          Value="[MULTISZPROPCONST]" />

        </RegistryKey>

      </Component>

    </Directory>

  </Directory>

</Directory>

So, how we can format data we read from registry during AppSearch?  Quick look at RegLocator table reveals that Windows Installer adds a prefix to the registry value that identifies the type of registry value.  Cool.  Let's test it to see if we already get the value we need:

<Property Id="STRINGPROP">

  <RegistrySearch Id="Search1"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="ObjectName"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="DWORDPROP">

  <RegistrySearch Id="Search2"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="Start"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="EXPANDSZPROP">

  <RegistrySearch Id="Search3"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="ImagePath"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="BINARYPROP">

  <RegistrySearch Id="Search4"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="FailureActions"

                  Type="raw"

                  Win64="no"/>

</Property>

<Property Id="MULTISZPROP">

  <RegistrySearch Id="Search5"

                  Root="HKLM"

                  Key="SYSTEM\CurrentControlSet\services\wscsvc"

                  Name="DependOnService"

                  Type="raw"

                  Win64="no"/>

</Property>

 

<SetProperty Id="STRINGPROPRAW" After="AppSearch" Value="..[STRINGPROP]" />

<SetProperty Id="DWORDPROPRAW" After="AppSearch" Value="..[DWORDPROP]" />

<SetProperty Id="EXPANDSZPROPRAW" After="AppSearch" Value="..[EXPANDSZPROP]" />

<SetProperty Id="BINARYPROPRAW" After="AppSearch" Value="..[BINARYPROP]" />

<SetProperty Id="MULTISZPROPRAW" After="AppSearch" Value="..[MULTISZPROP]" />

 

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

  <Directory Id="LocalAppDataFolder">

    <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

      <Component Id="ProductComponent"

                 Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

        <CreateFolder />

        <RemoveFolder Id="RemoveMe" On="uninstall" />

        <RegistryKey Id="TestValue"

                     Root="HKCU"

                     Key="ACME Corp"

                     Action="createAndRemoveOnUninstall">

 

          <RegistryValue Id="StringValueRaw"

                         Type="string"

                         Action="write"

                         Name="StringPropertyRaw"

                         Value="[STRINGPROPRAW]" />

          <RegistryValue Id="DWordValueRaw"

                         Type="string"

                         Action="write"

                         Name="DWordPropertyRaw"

                         Value="[DWORDPROPRAW]" />

          <RegistryValue Id="ExpandSzValueRaw"

                         Type="string"

                         Action="write"

                         Name="ExpandPropertyRaw"

                         Value="[EXPANDSZPROPRAW]" />

          <RegistryValue Id="BinaryValueRaw"

                         Type="string"

                         Action="write"

                         Name="BinaryPropertyRaw"

                         Value="[BINARYPROPRAW]" />

          <RegistryValue Id="MultiSzValueRaw"

                         Type="string"

                         Action="write"

                         Name="MultiSzPropertyRaw"

                         Value="[MULTISZPROPRAW]" />

        </RegistryKey>

      </Component>

    </Directory>

  </Directory>

</Directory>

As you can see, I collected registry values during AppSearch and then used Custom Action Type 51 to set other properties with collected values, but preprended with "..".  I did it because I want these values to be interpreted as strings.

It was expected that multi-string value will show up as multi-string, because its interpretation is based on presense of [~] and not on any particular prefix.  What is surprising is that expanded string property did show up as expanded and not prefixed with #% whereas description of RegLocator table clearly stating that it should be the case.

So, with the exception of expanded strings, all we need to do is to write values read during AppSearch to registry unchanged:

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

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

  <Product Id="30eb65de-f04a-4a79-a153-4542687b7515"

           Name="SetRegistry1"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="SetRegistry1"

           UpgradeCode="da268df3-4a01-41de-8c09-a8c11abee160">

  <Package InstallerVersion="200" Compressed="yes" InstallScope="perUser" />

 

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

 

    <Property Id="STRINGPROP">

      <RegistrySearch Id="Search1"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="ObjectName"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="DWORDPROP">

      <RegistrySearch Id="Search2"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="Start"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="EXPANDSZPROP">

      <RegistrySearch Id="Search3"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="ImagePath"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="BINARYPROP">

      <RegistrySearch Id="Search4"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="FailureActions"

                      Type="raw"

                      Win64="no"/>

    </Property>

    <Property Id="MULTISZPROP">

      <RegistrySearch Id="Search5"

                      Root="HKLM"

                      Key="SYSTEM\CurrentControlSet\services\wscsvc"

                      Name="DependOnService"

                      Type="raw"

                      Win64="no"/>

    </Property>

 

    <SetProperty Id="STRINGPROPCONST" After="AppSearch" Value="QWERTY" />

    <SetProperty Id="DWORDPROPCONST" After="AppSearch" Value="#2" />

    <SetProperty Id="EXPANDSZPROPCONST" After="AppSearch" Value="#%%SystemRoot%\System32\svchost.exe" />

    <SetProperty Id="BINARYPROPCONST" After="AppSearch" Value="#x0123456789ABCDEF" />

    <SetProperty Id="MULTISZPROPCONST" After="AppSearch" Value="a[~]b[~]c" />

 

    <SetProperty Id="STRINGPROPRAW" After="AppSearch" Value="..[STRINGPROP]" />

    <SetProperty Id="DWORDPROPRAW" After="AppSearch" Value="..[DWORDPROP]" />

    <SetProperty Id="EXPANDSZPROPRAW" After="AppSearch" Value="..[EXPANDSZPROP]" />

    <SetProperty Id="BINARYPROPRAW" After="AppSearch" Value="..[BINARYPROP]" />

    <SetProperty Id="MULTISZPROPRAW" After="AppSearch" Value="..[MULTISZPROP]" />

 

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

      <Directory Id="LocalAppDataFolder">

        <Directory Id="INSTALLLOCATION" Name="SetRegistry1">

          <Component Id="ProductComponent"

                     Guid="f4ca4e76-d04a-4b89-9a1a-7a666a42a635">

            <CreateFolder />

            <RemoveFolder Id="RemoveMe" On="uninstall" />

            <RegistryKey Id="TestValue"

                         Root="HKCU"

                         Key="ACME Corp"

                         Action="createAndRemoveOnUninstall">

 

              <RegistryValue Id="StringValueConst"

                             Type="string"

                             Action="write"

                             Name="StringPropertyConst"

                             Value="[STRINGPROPCONST]" />

              <RegistryValue Id="DWordValueConst"

                             Type="string"

                             Action="write"

                             Name="DWordPropertyConst"

                             Value="[DWORDPROPCONST]" />

              <RegistryValue Id="ExpandSzValueConst"

                             Type="string"

                             Action="write"

                             Name="ExpandPropertyConst"

                             Value="[EXPANDSZPROPCONST]" />

              <RegistryValue Id="BinaryValueConst"

                             Type="string"

                             Action="write"

                             Name="BinaryPropertyConst"

                             Value="[BINARYPROPCONST]" />

              <RegistryValue Id="MultiSzValueConst"

                             Type="string"

                             Action="write"

                             Name="MultiSzPropertyConst"

                             Value="[MULTISZPROPCONST]" />

 

              <RegistryValue Id="StringValueRaw"

                             Type="string"

                             Action="write"

                             Name="StringPropertyRaw"

                             Value="[STRINGPROPRAW]" />

              <RegistryValue Id="DWordValueRaw"

                             Type="string"

                             Action="write"

                             Name="DWordPropertyRaw"

                             Value="[DWORDPROPRAW]" />

              <RegistryValue Id="ExpandSzValueRaw"

                             Type="string"

                             Action="write"

                             Name="ExpandPropertyRaw"

                             Value="[EXPANDSZPROPRAW]" />

              <RegistryValue Id="BinaryValueRaw"

                             Type="string"

                             Action="write"

                             Name="BinaryPropertyRaw"

                             Value="[BINARYPROPRAW]" />

              <RegistryValue Id="MultiSzValueRaw"

                             Type="string"

                             Action="write"

                             Name="MultiSzPropertyRaw"

                             Value="[MULTISZPROPRAW]" />

 

              <RegistryValue Id="StringValue"

                             Type="string"

                             Action="write"

                             Name="StringProperty"

                             Value="[STRINGPROP]" />

              <RegistryValue Id="DWordValue"

                             Type="string"

                             Action="write"

                             Name="DWordProperty"

                             Value="[DWORDPROP]" />

              <RegistryValue Id="ExpandSzValue"

                             Type="string"

                             Action="write"

                             Name="ExpandProperty"

                             Value="[EXPANDSZPROP]" />

              <RegistryValue Id="BinaryValue"

                             Type="string"

                             Action="write"

                             Name="BinaryProperty"

                             Value="[BINARYPROP]" />

              <RegistryValue Id="MultiSzValue"

                             Type="string"

                             Action="write"

                             Name="MultiSzProperty"

                             Value="[MULTISZPROP]" />

            </RegistryKey>

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="SetRegistry1" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

 

  </Product>

</Wix>

So, the answer is - Yes, unless we want to preserve expandable string value.

 

Source code is in attachement.

 

Posted by Alex Shevchuk | 2 Comments
Filed under: , ,

Attachment(s): SetRegistry1.zip

From MSI to WiX, Part 20 - User Interface - Required Dialog Boxes

The main page for the series is here.

 

Introduction 

We will start with exploring Required Dialog Boxes.  Microsoft Windows Installer uses three special dialogs in response to the following events during product installation:

  • Exit Dialog - shown when installation completed successfully.  Required name for the dialog is ExitDialog.
  • Fatal Error Dialog - shown in response to installation termination due to the fatal error.  Required name for the dialog is FatalError.
  • User Exit Dialog - shown in response to user's request to cancel an installaton.  Required name for the dialog is UserExit.

Each of these dialogs has a special sequence number in both AdminUISequence and InstallUISequence tables:

  • Exit Dialog: -1
  • Fatal Error Dialog: -3
  • User Exit Dialog: -2

There is also one special dialog which Windows Installer uses to show an installation error.

For now, I will leave alone all the bells, whistles, and icing and will use the bare minimum set of WiX elements and their attributes, just enough to explain what needs to be done to make a required dialog.  So, here it goes.

Exit dialog

The Exit dialog is displayed at the end of successful installation.  The only requirement for this dialog is to have a button control which will be used by the user to close the dialog to complete the installation.  It is also advisable to give some sort of message to the user indicating that the installation is completed successfully, but as I said earlier, we will make our dialogs more "civilized" later.

By default, Microsoft Windows Installer is using System font to show text in the user interface.  This can cause the installer to improperly display text strings if the package's code page is different from the user's default UI code page.

The recomendation here is to set the DefaultUIFont property to one of the styles defined in the TextStyle table.  In WiX it is done like this:

<TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

<Property Id="DefaultUIFont" Value="DefaultFont" />

To make the correct implementation of Exit dialog the following must be done:

Here is the very simple implementation of the Exit dialog:

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

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

  <Product Id="732e6d89-5fa5-43a1-b590-88ebed02a05f"

           Name="UIRequiredDialogs"

           Language="1033"

           Version="1.0.0.0"

           Manufacturer="UIRequiredDialogs"

           UpgradeCode="8f2b749f-f563-4a4f-ab66-5a121bf07378">

    <Package InstallerVersion="200" Compressed="yes" />

 

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

 

    <UI>

      <Dialog Id="ExitDialog" Width="370" Height="270" Title="Exit Dialog">

        <Control Id="Exit"

                 Type="PushButton"

                 X="236" Y="243" Width="56" Height="17"

                 Default="yes"

                 Cancel="yes"

                 Text="Exit">

          <Publish Event="EndDialog" Value="Return">1</Publish>

        </Control>

      </Dialog>

 

      <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

      <Property Id="DefaultUIFont" Value="DefaultFont" />

 

      <InstallUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

      </InstallUISequence>

 

      <AdminUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

      </AdminUISequence>

    </UI>

 

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

      <Directory Id="ProgramFilesFolder">

        <Directory Id="INSTALLLOCATION" Name="UIRequiredDialogs">

          <Component Id="ProductComponent" Guid="df47e78b-325c-4371-ba28-10e899dad783">

            <File Id="Readme.txt" Name="Readme.txt" Source="Readme.txt" Vital="yes" KeyPath="yes" />

          </Component>

        </Directory>

      </Directory>

    </Directory>

 

    <Feature Id="ProductFeature" Title="UIRequiredDialogs" Level="1">

      <ComponentRef Id="ProductComponent" />

    </Feature>

  </Product>

</Wix>

As you can see, all UI-related elements are located inside the <UI> element.

The Id attribute of the <Dialog> element can have any value and must match the value of Dialog attribute of the <Show> element and this value does not have to be "ExitDialog".  What makes this dialog Exit dialog is the value of OnExit attribute of the <Show> element.  By setting OnExit attribute's value to "success" we are telling to WIX that this dialog must have sequence number -1.

It seems to me that X and Y attributes of the <Dialog> element in WIX are coordinates of the center of the dialog on the sceen as a percentage of the screen's width and height.  By default, their values will be set to 50 and dialog will appear at the center of the screen.

Width and Height attributes are the width and height of the dialog window.  These values are in Installer units.

<Control> element's position and size attributes are in Installer unitsDefault attribute set to "yes" means that pressing Enter key will have the same effect as clicking the button control.  By setting Cancel attribute's value to "yes" we are saying that closing the dialog by clicking X button in the right upper corner of the dialog will also have the same effect as clicking the button control.

To raise the EndDialog control event when user click the button, we must add <Publish> element as a chile element of the push button <Control> element.  Event attribute's value must be "EndDialog" (the name of the standard EndDialog control event) and Value attribute's value set to "Return" (means that control returns to the installer with the Success value).

Let's compile our project.  Ignore for now six ICE20 errors.  Install our test product and see how your dialog looks like.  Don't forget to uninstall the test product.

Fatal Error dialog

The FatalError dialog is displayed when installation is terminated because of a fatal error.  Requirements and implementation of this dialog are almost identical to requirements and implementation of Exit dialog.  The only difference is in the <Show> element (only relevant code is shown):

<Property Id="FailureProgram">

<![CDATA[

    Function Main()

      Main = 3

    End Function

    ]]>

</Property>

 

<CustomAction Id="FakeFailure"

              VBScriptCall="Main"

              Property="FailureProgram" />

 

<InstallExecuteSequence>

  <Custom Action='FakeFailure'

          Before='InstallInitialize'>TESTFAIL</Custom>

</InstallExecuteSequence>

 

<UI>

  <Dialog Id="FatalError"

          Width="370" Height="270"

          Title="Fatal Error Dialog">

    <Control Id="Finish" Type="PushButton"

             X="236" Y="243" Width="56" Height="17"

             Default="yes" Cancel="yes"

             Text="Exit">

      <Publish Event="EndDialog" Value="Exit">1</Publish>

    </Control>

  </Dialog>

     

  <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

  <Property Id="DefaultUIFont" Value="DefaultFont" />

 

  <InstallUISequence>

    <Show Dialog="FatalError" OnExit="error" />

  </InstallUISequence>

 

  <AdminUISequence>

    <Show Dialog="FatalError" OnExit="error" />

  </AdminUISequence>

</UI>

To immitate the fatal error I added custom action which returns a failure exit code. To test it you need to pass the TESTFAIL property in the command line:

Msiexec /I UIRequiredDialogs.msi TESTFAIL=1

User Exit dialog

The UserExit dialog is displayed when installation is terminated at the user's request.  Requirements and implementation of this dialog are identical to requirements and implementation of FatalError dialog:

<Property Id="UserExitProgram">

  <![CDATA[

    Function Main()

      Main = 2

    End Function

    ]]>

</Property>

 

<CustomAction Id="FakeUserExit"

              VBScriptCall="Main"

              Property="UserExitProgram" />

 

<InstallExecuteSequence>

  <Custom Action='FakeUserExit'

          Before='InstallInitialize'>TESTUSEREXIT</Custom>

</InstallExecuteSequence>

 

<UI>

  <Dialog Id="UserExit"

          Width="370" Height="270"

          Title="User Exit dialog">

    <Control Id="Finish" Type="PushButton"

             X="236" Y="243" Width="56" Height="17"

             Default="yes" Cancel="yes"

             Text="Exit">

      <Publish Event="EndDialog" Value="Exit">1</Publish>

    </Control>

  </Dialog>

 

  <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

  <Property Id="DefaultUIFont" Value="DefaultFont" />

 

  <InstallUISequence>

    <Show Dialog="UserExit" OnExit="cancel" />

  </InstallUISequence>

 

  <AdminUISequence>

    <Show Dialog="UserExit" OnExit="cancel" />

  </AdminUISequence>

</UI>

To immitate the user exit requestI added custom action which returns a UserExit code. To test it you need to pass the TESTUSEREXIT property in the command line:

Msiexec /I UIRequiredDialogs.msi TESTUSEREXIT=1

Error dialog

The Error dialog displays the error message.  To display an Error dialog we must create a dialog and set the ErrorDialog property to the Id of the Error dialog.

The following are requirements for the Error dialog:

  • Dialog must have attribute ErrorDialog set to "yes".
  • First control in the dialog must be control with Id="ErrorText".
  • Dialog must contain seven buttons.  Each button must publish EndDialog event.  The only two differences between buttons are the value of Id attribute of the Control element and the value of the Value attribute (event argument) of Publish element:
    • Abort button: publishes an event with argument ErrorAbort and must have Control/@Id="A".
    • Cancel button: publishes an event with argument ErrorCancel and must have Control/@Id="C".
    • Ignore button: publishes an event with argument ErrorIgnore and must have Control/@Id="I".
    • No button: publishes an event with argument ErrorNo and must have Control/@Id="N".
    • OK button: publishes an event with argument ErrorOK and must have Control/@Id="O".
    • Retry button: publishes an event with argument ErrorRetry and must have Control/@Id="R".
    • Yes button: publishes an event with argument ErrorYes and must have Control/@Id="Y".
  • All buttons must have attribute TabSkip set to "yes".
  • All buttons must have the same position (same value of the X and Y attributes).
  • In Error dialog the values of Cancel and Default attributes of button Control elements are ignored, so there is no need to give them any value.
  • Error dialog can also have an Icon control.  If included, this control must have an Id attribute set to "ErrorIcon" and attribute FixedSize set to "yes".  Setting Text attribute is not necessary, but not setting it will generate an ICE17.  As a workaround, add an icon to Binary table and set Text attribute to that icon.

Here is the complete source code which includes all four required dialogs.  Compilation of this code still produces ICE20 about missing FilesInUse dialog, but for now we can safely ignore this error.

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

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

  <Product Id="732e6d89-5fa5-43a1-b590-88ebed02a05f" Name="UIRequiredDialogs" Language="1033" Version="1.0.0.0" Manufacturer="UIRequiredDialogs" UpgradeCode="8f2b749f-f563-4a4f-ab66-5a121bf07378">

    <Package InstallerVersion="200" Compressed="yes" />

 

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

 

    <Binary Id="ErrorIcon" SourceFile="ErrorIcon.ico" />

 

    <!-- Fatal Error -->

    <Property Id="FailureProgram">

    <![CDATA[

        Function Main()

          Main = 3

        End Function

        ]]>

    </Property>

 

    <CustomAction Id="FakeFailure"

            VBScriptCall="Main"

            Property="FailureProgram" />

 

    <InstallExecuteSequence>

      <Custom Action="FakeFailure"

          Before="InstallInitialize">TESTFAIL</Custom>

    </InstallExecuteSequence>

 

    <!-- User Exit -->

    <Property Id="UserExitProgram">

      <![CDATA[

        Function Main()

          Main = 2

        End Function

        ]]>

    </Property>

 

    <CustomAction Id="FakeUserExit"

            VBScriptCall="Main"

            Property="UserExitProgram" />

 

    <InstallExecuteSequence>

      <Custom Action="FakeUserExit"

          Before="InstallInitialize">TESTUSEREXIT</Custom>

    </InstallExecuteSequence>

 

    <!-- Error dialog -->

    <CustomAction Id="TestError" Error="25000" />

 

    <InstallExecuteSequence>

      <Custom Action="TestError" Before="InstallInitialize">TESTERROR</Custom>

    </InstallExecuteSequence>

 

    <UI>

      <Dialog Id="ExitDialog"

          Width="370" Height="270"

          Title="Exit Dialog">

        <Control Id="Exit" Type="PushButton"

             X="236" Y="243" Width="56" Height="17"

             Default="yes" Cancel="yes"

             Text="Exit">

          <Publish Event="EndDialog" Value="Return">1</Publish>

        </Control>

      </Dialog>

 

      <Dialog Id="FatalError"

          Width="370" Height="270"

          Title="Fatal Error Dialog">

        <Control Id="Finish" Type="PushButton"

             X="236" Y="243" Width="56" Height="17"

             Default="yes" Cancel="yes"

             Text="Exit">

          <Publish Event="EndDialog" Value="Exit">1</Publish>

        </Control>

      </Dialog>

     

      <Dialog Id="UserExit"

          Width="370" Height="270"

          Title="User Exit dialog">

        <Control Id="Finish" Type="PushButton"

             X="236" Y="243" Width="56" Height="17"

             Default="yes" Cancel="yes"

             Text="Exit">

          <Publish Event="EndDialog" Value="Exit">1</Publish>

        </Control>

      </Dialog>

 

      <Property Id="ErrorDialog" Value="ErrorDlg" />

      <Error Id="25000">Error message goes here!</Error>

 

      <Dialog Id="ErrorDlg" Width="270" Height="105" Title="My Error Dialog" ErrorDialog="yes">

        <Control Id="ErrorText" Type="Text" X="48" Y="15" Width="205" Height="60" TabSkip="no" Text="Error text" />

        <Control Id="Y" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Yes">

          <Publish Event="EndDialog" Value="ErrorYes">1</Publish>

        </Control>

        <Control Id="A" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Abort">

          <Publish Event="EndDialog" Value="ErrorAbort">1</Publish>

        </Control>

        <Control Id="C" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Cancel">

          <Publish Event="EndDialog" Value="ErrorCancel">1</Publish>

        </Control>

        <Control Id="I" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Ignore">

          <Publish Event="EndDialog" Value="ErrorIgnore">1</Publish>

        </Control>

        <Control Id="N" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="No">

          <Publish Event="EndDialog" Value="ErrorNo">1</Publish>

        </Control>

        <Control Id="O" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="OK">

          <Publish Event="EndDialog" Value="ErrorOk">1</Publish>

        </Control>

        <Control Id="R" Type="PushButton" X="100" Y="80" Width="56" Height="17" TabSkip="yes" Text="Retry">

          <Publish Event="EndDialog" Value="ErrorRetry">1</Publish>

        </Control>

        <Control Id="ErrorIcon" Type="Icon" X="15" Y="15" Width="24" Height="24" ToolTip="Error tooltip" FixedSize="yes" Text="ErrorIcon" />

      </Dialog>

     

      <TextStyle Id="DefaultFont" FaceName="Tahoma" Size="8" />

      <Property Id="DefaultUIFont" Value="DefaultFont" />

 

      <InstallUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

        <Show Dialog="FatalError" OnExit="error" />

        <Show Dialog="UserExit" OnExit="cancel" />

      </InstallUISequence>

 

      <AdminUISequence>

        <Show Dialog="ExitDialog" OnExit="success" />

        <Show Dialog="FatalError" OnExit="error" />

        <Show Dialog=