Part 3 of Writing Your Own .Net-based Installer with WiX - Context Data

This article is one in my series focusing on what I learned creating a Windows Installer using the WiX toolkit’s "Managed Bootstrapper Application" (Custom .NET UI) framework. Each post builds upon the previous posts, so I would suggest you at least skim through the earlier articles in the series.

Here’s an index into the articles in this series:

Getting access to installer (and package) metadata

A lot of information is embedded in the WiX xml files, such as package/feature layout, names, descriptions, ids, etc, which we use to build out our bundle models, but almost none of it is made available at runtime via the event args. However, WiX does generate a BootstrapperApplicationData.xml file which includes a lot of that information and is included in the files available at runtime. We can parse that file at runtime in order to access that metadata, which I suggest you do before you run the detection logic (see below) in order to have a populated model to use in the event handlers. Since the file, along with all of our assemblies and .msi files, are placed in a randomly-name temp folder, we can’t know ahead of time where the file will live, so we must use our assembly’s path to find it.

You can then parse the XML to get the metadata. I would suggest running a makeshift installer in debug mode and setting a breakpoint here to inspect the contents of the XML in order to get a full list of what’s available. Here’s an example of how I get data from the file. Note: in this example, my domain objects are MBAPrereqPackage, BundlePackage and PackageFeature, each of which take an XML node object in their constructor and further parse the data into the object’s properties.

const  XNamespace ManifestNamespace = ( XNamespace) “http://schemas.microsoft.com/wix/2010/BootstrapperApplicationData” ;

public void Initialize()
{

    //
    // parse the ApplicationData to find included packages and features
    //
    var bundleManifestData = this.ApplicationData;
    var bundleDisplayName = bundleManifestData 
                              .Element(ManifestNamespace + “WixBundleProperties“ )
                              .Attribute( “DisplayName“)
                              .Value;

    var mbaPrereqs = bundleManifestData.Descendants(ManifestNamespace + “WixMbaPrereqInformation“)
                                       .Select(x => new MBAPrereqPackage(x))
                                       .ToList();

    //
    //exclude the MBA prereq packages, such as the .Net 4 installer
    //
    var pkgs = bundleManifestData.Descendants(ManifestNamespace + “WixPackageProperties“)
                                 .Select(x => new BundlePackage(x))
                                 .Where(pkg => !mbaPrereqs.Any(preReq => preReq.PackageId == pkg.Id));

    //
    // Add the packages to a collection of BundlePackages
    //
    BundlePackages.AddRange(pkgs);

    //
    // check for features and associate them with their parent packages
    //
    var featureNodes = bundleManifestData.Descendants(ManifestNamespace + “WixPackageFeatureInfo“);
    foreach ( var featureNode in featureNodes)
    {
       var feature = new PackageFeature(featureNode);
       var parentPkg = BundlePackages.First(pkg => pkg.Id == feature.PackageId);
       parentPkg.AllFeatures.Add(feature);
       feature.Package = parentPkg;
    }
}

/// 
/// Fetch BootstrapperApplicationData.xml and parse into XDocument.
/// 
public XElement ApplicationData
{
    get
    {
        var workingFolder = Path.GetDirectoryName(this.GetType().Assembly.Location);
        var bootstrapperDataFilePath = Path.Combine(workingFolder, “BootstrapperApplicationData.xml”);

        using (var reader = new StreamReader(bootstrapperDataFilePath))
        {
            var xml = reader.ReadToEnd();
            var xDoc = XDocument.Parse(xml);
            return xDoc.Element(ManifestNamespace + “BootstrapperApplicationData“);                   
        }
    }
}

Access to command line parameters (Install/Upgrade/Modify/Uninstall, Silent mode, etc)

Along with the Engine property provided in the base class, a Command property is also exposed. There are a few properties off that Command object that are very useful:

The Action property, which exposes a LaunchAction enum value, tells you how the installer was initiated. If the user just double-clicked on the executable, it will come in as Install, but if command-line parameters are used to execute a specific action, that will be translated into this enum. This includes clicking “Uninstall” from the Add/Remove programs list, etc.

/// 
/// Requested action from the commandline
/// 
public LaunchAction RunMode { get { return Command.Action; } }

public enum LaunchAction
{
  Unknown,
  Help,
  Layout,
  Uninstall,
  Install,
  Modify,
  Repair,
}

The Display property, which exposes a Display enum value, tells you if the user wants silent mode, etc. These map to the Windows Installer commandline allowed values.

/// 
/// Requested display mode from the commandline
/// (Full, Passive/Silent, Embedded)
/// 
public Display DisplayMode { get { return Command.Display; } }

public enum Display
{
  Unknown,
  Embedded,
  None,
  Passive,
  Full,
}

And then the CommandLine property exposes the rest of the command line. Note that WiX will actually remove several of the parameters that are exposed via other properties (Display, Action, etc)

/// 
/// Full application command line
/// 
public IEnumerable<string> CommandLine { get { return (Command.CommandLine ?? string.Empty).Split(‘ ‘ ); } }