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="https://schemas.microsoft.com/wix/2006/wi"

     xmlns:util = "https://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="https://schemas.microsoft.com/wix/2006/wi"

     xmlns:util="https://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.

XmlFileUpdate.zip