summaryrefslogtreecommitdiff
path: root/src/tools/heat/RegFileHarvester.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/heat/RegFileHarvester.cs')
-rw-r--r--src/tools/heat/RegFileHarvester.cs438
1 files changed, 438 insertions, 0 deletions
diff --git a/src/tools/heat/RegFileHarvester.cs b/src/tools/heat/RegFileHarvester.cs
new file mode 100644
index 00000000..b7ad8c7b
--- /dev/null
+++ b/src/tools/heat/RegFileHarvester.cs
@@ -0,0 +1,438 @@
1// 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.
2
3namespace WixToolset.Harvesters
4{
5 using System;
6 using System.Collections;
7 using System.Globalization;
8 using System.IO;
9 using WixToolset.Data;
10 using WixToolset.Harvesters.Data;
11 using WixToolset.Harvesters.Extensibility;
12 using Wix = WixToolset.Harvesters.Serialize;
13
14 /// <summary>
15 /// Harvest WiX authoring for a reg file.
16 /// </summary>
17 public sealed class RegFileHarvester : BaseHarvesterExtension
18 {
19 private static readonly string ComponentPrefix = "cmp";
20
21 /// <summary>
22 /// Current line in the reg file being processed.
23 /// </summary>
24 private int currentLineNumber = 0;
25
26 /// <summary>
27 /// Flag indicating whether this is a unicode registry file.
28 /// </summary>
29 private bool unicodeRegistry;
30
31 /// <summary>
32 /// Harvest a file.
33 /// </summary>
34 /// <param name="argument">The path of the file.</param>
35 /// <returns>A harvested file.</returns>
36 public override Wix.Fragment[] Harvest(string argument)
37 {
38 if (null == argument)
39 {
40 throw new ArgumentNullException("argument");
41 }
42
43 // Harvest the keys from the registry file
44 Wix.Fragment fragment = this.HarvestRegFile(argument);
45
46 return new Wix.Fragment[] { fragment };
47 }
48
49 /// <summary>
50 /// Harvest a reg file.
51 /// </summary>
52 /// <param name="path">The path of the file.</param>
53 /// <returns>A harvested registy file.</returns>
54 public Wix.Fragment HarvestRegFile(string path)
55 {
56 if (null == path)
57 {
58 throw new ArgumentNullException("path");
59 }
60
61 if (!File.Exists(path))
62 {
63 throw new WixException(HarvesterErrors.FileNotFound(path));
64 }
65
66 Wix.Directory directory = new Wix.Directory();
67 directory.Id = "TARGETDIR";
68
69 // Use absolute paths
70 path = Path.GetFullPath(path);
71 FileInfo file = new FileInfo(path);
72
73 using (StreamReader sr = file.OpenText())
74 {
75 string line;
76 this.currentLineNumber = 0;
77
78 while (null != (line = this.GetNextLine(sr)))
79 {
80 if (line.StartsWith(@"Windows Registry Editor Version 5.00"))
81 {
82 this.unicodeRegistry = true;
83 }
84 else if (line.StartsWith(@"REGEDIT4"))
85 {
86 this.unicodeRegistry = false;
87 }
88 else if (line.StartsWith(@"[HKEY_CLASSES_ROOT\"))
89 {
90 this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKCR, line.Substring(19, line.Length - 20));
91 }
92 else if (line.StartsWith(@"[HKEY_CURRENT_USER\"))
93 {
94 this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKCU, line.Substring(19, line.Length - 20));
95 }
96 else if (line.StartsWith(@"[HKEY_LOCAL_MACHINE\"))
97 {
98 this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKLM, line.Substring(20, line.Length - 21));
99 }
100 else if (line.StartsWith(@"[HKEY_USERS\"))
101 {
102 this.ConvertKey(sr, ref directory, Wix.RegistryRootType.HKU, line.Substring(12, line.Length - 13));
103 }
104 }
105 }
106
107 Console.WriteLine("Processing complete");
108
109 Wix.Fragment fragment = new Wix.Fragment();
110 fragment.AddChild(directory);
111
112 return fragment;
113 }
114
115 /// <summary>
116 /// Converts the registry key to a WiX component element.
117 /// </summary>
118 /// <param name="sr">The registry file stream.</param>
119 /// <param name="directory">A WiX directory reference.</param>
120 /// <param name="root">The root key.</param>
121 /// <param name="line">The current line.</param>
122 private void ConvertKey(StreamReader sr, ref Wix.Directory directory, Wix.RegistryRootType root, string line)
123 {
124 Wix.Component component = new Wix.Component();
125
126 component.Id = this.Core.GenerateIdentifier(ComponentPrefix, line);
127 component.KeyPath = Wix.YesNoType.yes;
128
129 this.ConvertValues(sr, ref component, root, line);
130 directory.AddChild(component);
131 }
132
133 /// <summary>
134 /// Converts the registry values to WiX regisry key element.
135 /// </summary>
136 /// <param name="sr">The registry file stream.</param>
137 /// <param name="component">A WiX component reference.</param>
138 /// <param name="root">The root key.</param>
139 /// <param name="line">The current line.</param>
140 private void ConvertValues(StreamReader sr, ref Wix.Component component, Wix.RegistryRootType root, string line)
141 {
142 string name = null;
143 string value = null;
144 var registryKey = new Wix.RegistryKey
145 {
146 Root = root,
147 Key = line
148 };
149
150 while (this.GetValue(sr, ref name, ref value, out var type))
151 {
152 Wix.RegistryValue registryValue = new Wix.RegistryValue();
153 ArrayList charArray;
154
155 // Don't specifiy name for default attribute
156 if (!String.IsNullOrEmpty(name))
157 {
158 registryValue.Name = name;
159 }
160
161 registryValue.Type = type;
162
163 switch (type)
164 {
165 case Wix.RegistryValue.TypeType.binary:
166 registryValue.Value = value.Replace(",", String.Empty).ToUpper();
167 break;
168
169 case Wix.RegistryValue.TypeType.integer:
170 registryValue.Value = Int32.Parse(value, NumberStyles.HexNumber).ToString();
171 break;
172
173 case Wix.RegistryValue.TypeType.expandable:
174 charArray = this.ConvertCharList(value);
175 value = String.Empty;
176
177 // create the string, remove the terminating null
178 for (int i = 0; i < charArray.Count; i++)
179 {
180 if ('\0' != (char)charArray[i])
181 {
182 value += charArray[i];
183 }
184 }
185
186 registryValue.Value = value;
187 break;
188
189 case Wix.RegistryValue.TypeType.multiString:
190 charArray = this.ConvertCharList(value);
191 value = String.Empty;
192
193 // Convert the character array to a string so we can simply split it at the nulls, ignore the final null null.
194 for (int i = 0; i < (charArray.Count - 2); i++)
195 {
196 value += charArray[i];
197 }
198
199 // Although the value can use [~] the preffered way is to use MultiStringValue
200 string[] parts = value.Split("\0".ToCharArray());
201 foreach (string part in parts)
202 {
203 Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue();
204 multiStringValue.Content = part;
205 registryValue.AddChild(multiStringValue);
206 }
207
208 break;
209
210 case Wix.RegistryValue.TypeType.@string:
211 // Remove \\ and \"
212 value = value.ToString().Replace("\\\"", "\"");
213 value = value.ToString().Replace(@"\\", @"\");
214 // Escape [ and ]
215 value = value.ToString().Replace(@"[", @"[\[]");
216 value = value.ToString().Replace(@"]", @"[\]]");
217 // This undoes the duplicate escaping caused by the second replace
218 value = value.ToString().Replace(@"[\[[\]]", @"[\[]");
219 // Escape $
220 value = value.ToString().Replace(@"$", @"$$");
221
222 registryValue.Value = value;
223 break;
224
225 default:
226 throw new ApplicationException(String.Format("Did not recognize the type of reg value on line {0}", this.currentLineNumber));
227 }
228
229 registryKey.AddChild(registryValue);
230 }
231
232 // Make sure empty keys are created
233 if (null == value)
234 {
235 registryKey.ForceCreateOnInstall = Wix.YesNoType.yes;
236 }
237
238 component.AddChild(registryKey);
239 }
240
241 /// <summary>
242 /// Parse a value from a line.
243 /// </summary>
244 /// <param name="sr">Reader for the reg file.</param>
245 /// <param name="name">Name of the value.</param>
246 /// <param name="value">Value of the value.</param>
247 /// <param name="type">Type of the value.</param>
248 /// <returns>true if the value can be parsed, false otherwise.</returns>
249 private bool GetValue(StreamReader sr, ref string name, ref string value, out Wix.RegistryValue.TypeType type)
250 {
251 string line = this.GetNextLine(sr);
252
253 if (null == line || 0 == line.Length)
254 {
255 type = 0;
256 return false;
257 }
258
259 string[] parts;
260
261 if (line.StartsWith("@"))
262 {
263 // Special case for default value
264 parts = line.Trim().Split("=".ToCharArray(), 2);
265
266 name = null;
267 }
268 else
269 {
270 parts = line.Trim().Split("=".ToCharArray());
271
272 // It is valid to have an '=' in the name or the data. This is probably a string so the separator will be '"="'.
273 if (2 != parts.Length)
274 {
275 string[] stringSeparator = new string[] { "\"=\"" };
276 parts = line.Trim().Split(stringSeparator, StringSplitOptions.None);
277
278 if (2 != parts.Length)
279 {
280 // Line still no parsed correctly
281 throw new ApplicationException(String.Format("Cannot parse value: {0} at line {1}.", line, this.currentLineNumber));
282 }
283
284 // Put back quotes stripped by Split()
285 parts[0] += "\"";
286 parts[1] = "\"" + parts[1];
287 }
288
289 name = parts[0].Substring(1, parts[0].Length - 2);
290 }
291
292 if (parts[1].StartsWith("hex:"))
293 {
294 // binary
295 value = parts[1].Substring(4);
296 type = Wix.RegistryValue.TypeType.binary;
297 }
298 else if (parts[1].StartsWith("dword:"))
299 {
300 // dword
301 value = parts[1].Substring(6);
302 type = Wix.RegistryValue.TypeType.integer;
303 }
304 else if (parts[1].StartsWith("hex(2):"))
305 {
306 // expandable string
307 value = parts[1].Substring(7);
308 type = Wix.RegistryValue.TypeType.expandable;
309 }
310 else if (parts[1].StartsWith("hex(7):"))
311 {
312 // multi-string
313 value = parts[1].Substring(7);
314 type = Wix.RegistryValue.TypeType.multiString;
315 }
316 else if (parts[1].StartsWith("hex("))
317 {
318 // Give a better error when we find something that isn't supported
319 // by specifying the type that isn't supported.
320 string unsupportedType = "";
321
322 if (parts[1].StartsWith("hex(0")) { unsupportedType = "REG_NONE"; }
323 else if (parts[1].StartsWith("hex(6")) { unsupportedType = "REG_LINK"; }
324 else if (parts[1].StartsWith("hex(8")) { unsupportedType = "REG_RESOURCE_LIST"; }
325 else if (parts[1].StartsWith("hex(9")) { unsupportedType = "REG_FULL_RESOURCE_DESCRIPTOR"; }
326 else if (parts[1].StartsWith("hex(a")) { unsupportedType = "REG_RESOURCE_REQUIREMENTS_LIST"; }
327 else if (parts[1].StartsWith("hex(b")) { unsupportedType = "REG_QWORD"; }
328
329 // REG_NONE(0), REG_LINK(6), REG_RESOURCE_LIST(8), REG_FULL_RESOURCE_DESCRIPTOR(9), REG_RESOURCE_REQUIREMENTS_LIST(a), REG_QWORD(b)
330 this.Core.Messaging.Write(HarvesterWarnings.UnsupportedRegistryType(parts[0], this.currentLineNumber, unsupportedType));
331
332 type = 0;
333 return false;
334 }
335 else if (parts[1].StartsWith("\""))
336 {
337 // string
338 value = parts[1].Substring(1, parts[1].Length - 2);
339 type = Wix.RegistryValue.TypeType.@string;
340 }
341 else
342 {
343 // unsupported value
344 throw new ApplicationException(String.Format("Unsupported registry value {0} at line {1}.", line, this.currentLineNumber));
345 }
346
347 return true;
348 }
349
350 /// <summary>
351 /// Get the next line from the reg file input stream.
352 /// </summary>
353 /// <param name="sr">Reader for the reg file.</param>
354 /// <returns>The next line.</returns>
355 private string GetNextLine(StreamReader sr)
356 {
357 string line;
358 string totalLine = null;
359
360 while (null != (line = sr.ReadLine()))
361 {
362 bool stop = true;
363
364 this.currentLineNumber++;
365 line = line.Trim();
366 Console.Write("Processing line: {0}\r", this.currentLineNumber);
367
368 if (line.EndsWith("\\"))
369 {
370 stop = false;
371 line = line.Substring(0, line.Length - 1);
372 }
373
374 if (null == totalLine)
375 {
376 // first line
377 totalLine = line;
378 }
379 else
380 {
381 // other lines
382 totalLine += line;
383 }
384
385 // break if there is no more info for this line
386 if (stop)
387 {
388 break;
389 }
390 }
391
392 return totalLine;
393 }
394
395 /// <summary>
396 /// Convert a character list into the proper WiX format for either unicode or ansi lists.
397 /// </summary>
398 /// <param name="charList">List of characters.</param>
399 /// <returns>Array of characters.</returns>
400 private ArrayList ConvertCharList(string charList)
401 {
402 if (String.IsNullOrEmpty(charList))
403 {
404 return new ArrayList();
405 }
406
407 string[] strChars = charList.Split(",".ToCharArray());
408
409 ArrayList charArray = new ArrayList();
410
411 if (this.unicodeRegistry)
412 {
413 if (0 != strChars.Length % 2)
414 {
415 throw new ApplicationException(String.Format("Problem parsing Expandable string data at line {0}, its probably not Unicode.", this.currentLineNumber));
416 }
417
418 for (int i = 0; i < strChars.Length; i += 2)
419 {
420 string chars = strChars[i + 1] + strChars[i];
421 int unicodeInt = Int32.Parse(chars, NumberStyles.HexNumber);
422 char unicodeChar = (char)unicodeInt;
423 charArray.Add(unicodeChar);
424 }
425 }
426 else
427 {
428 for (int i = 0; i < strChars.Length; i++)
429 {
430 char charValue = (char)Int32.Parse(strChars[i], NumberStyles.HexNumber);
431 charArray.Add(charValue);
432 }
433 }
434
435 return charArray;
436 }
437 }
438}