Sometimes great is the enemy of good. I recently had to harvest a large number of files to create a wix installer. I tried heat.exe, but after struggling with its generation style and even considering writing an XSLT, I decided it would be faster to just code what I want in C#. This generates a .wxi file that follows somewhat of a standard of naming components .C, directories .D and skipping .svn folders.
It just gets the job done.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
namespace WixHarvest
{
class Program
{
static string MakeId(string id)
{
return id.Replace("_", ".").Replace("-", ".");
}
const String wixns = "https://schemas.microsoft.com/wix/2006/wi";
static List<String> GenerateFragment(XmlDocument doc, XmlNode parent, DirectoryInfo dir, DirectoryInfo root)
{
List<String> componentIds = new List<string>();
XmlNode directory = (doc.FirstChild == parent)
? parent.AppendChild(doc.CreateElement("DirectoryRef", wixns))
: parent.AppendChild(doc.CreateElement("Directory", wixns));
string id = "";
if (parent.Attributes["Id"] != null) id = parent.Attributes["Id"].Value.Replace(".D", "") + ".";
id += dir.Name + ".D";
directory.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(id);
if (doc.FirstChild != parent)
{
directory.Attributes.Append(doc.CreateAttribute("Name")).Value = dir.Name;
}
FileInfo[] files = dir.GetFiles();
List<DirectoryInfo> subdirs = new List<DirectoryInfo>(dir.GetDirectories());
for (int i = subdirs.Count - 1; i >= 0; i--)
{
if (subdirs.Name == ".svn")
{
subdirs.RemoveAt(i);
}
}
if (files.Length == 0 && subdirs.Count == 0)
{
XmlNode component = directory.AppendChild(doc.CreateElement("Component", wixns));
string componentId = id.Replace(".D", ".") + "C";
componentIds.Add(componentId);
component.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(componentId);
component.Attributes.Append(doc.CreateAttribute("Guid")).Value = Guid.NewGuid().ToString();
component.AppendChild(doc.CreateElement("CreateFolder", wixns));
}
else if (files.Length > 0)
{
XmlNode component = directory.AppendChild(doc.CreateElement("Component", wixns));
string componentId = id.Replace(".D", ".") + "C";
componentIds.Add(componentId);
component.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(componentId);
component.Attributes.Append(doc.CreateAttribute("Guid")).Value = Guid.NewGuid().ToString();
foreach (FileInfo file in files)
{
XmlNode node = component.AppendChild(doc.CreateElement("File", wixns));
node.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(id.Replace(".D", ".") + file.Name);
node.Attributes.Append(doc.CreateAttribute("Source")).Value =
"$(var." + Path.GetFileName(root.FullName) + "Dir)\\" +
file.FullName.Substring(root.FullName.Length).TrimStart('\\');
}
}
foreach (DirectoryInfo subdir in subdirs)
{
componentIds.AddRange(GenerateFragment(doc, directory, subdir, root));
}
return componentIds;
}
static void Main(string[] args)
{
try
{
if (args.Length == 0)
{
throw new ArgumentException("Missing dir");
}
XmlDocument doc = new XmlDocument();
doc.LoadXml("<Include xmlns=\"https://schemas.microsoft.com/wix/2006/wi\" />");
DirectoryInfo root = new DirectoryInfo(args[0]);
List<String> componentIds = GenerateFragment(doc, doc.FirstChild, root, root);
XmlNode featureRef = doc.FirstChild.AppendChild(doc.CreateElement("Feature", wixns));
featureRef.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(root.Name);
featureRef.Attributes.Append(doc.CreateAttribute("Level")).Value = "1";
featureRef.Attributes.Append(doc.CreateAttribute("Title")).Value = root.Name;
foreach (String componentId in componentIds)
{
XmlNode node = featureRef.AppendChild(doc.CreateElement("ComponentRef", wixns));
node.Attributes.Append(doc.CreateAttribute("Id")).Value = MakeId(componentId);
}
Console.WriteLine(doc.OuterXml);
}
catch (Exception ex)
{
Console.WriteLine("ERROR: {0}", ex.Message);
}
}
}
}