diff options
Diffstat (limited to 'src/tools/heat/RegFileHarvester.cs')
-rw-r--r-- | src/tools/heat/RegFileHarvester.cs | 438 |
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 | |||
3 | namespace 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 | } | ||