// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Harvesters
{
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using WixToolset.Data;
using WixToolset.Harvesters.Data;
using WixToolset.Harvesters.Extensibility;
using Wix = WixToolset.Harvesters.Serialize;
///
/// Harvest WiX authoring for a reg file.
///
public sealed class RegFileHarvester : BaseHarvesterExtension
{
private static readonly string ComponentPrefix = "cmp";
///
/// Current line in the reg file being processed.
///
private int currentLineNumber = 0;
///
/// Flag indicating whether this is a unicode registry file.
///
private bool unicodeRegistry;
///
/// Harvest a file.
///
/// The path of the file.
/// A harvested file.
public override Wix.Fragment[] Harvest(string argument)
{
if (null == argument)
{
throw new ArgumentNullException("argument");
}
// Harvest the keys from the registry file
Wix.Fragment fragment = this.HarvestRegFile(argument);
return new Wix.Fragment[] { fragment };
}
///
/// Harvest a reg file.
///
/// The path of the file.
/// A harvested registy file.
public Wix.Fragment HarvestRegFile(string path)
{
if (null == path)
{
throw new ArgumentNullException("path");
}
if (!File.Exists(path))
{
throw new WixException(HarvesterErrors.FileNotFound(path));
}
var directory = DirectoryHelper.CreateDirectory("TARGETDIR");
// Use absolute paths
path = Path.GetFullPath(path);
FileInfo file = new FileInfo(path);
using (StreamReader sr = file.OpenText())
{
string line;
this.currentLineNumber = 0;
while (null != (line = this.GetNextLine(sr)))
{
if (line.StartsWith(@"Windows Registry Editor Version 5.00"))
{
this.unicodeRegistry = true;
}
else if (line.StartsWith(@"REGEDIT4"))
{
this.unicodeRegistry = false;
}
else if (line.StartsWith(@"[HKEY_CLASSES_ROOT\"))
{
this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKCR, line.Substring(19, line.Length - 20));
}
else if (line.StartsWith(@"[HKEY_CURRENT_USER\"))
{
this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKCU, line.Substring(19, line.Length - 20));
}
else if (line.StartsWith(@"[HKEY_LOCAL_MACHINE\"))
{
this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKLM, line.Substring(20, line.Length - 21));
}
else if (line.StartsWith(@"[HKEY_USERS\"))
{
this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKU, line.Substring(12, line.Length - 13));
}
}
}
Console.WriteLine("Processing complete");
Wix.Fragment fragment = new Wix.Fragment();
fragment.AddChild(directory);
return fragment;
}
///
/// Converts the registry key to a WiX component element.
///
/// The registry file stream.
/// A WiX directory reference.
/// The root key.
/// The current line.
private void ConvertKey(StreamReader sr, ref Wix.DirectoryBase directory, Wix.RegistryRootType root, string line)
{
Wix.Component component = new Wix.Component();
component.Id = this.Core.GenerateIdentifier(ComponentPrefix, line);
component.KeyPath = Wix.YesNoType.yes;
this.ConvertValues(sr, ref component, root, line);
directory.AddChild(component);
}
///
/// Converts the registry values to WiX regisry key element.
///
/// The registry file stream.
/// A WiX component reference.
/// The root key.
/// The current line.
private void ConvertValues(StreamReader sr, ref Wix.Component component, Wix.RegistryRootType root, string line)
{
string name = null;
string value = null;
var registryKey = new Wix.RegistryKey
{
Root = root,
Key = line
};
while (this.GetValue(sr, ref name, ref value, out var type))
{
Wix.RegistryValue registryValue = new Wix.RegistryValue();
ArrayList charArray;
// Don't specifiy name for default attribute
if (!String.IsNullOrEmpty(name))
{
registryValue.Name = name;
}
registryValue.Type = type;
switch (type)
{
case Wix.RegistryValue.TypeType.binary:
registryValue.Value = value.Replace(",", String.Empty).ToUpper();
break;
case Wix.RegistryValue.TypeType.integer:
registryValue.Value = Int32.Parse(value, NumberStyles.HexNumber).ToString();
break;
case Wix.RegistryValue.TypeType.expandable:
charArray = this.ConvertCharList(value);
value = String.Empty;
// create the string, remove the terminating null
for (int i = 0; i < charArray.Count; i++)
{
if ('\0' != (char)charArray[i])
{
value += charArray[i];
}
}
registryValue.Value = value;
break;
case Wix.RegistryValue.TypeType.multiString:
charArray = this.ConvertCharList(value);
value = String.Empty;
// Convert the character array to a string so we can simply split it at the nulls, ignore the final null null.
for (int i = 0; i < (charArray.Count - 2); i++)
{
value += charArray[i];
}
// Although the value can use [~] the preffered way is to use MultiStringValue
string[] parts = value.Split("\0".ToCharArray());
foreach (string part in parts)
{
Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue();
multiStringValue.Value = part;
registryValue.AddChild(multiStringValue);
}
break;
case Wix.RegistryValue.TypeType.@string:
// Remove \\ and \"
value = value.ToString().Replace("\\\"", "\"");
value = value.ToString().Replace(@"\\", @"\");
// Escape [ and ]
value = value.ToString().Replace(@"[", @"[\[]");
value = value.ToString().Replace(@"]", @"[\]]");
// This undoes the duplicate escaping caused by the second replace
value = value.ToString().Replace(@"[\[[\]]", @"[\[]");
// Escape $
value = value.ToString().Replace(@"$", @"$$");
registryValue.Value = value;
break;
default:
throw new ApplicationException(String.Format("Did not recognize the type of reg value on line {0}", this.currentLineNumber));
}
registryKey.AddChild(registryValue);
}
// Make sure empty keys are created
if (null == value)
{
registryKey.ForceCreateOnInstall = Wix.YesNoType.yes;
}
component.AddChild(registryKey);
}
///
/// Parse a value from a line.
///
/// Reader for the reg file.
/// Name of the value.
/// Value of the value.
/// Type of the value.
/// true if the value can be parsed, false otherwise.
private bool GetValue(StreamReader sr, ref string name, ref string value, out Wix.RegistryValue.TypeType type)
{
string line = this.GetNextLine(sr);
if (null == line || 0 == line.Length)
{
type = 0;
return false;
}
string[] parts;
if (line.StartsWith("@"))
{
// Special case for default value
parts = line.Trim().Split("=".ToCharArray(), 2);
name = null;
}
else
{
parts = line.Trim().Split("=".ToCharArray());
// It is valid to have an '=' in the name or the data. This is probably a string so the separator will be '"="'.
if (2 != parts.Length)
{
string[] stringSeparator = new string[] { "\"=\"" };
parts = line.Trim().Split(stringSeparator, StringSplitOptions.None);
if (2 != parts.Length)
{
// Line still no parsed correctly
throw new ApplicationException(String.Format("Cannot parse value: {0} at line {1}.", line, this.currentLineNumber));
}
// Put back quotes stripped by Split()
parts[0] += "\"";
parts[1] = "\"" + parts[1];
}
name = parts[0].Substring(1, parts[0].Length - 2);
}
if (parts[1].StartsWith("hex:"))
{
// binary
value = parts[1].Substring(4);
type = Wix.RegistryValue.TypeType.binary;
}
else if (parts[1].StartsWith("dword:"))
{
// dword
value = parts[1].Substring(6);
type = Wix.RegistryValue.TypeType.integer;
}
else if (parts[1].StartsWith("hex(2):"))
{
// expandable string
value = parts[1].Substring(7);
type = Wix.RegistryValue.TypeType.expandable;
}
else if (parts[1].StartsWith("hex(7):"))
{
// multi-string
value = parts[1].Substring(7);
type = Wix.RegistryValue.TypeType.multiString;
}
else if (parts[1].StartsWith("hex("))
{
// Give a better error when we find something that isn't supported
// by specifying the type that isn't supported.
string unsupportedType = "";
if (parts[1].StartsWith("hex(0")) { unsupportedType = "REG_NONE"; }
else if (parts[1].StartsWith("hex(6")) { unsupportedType = "REG_LINK"; }
else if (parts[1].StartsWith("hex(8")) { unsupportedType = "REG_RESOURCE_LIST"; }
else if (parts[1].StartsWith("hex(9")) { unsupportedType = "REG_FULL_RESOURCE_DESCRIPTOR"; }
else if (parts[1].StartsWith("hex(a")) { unsupportedType = "REG_RESOURCE_REQUIREMENTS_LIST"; }
else if (parts[1].StartsWith("hex(b")) { unsupportedType = "REG_QWORD"; }
// REG_NONE(0), REG_LINK(6), REG_RESOURCE_LIST(8), REG_FULL_RESOURCE_DESCRIPTOR(9), REG_RESOURCE_REQUIREMENTS_LIST(a), REG_QWORD(b)
this.Core.Messaging.Write(HarvesterWarnings.UnsupportedRegistryType(parts[0], this.currentLineNumber, unsupportedType));
type = 0;
return false;
}
else if (parts[1].StartsWith("\""))
{
// string
value = parts[1].Substring(1, parts[1].Length - 2);
type = Wix.RegistryValue.TypeType.@string;
}
else
{
// unsupported value
throw new ApplicationException(String.Format("Unsupported registry value {0} at line {1}.", line, this.currentLineNumber));
}
return true;
}
///
/// Get the next line from the reg file input stream.
///
/// Reader for the reg file.
/// The next line.
private string GetNextLine(StreamReader sr)
{
string line;
string totalLine = null;
while (null != (line = sr.ReadLine()))
{
bool stop = true;
this.currentLineNumber++;
line = line.Trim();
if (line.EndsWith("\\"))
{
stop = false;
line = line.Substring(0, line.Length - 1);
}
if (null == totalLine)
{
// first line
totalLine = line;
}
else
{
// other lines
totalLine += line;
}
// break if there is no more info for this line
if (stop)
{
break;
}
}
return totalLine;
}
///
/// Convert a character list into the proper WiX format for either unicode or ansi lists.
///
/// List of characters.
/// Array of characters.
private ArrayList ConvertCharList(string charList)
{
if (String.IsNullOrEmpty(charList))
{
return new ArrayList();
}
string[] strChars = charList.Split(",".ToCharArray());
ArrayList charArray = new ArrayList();
if (this.unicodeRegistry)
{
if (0 != strChars.Length % 2)
{
throw new ApplicationException(String.Format("Problem parsing Expandable string data at line {0}, its probably not Unicode.", this.currentLineNumber));
}
for (int i = 0; i < strChars.Length; i += 2)
{
string chars = strChars[i + 1] + strChars[i];
int unicodeInt = Int32.Parse(chars, NumberStyles.HexNumber);
char unicodeChar = (char)unicodeInt;
charArray.Add(unicodeChar);
}
}
else
{
for (int i = 0; i < strChars.Length; i++)
{
char charValue = (char)Int32.Parse(strChars[i], NumberStyles.HexNumber);
charArray.Add(charValue);
}
}
return charArray;
}
}
}