Is there a tool to generate WiX XML given a .reg file?
In 2.0, you were supposed to be able to run tallow to generate registry XML:
tallow -r my.reg
For what it's worth, the version of tallow I have is producing empty XML.
In 3.0, tallow has been replaced with heat, but I can't figure out how to get it to produce output from a .reg file.
Is there a way to do this in 3.0?
I couldn't find a tool, so I made one.
The source code may not be elegant, but it seems to work:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Text.RegularExpressions;
namespace Reg2Wix
{
class Program
{
static void PrintUsage()
{
Console.WriteLine("reg2wix <input file> <output file>");
}
/// <summary>
/// Parse the hive out of a registry key
/// </summary>
/// <param name="keyWithHive"></param>
/// <param name="hive"></param>
/// <param name="key"></param>
static void ParseKey(string keyWithHive, out string hive, out string key)
{
if (keyWithHive == null)
{
throw new ArgumentNullException("keyWithHive");
}
if (keyWithHive.StartsWith("HKEY_LOCAL_MACHINE\\"))
{
hive = "HKLM";
key = keyWithHive.Substring(19);
}
else if (keyWithHive.StartsWith("HKEY_CLASSES_ROOT\\"))
{
hive = "HKCR";
key = keyWithHive.Substring(18);
}
else if (keyWithHive.StartsWith("HKEY_USERS\\"))
{
hive = "HKU";
key = keyWithHive.Substring(11);
}
else if (keyWithHive.StartsWith("HKEY_CURRENT_USER\\"))
{
hive = "HKCU";
key = keyWithHive.Substring(18);
}
else
{
throw new ArgumentException();
}
}
/// <summary>
/// Write a WiX RegistryValue element for the specified key, name, and value
/// </summary>
/// <param name="writer"></param>
/// <param name="key"></param>
/// <param name="name"></param>
/// <param name="value"></param>
static void WriteRegistryValue(XmlWriter writer, string key, string name, string value)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (key == null)
{
throw new ArgumentNullException("key");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
string hive;
string keyPart;
ParseKey(key, out hive, out keyPart);
writer.WriteStartElement("RegistryValue");
writer.WriteAttributeString("Root", hive);
writer.WriteAttributeString("Key", keyPart);
if (!String.IsNullOrEmpty(name))
{
writer.WriteAttributeString("Name", name);
}
writer.WriteAttributeString("Value", value);
writer.WriteAttributeString("Type", "string");
writer.WriteAttributeString("Action", "write");
writer.WriteEndElement();
}
/// <summary>
/// Convert a .reg file into an XML document
/// </summary>
/// <param name="inputReader"></param>
/// <param name="xml"></param>
static void RegistryFileToWix(TextReader inputReader, XmlWriter xml)
{
Regex regexKey = new Regex("^\\[([^\\]]+)\\]$");
Regex regexValue = new Regex("^\"([^\"]+)\"=\"([^\"]*)\"$");
Regex regexDefaultValue = new Regex("@=\"([^\"]+)\"$");
string currentKey = null;
string line;
while ((line = inputReader.ReadLine()) != null)
{
line = line.Trim();
Match match = regexKey.Match(line);
if (match.Success)
{
//key track of the current key
currentKey = match.Groups[1].Value;
}
else
{
//if we have a current key
if (currentKey != null)
{
//see if this is an acceptable name=value pair
match = regexValue.Match(line);
if (match.Success)
{
WriteRegistryValue(xml, currentKey, match.Groups[1].Value, match.Groups[2].Value);
}
else
{
//see if this is an acceptable default value (starts with @)
match = regexDefaultValue.Match(line);
if (match.Success)
{
WriteRegistryValue(xml, currentKey, (string)null, match.Groups[1].Value);
}
}
}
}
}
}
/// <summary>
/// Convert a .reg file into a .wsx file
/// </summary>
/// <param name="inputPath"></param>
/// <param name="outputPath"></param>
static void RegistryFileToWix(string inputPath, string outputPath)
{
using (StreamReader reader = new StreamReader(inputPath))
{
using (XmlTextWriter writer = new XmlTextWriter(outputPath, Encoding.UTF8))
{
writer.Formatting = Formatting.Indented;
writer.Indentation = 3;
writer.IndentChar = ' ';
writer.WriteStartDocument();
writer.WriteStartElement("Component");
RegistryFileToWix(reader, writer);
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}
static void Main(string[] args)
{
if (args.Length != 2)
{
PrintUsage();
return;
}
RegistryFileToWix(args[0], args[1]);
}
}
}
Here is the source code of the utility that generates Wix 3 markup (including binary, dword and multi-string registry values):
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
namespace AbsReg2Wix
{
public class Program
{
#region Constants
private const string NS_URI = "http://schemas.microsoft.com/wix/2006/wi";
private const string RegEditorVersionPattern = @"Windows\sRegistry\sEditor\sVersion\s(?<RegEditorVersion>.*)";
private const string RegKeyPattern = @"\[(?<RegistryHive>[^\\]*)\\(?<RegistryKey>.*)\]";
private const string RegNameValuePattern = "\\\"(?<Name>.*)\\\"=(?<Value>\\\"?[^\\\\\\\"]*)(?<MultiLine>\\\\?)";
private const RegexOptions DefaultRegexOptions = RegexOptions.Multiline |
RegexOptions.IgnorePatternWhitespace |
RegexOptions.CultureInvariant;
#endregion
#region Methods
/// <summary>
/// Main applciation entry point
/// </summary>
/// <param name="args">The args.</param>
private static void Main(string[] args)
{
if (args.Length != 4)
{
PrintUsageInstructions();
return;
}
if (File.Exists(args[1]))
{
ConvertRegistryFileToWix(args[1], args[3]);
Console.WriteLine("Successfully completed conversion.");
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
else
{
Console.WriteLine(@"Input file {0} not found.", args[1]);
}
}
/// <summary>
/// Prints the usage instructions.
/// </summary>
private static void PrintUsageInstructions()
{
Console.WriteLine("Syntax: AbsReg2Wix.exe /in <Input File (.reg)> /out <Output File>");
}
/// <summary>
/// Convert a .reg file into a .wsx file
/// </summary>
/// <param name="inputPath">The input path.</param>
/// <param name="outputPath">The output path.</param>
private static void ConvertRegistryFileToWix(string inputPath, string outputPath)
{
try
{
using (var reader = new StreamReader(inputPath))
{
string regEditorVersion = string.Empty;
bool isRegEditorVersionFound = false;
// Initialize Regex
var regEditorVersionRegex = new Regex(RegEditorVersionPattern, DefaultRegexOptions);
var regKeyRegex = new Regex(RegKeyPattern, DefaultRegexOptions);
var regNameValueRegex = new Regex(RegNameValuePattern, DefaultRegexOptions);
// Create xml document for output
var xDoc = new XmlDocument();
xDoc.AppendChild(xDoc.CreateProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\""));
xDoc.AppendChild(xDoc.CreateComment(
string.Format(
"{0}Following code was generated by AbsReg2Wix tool.{0}Tool Version: {1}{0}Date: {2}{0}Command Line: {3}\n",
"\n\t", Assembly.GetExecutingAssembly().GetName().Version,
DateTime.Now.ToString("F"),
Environment.CommandLine)));
XmlElement includeElement = xDoc.CreateElement("Include", NS_URI);
XmlElement componentElement = null,
regKeyElement = null,
registryValueElement = null;
bool multiLine = false;
var rawValueBuilder = new StringBuilder();
while (!reader.EndOfStream)
{
string regFileLine = reader.ReadLine().Trim();
if (!isRegEditorVersionFound)
{
var regEditorVersionMatch = regEditorVersionRegex.Match(regFileLine);
if (regEditorVersionMatch.Success)
{
regEditorVersion = regEditorVersionMatch.Groups["RegEditorVersion"].Value;
includeElement.AppendChild(
xDoc.CreateComment("Registry Editor Version: " + regEditorVersion));
isRegEditorVersionFound = true;
}
}
var regKeyMatch = regKeyRegex.Match(regFileLine);
// Registry Key line found
if (regKeyMatch.Success)
{
if (componentElement != null)
{
componentElement.AppendChild(regKeyElement);
includeElement.AppendChild(componentElement);
}
componentElement = xDoc.CreateElement("Component", NS_URI);
var idAttr = xDoc.CreateAttribute("Id");
idAttr.Value = "Comp_" + GetMD5HashForString(regFileLine);
componentElement.Attributes.Append(idAttr);
var guidAttr = xDoc.CreateAttribute("Guid");
guidAttr.Value = Guid.NewGuid().ToString();
componentElement.Attributes.Append(guidAttr);
regKeyElement = xDoc.CreateElement("RegistryKey", NS_URI);
var hiveAttr = xDoc.CreateAttribute("Root");
hiveAttr.Value = GetShortHiveName(regKeyMatch.Groups["RegistryHive"].Value);
regKeyElement.Attributes.Append(hiveAttr);
var keyAttr = xDoc.CreateAttribute("Key");
keyAttr.Value = regKeyMatch.Groups["RegistryKey"].Value;
regKeyElement.Attributes.Append(keyAttr);
var actionAttr = xDoc.CreateAttribute("Action");
actionAttr.Value = "createAndRemoveOnUninstall";
regKeyElement.Attributes.Append(actionAttr);
}
var regNameValueMatch = regNameValueRegex.Match(regFileLine);
// Registry Name/Value pair line found
if (regNameValueMatch.Success)
{
registryValueElement = xDoc.CreateElement("RegistryValue", NS_URI);
var nameAttr = xDoc.CreateAttribute("Name");
nameAttr.Value = regNameValueMatch.Groups["Name"].Value;
registryValueElement.Attributes.Append(nameAttr);
var actionAttr = xDoc.CreateAttribute("Action");
actionAttr.Value = "write";
registryValueElement.Attributes.Append(actionAttr);
if (string.IsNullOrEmpty(regNameValueMatch.Groups["MultiLine"].Value))
{
string valueType, actualValue;
ParseRegistryValue(regNameValueMatch.Groups["Value"].Value, out valueType,
out actualValue);
var typeAttr = xDoc.CreateAttribute("Type");
typeAttr.Value = valueType;
registryValueElement.Attributes.Append(typeAttr);
var valueAttr = xDoc.CreateAttribute("Value");
valueAttr.Value = actualValue;
registryValueElement.Attributes.Append(valueAttr);
regKeyElement.AppendChild(registryValueElement);
}
else
{
multiLine = true;
rawValueBuilder.Append(regNameValueMatch.Groups["Value"].Value
.Replace("\\", string.Empty));
}
}
else if (multiLine)
{
if (regFileLine.IndexOf("\\") != -1)
{
rawValueBuilder.Append(regFileLine.Replace("\\", string.Empty));
}
else
{
rawValueBuilder.Append(regFileLine);
string valueType, actualValue;
ParseRegistryValue(rawValueBuilder.ToString(), out valueType, out actualValue);
var typeAttr = xDoc.CreateAttribute("Type");
typeAttr.Value = valueType;
registryValueElement.Attributes.Append(typeAttr);
var valueAttr = xDoc.CreateAttribute("Value");
valueAttr.Value = actualValue;
registryValueElement.Attributes.Append(valueAttr);
regKeyElement.AppendChild(registryValueElement);
rawValueBuilder.Remove(0, rawValueBuilder.Length);
multiLine = false;
}
}
}
if (componentElement != null)
{
componentElement.AppendChild(regKeyElement);
includeElement.AppendChild(componentElement);
}
xDoc.AppendChild(includeElement);
xDoc.Save(outputPath);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// Parses the registry value.
/// </summary>
/// <param name="rawValue">The raw value.</param>
/// <param name="valueType">Type of the value.</param>
/// <param name="actualValue">The actual value.</param>
private static void ParseRegistryValue(string rawValue, out string valueType, out string actualValue)
{
if (rawValue.IndexOf("\"") != -1)
{
valueType = "string";
actualValue = rawValue.Substring(1, rawValue.Length - 2);
}
else if (rawValue.IndexOf("dword:") != -1)
{
valueType = "integer";
actualValue = rawValue.Replace("dword:", string.Empty);
}
else if (rawValue.IndexOf("hex:") != -1)
{
valueType = "binary";
actualValue = rawValue.Replace("hex:", string.Empty)
.Replace(",", string.Empty)
.ToUpper();
}
else if (rawValue.IndexOf("hex(7):") != -1)
{
valueType = "multiString";
string[] hexStrings = rawValue.Replace("hex(7):", string.Empty).Split(',');
var bytes = new byte[hexStrings.Length];
for (int i = 0; i < hexStrings.Length; i++)
{
bytes[i] = byte.Parse(hexStrings[i], NumberStyles.HexNumber);
}
actualValue = Encoding.Unicode.GetString(bytes).Replace("\0", "[~]");
}
else
{
valueType = "string";
actualValue = rawValue;
}
}
/// <summary>
/// Gets the short name of the registry hive.
/// </summary>
/// <param name="fullHiveName">Full name of the hive.</param>
/// <returns></returns>
private static string GetShortHiveName(string fullHiveName)
{
switch (fullHiveName)
{
case "HKEY_LOCAL_MACHINE":
return "HKLM";
case "HKEY_CLASSES_ROOT":
return "HKCR";
case "HKEY_USERS":
return "HKU";
case "HKEY_CURRENT_USER":
return "HKCU";
default:
throw new ArgumentException(string.Format("Registry Hive unsupported by Wix: {0}.",
fullHiveName));
}
}
/// <summary>
/// Gets the MD5 hash for string.
/// </summary>
/// <param name="inputString">The input string.</param>
/// <returns></returns>
private static string GetMD5HashForString(string inputString)
{
MD5 hashAlg = MD5.Create();
byte[] originalInBytes = Encoding.ASCII.GetBytes(inputString);
byte[] hashedOriginal = hashAlg.ComputeHash(originalInBytes);
String outputString = Convert.ToBase64String(hashedOriginal)
.Replace("/", "aa")
.Replace("+", "bb")
.Replace("=", "cc");
return outputString;
}
#endregion
}
}
Now it's built into Wix : Heat.exe
- Harvesting tool.
https://stackoverflow.com/a/11988983/483588
I've tried tallow.exe (version 2.0.5805) from the latest stable Wix 2 release and it worked fine for me.
tallow -reg my.reg
This will generate the markup using Wix 2 "Registry" tag that was deprecated in Wix 3. Then you have to copy the output into a wix source file and execute WixCop utility to convert Wix 2 markup to Wix 3:
wixcop my.wxs -f
This code works well, but if you have a an empty string value in the registry file you are importing, an exception error gets thrown. You may want to update the ParseRegistryValue section accordingly.
if (rawValue.IndexOf("\"") != -1)
{
valueType = "string";
if (rawValue.Length > 1)
{
actualValue = rawValue.Substring(1, rawValue.Length - 2);
}
else
{
actualValue = "";
}
}
From Wix 4.0
C:\Program Files (x86)\WiX Toolset v4.0\bin>heat /? | find /i "reg"
reg harvest a .reg file
-sreg suppress registry harvesting
To use, where regfile.reg is the registry input file and fragment.xml is the output file to generate.
heat reg regfile.reg -o fragment.xml