// 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; } } }