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 | 1 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 | 0 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=