I was ranting recently about the WiX toolset. Perfectly normal thing to do, of course, especially when you’re talking about creating installers. Today however when I returned to that task to do some more research I learned something about how it is designed which is so nice it’s overwhelmed the complaints I had earlier.

The situation is one where I’m using heat to generate file components for all of the assets that happen to be in a directory. The problem is no combination of switches would produce a wxs file that had an inventory I could pull in conveniently.

Then I saw an article describing the heat extensions architecture, specifically for mutators. This has cleared up all of the significant problems I was having pulling together boilerplate unattended msi generation.

Here’s the extension class:

public class ComponentGroupExtension : HeatExtension
{
    private const string Switch = "-compgroup:";

    public ComponentGroupExtension()
    {
        Console.WriteLine("ComponentGroupExtension created");
    }

    public override HeatCommandLineOption[] CommandLineTypes
    {
        get
        {
            return new[]
                   {
                       new HeatCommandLineOption(
                           Switch + "<id>",
                           "Generate a component group")
                   };
        }
    }

    public override void ParseOptions(string type, string[] args)
    {
        for (int i = 0; i < args.Length; i++)
        {
            if (args[i].StartsWith(Switch))
            {
                var mutator = new ComponentGroupMutator(
                    args[i].Substring(Switch.Length));

                Core.Mutator.AddExtension(mutator);
            }
        }

    }
}

All it does is recognize a switch and add a mutator with the switches value. Like -compgroup:WebSiteFiles.

Here’s the mutator:

internal class ComponentGroupMutator : MutatorExtension
{
    private readonly string _componentGroupId;
    private readonly IList<Component> _components = new List<Component>();

    public ComponentGroupMutator(string componentGroupId)
    {
        _componentGroupId = componentGroupId;
    }

    public override int Sequence
    {
        get { return 3000; }
    }

    public override void Mutate(Wix.Wix wix)
    {
        IndexElement(wix);

        var componentGroup = new ComponentGroup {Id = _componentGroupId};

        foreach (var component in _components)
        {
            if (null == component.Id) continue;

            var componentRef = new ComponentRef {Id = component.Id};
            componentGroup.AddChild(componentRef);
        }

        var fragment = new Fragment();
        fragment.AddChild(componentGroup);
        wix.AddChild(fragment);
    }

    private void IndexElement(ISchemaElement element)
    {
        if (element is Component)
        {
            _components.Add((Component)element);
        }

        if (element is IParentElement)
        {
            var children = ((IParentElement) element).Children;
            foreach (ISchemaElement childElement in children)
            {
                IndexElement(childElement);
            }
        }
    }
}

That’s all there is to it! Now I can use heat to generate all of the file and component fragments and give it a deterministic component group id, which can be pulled in by name from another much smaller wxs file that has the product metadata, target folder, iis settings, etc.

Documentation is very thin, especially for WiX 3.0 specific things, and it was only looking at the the source code for the existing heat extensions that made it possible to write this. But with very little code you can make an assembly that gives you complete control over the shaping of your harvested assets.