Wix Custom Actions - Reading Parameters from an XML file

This isn't a perfect solution but I've spent two days getting it to work and wanted to share. No doubt there will be some errors, but I have done as good as I can in the time available:

  1. Add a New Project and select a Windows Installer Xml Setup Project
  2. Add a New Project and select a Windows Installer Xml C# Custom Actions Project
  3. In your setup project:

    • Add something to be installed e.g. files \ web site etc. (See other tutorials on how to do this)
    • Set some properties in your Product.wxs e.g.

      <Property Id="MyProperty1" />
      <Property Id="MyProperty2" />
      
    • Reference your newly created Custom Actions (below) in your Product.wxs:

      <Product> .....
          <Binary Id='VantageInstallerCustomActions.CA.dll' src='..\VantageInstallerCustomActions\bin\$(var.Configuration)\VantageInstallerCustomActions.CA.dll' />
          <InstallExecuteSequence>
              <Custom Action="SetInstallerProperties" Before="CostFinalize"  />
          </InstallExecuteSequence>
      </Product>
      
      <Fragment>
          <CustomAction Id='SetInstallerProperties' BinaryKey='VantageInstallerCustomActions.CA.dll' DllEntry='SetInstallerProperties' Return='check' Execute='immediate' />
      </Fragment>
      
  4. Add the following code into your Custom Actions Project or something similar:

Add a CustomAction class:

    public class CustomActions
    {
     private static readonly InstallerPropertiesFileManager InstallerPropertiesFileManager = new InstallerPropertiesFileManager();

    [CustomAction]
    public static ActionResult SetInstallerProperties(Session session)
    {
        session.Log("Begin SetInstallerProperties");

        try
        {

            var doc = XDocument.Load(@"C:\temp\Parameters.xml");

            session.Log("Parameters Loaded:" + (doc.Root != null));
            session.Log("Parameter Count:" + doc.Descendants("Parameter").Count());
            var parameters = doc.Descendants("Parameter").ToDictionary(n => n.Attribute("Name").Value, v => v.Attribute("Value").Value);

            if (parameters.Any())
            {
                session.Log("Parameters loaded into Dictionary Count: " + parameters.Count());

                //Set the Wix Properties in the Session object from the XML file
                foreach (var parameter in parameters)
                {
                    session[parameter.Key] = parameter.Value;
                }
            }                
            else
            {
                session.Log("No Parameters loaded");
            }
        }
        catch (Exception ex)
        {
            session.Log("ERROR in custom action SetInstallerProperties {0}", ex.ToString());
            return ActionResult.Failure;
        }
        session.Log("End SetInstallerProperties");
        return ActionResult.Success;
    }
    }

Create your C:\temp\Parameters.xml file to store on disk

    <?xml version="1.0" encoding="utf-8"?>
    <Parameters>
        <Environment ComputerName="Mycomputer" Description="Installation Parameters for Mycomputer" />
        <Category Name="WebServices">
            <Parameter Name="MyProperty1" Value="http://myserver/webservice" />
            <Parameter Name="MyProperty2" Value="myconfigSetting" />
        </Category>
    </Parameters>

N.B. you do not need to reference the Custom Actions Project from the Setup Project. You also shouldn't set properties too late in the installation cycle that are required early on for example those that are File Paths to install files. I tend to avoid these.

Use your properties in your Product.wxs to do something! e.g. I am using the retrieved property to update a web service end point in the installed web.config

<Fragment>
    <DirectoryRef Id ="INSTALLFOLDER">
      <Component Id="WebConfig" Guid="36768416-7661-4805-8D8D-E7329F4F3AB7">
        <CreateFolder />
        <util:XmlFile Id="WebServiceEnpointUrl" Action="setValue" ElementPath="//configuration/system.serviceModel/client/endpoint[\[]@contract='UserService.V1_0.GetUser.ClientProxy.Raw.IGetUserService'[\]]/@address" Value="[MyProperty1]" File="[INSTALLFOLDER]web.config" SelectionLanguage="XPath" />
      </Component>
    </DirectoryRef>
  </Fragment>

As always with Wix installers, nothing works first time. Re-Build your Wix SetupProject and run the msi locally with the following command line to turn logging on:

msiexec /i "myInstaller.msi" /l*v "log.log"

Once run, open up the log file and you should see the following events:

MSI (s) (C4:3C) [11:00:11:655]: Doing action: SetInstallerProperties
Action start 11:00:11: SetInstallerProperties.
MSI (s) (C4:A8) [11:00:11:702]: Invoking remote custom action. DLL: C:\WINDOWS\Installer\MSICD83.tmp, Entrypoint: SetInstallerProperties
MSI (s) (C4:A8) [11:00:11:702]: Generating random cookie.
MSI (s) (C4:A8) [11:00:11:702]: Created Custom Action Server with PID 496 (0x1F0).
MSI (s) (C4:CC) [11:00:11:733]: Running as a service.
MSI (s) (C4:CC) [11:00:11:733]: Hello, I'm your 32bit Impersonated custom action server.
SFXCA: Extracting custom action to temporary directory: C:\Users\ak9763\AppData\Local\Temp\MSICD83.tmp-\
SFXCA: Binding to CLR version v4.0.30319
Calling custom action VantageInstallerCustomActions!VantageInstallerCustomActions.CustomActions.SetInstallerProperties
Begin SetInstallerProperties
Parameters loaded into Dictionary: 2
MSI (s) (C4!C0) [11:00:11:858]: PROPERTY CHANGE: Adding MyProperty1 property. Its value is 'http://myserver/webservice'.
MSI (s) (C4!C0) [11:00:11:858]: PROPERTY CHANGE: Adding MyProperty2 property. Its value is 'myConfigSetting'.
End SetInstallerProperties
Action ended 11:00:11: SetInstallerProperties. Return value 1.

References for this post:

Creating WiX Custom Actions in C# and Passing Parameters

From MSI to WiX, Part 5 - Custom actions: Introduction

Create an MSI log file


One solution is to use the "Community MSI Extensions"

The custom action you're after is probably Xml_SelectNodeValue (there's an example on how to use it).

Tags:

C#

Wix