summaryrefslogtreecommitdiff
path: root/src/tools/heat/RegFileHarvester.cs
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-07-26 17:20:39 -0700
committerRob Mensching <rob@firegiant.com>2022-08-01 20:25:19 -0700
commita627ca9b720047e633a8fe72003ab9bee31006c5 (patch)
tree2bc8a924bb4141ab718e74d08f6459a0ffe8d573 /src/tools/heat/RegFileHarvester.cs
parent521eb3c9cf38823a2c4019abb85dc0b3200b92cb (diff)
downloadwix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.gz
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.tar.bz2
wix-a627ca9b720047e633a8fe72003ab9bee31006c5.zip
Create WixToolset.Heat.nupkg to distribute heat.exe and Heat targets
Moves Heat functionality to the "tools" layer and packages it all up in WixToolset.Heat.nupkg for distribution in WiX v4. Completes 6838
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}