diff options
Diffstat (limited to 'src/tools/heat/RegistryHarvester.cs')
-rw-r--r-- | src/tools/heat/RegistryHarvester.cs | 477 |
1 files changed, 477 insertions, 0 deletions
diff --git a/src/tools/heat/RegistryHarvester.cs b/src/tools/heat/RegistryHarvester.cs new file mode 100644 index 00000000..0dfd3a29 --- /dev/null +++ b/src/tools/heat/RegistryHarvester.cs | |||
@@ -0,0 +1,477 @@ | |||
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.Diagnostics; | ||
8 | using System.Globalization; | ||
9 | using System.Runtime.InteropServices; | ||
10 | using System.Text; | ||
11 | using Microsoft.Win32; | ||
12 | using WixToolset.Data; | ||
13 | using WixToolset.Harvesters.Data; | ||
14 | using Wix = WixToolset.Harvesters.Serialize; | ||
15 | |||
16 | /// <summary> | ||
17 | /// Harvest WiX authoring from the registry. | ||
18 | /// </summary> | ||
19 | public sealed class RegistryHarvester : IDisposable | ||
20 | { | ||
21 | private const string HKCRPathInHKLM = @"Software\Classes"; | ||
22 | private string remappedPath; | ||
23 | private static readonly int majorOSVersion = Environment.OSVersion.Version.Major; | ||
24 | private RegistryKey regKeyToOverride = Registry.LocalMachine; | ||
25 | private IntPtr regRootToOverride = NativeMethods.HkeyLocalMachine; | ||
26 | |||
27 | /// <summary> | ||
28 | /// Instantiate a new RegistryHarvester. | ||
29 | /// </summary> | ||
30 | /// <param name="remap">Set to true to remap the entire registry to a private location for this process.</param> | ||
31 | public RegistryHarvester(bool remap) | ||
32 | { | ||
33 | // Detect OS major version and set the hive to use when | ||
34 | // redirecting registry writes. We want to redirect registry | ||
35 | // writes to HKCU on Windows Vista and higher to avoid UAC | ||
36 | // problems, and to HKLM on downlevel OS's. | ||
37 | if (majorOSVersion >= 6) | ||
38 | { | ||
39 | this.regKeyToOverride = Registry.CurrentUser; | ||
40 | this.regRootToOverride = NativeMethods.HkeyCurrentUser; | ||
41 | } | ||
42 | |||
43 | // create a path in the registry for redirected keys which is process-specific | ||
44 | if (remap) | ||
45 | { | ||
46 | this.remappedPath = String.Concat(@"SOFTWARE\WiX\heat\", Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture)); | ||
47 | |||
48 | // remove the previous remapped key if it exists | ||
49 | this.RemoveRemappedKey(); | ||
50 | |||
51 | // remap the registry roots supported by MSI | ||
52 | // note - order is important here - the hive being used to redirect | ||
53 | // to must be overridden last to avoid creating the other override | ||
54 | // hives in the wrong location in the registry. For example, if HKLM is | ||
55 | // the redirect destination, overriding it first will cause other hives | ||
56 | // to be overridden under HKLM\Software\WiX\heat\HKLM\Software\WiX\HKCR | ||
57 | // instead of under HKLM\Software\WiX\heat\HKCR | ||
58 | if (majorOSVersion < 6) | ||
59 | { | ||
60 | this.RemapRegistryKey(NativeMethods.HkeyClassesRoot, String.Concat(this.remappedPath, @"\\HKEY_CLASSES_ROOT")); | ||
61 | this.RemapRegistryKey(NativeMethods.HkeyCurrentUser, String.Concat(this.remappedPath, @"\\HKEY_CURRENT_USER")); | ||
62 | this.RemapRegistryKey(NativeMethods.HkeyUsers, String.Concat(this.remappedPath, @"\\HKEY_USERS")); | ||
63 | this.RemapRegistryKey(NativeMethods.HkeyLocalMachine, String.Concat(this.remappedPath, @"\\HKEY_LOCAL_MACHINE")); | ||
64 | } | ||
65 | else | ||
66 | { | ||
67 | this.RemapRegistryKey(NativeMethods.HkeyClassesRoot, String.Concat(this.remappedPath, @"\\HKEY_CLASSES_ROOT")); | ||
68 | this.RemapRegistryKey(NativeMethods.HkeyLocalMachine, String.Concat(this.remappedPath, @"\\HKEY_LOCAL_MACHINE")); | ||
69 | this.RemapRegistryKey(NativeMethods.HkeyUsers, String.Concat(this.remappedPath, @"\\HKEY_USERS")); | ||
70 | this.RemapRegistryKey(NativeMethods.HkeyCurrentUser, String.Concat(this.remappedPath, @"\\HKEY_CURRENT_USER")); | ||
71 | |||
72 | // Typelib registration on Windows Vista requires that the key | ||
73 | // HKLM\Software\Classes exist, so add it to the remapped root | ||
74 | Registry.LocalMachine.CreateSubKey(HKCRPathInHKLM); | ||
75 | } | ||
76 | } | ||
77 | } | ||
78 | |||
79 | /// <summary> | ||
80 | /// Close the RegistryHarvester and remove any remapped registry keys. | ||
81 | /// </summary> | ||
82 | public void Close() | ||
83 | { | ||
84 | // note - order is important here - we must quit overriding the hive | ||
85 | // being used to redirect first | ||
86 | if (majorOSVersion < 6) | ||
87 | { | ||
88 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyLocalMachine, IntPtr.Zero); | ||
89 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyClassesRoot, IntPtr.Zero); | ||
90 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyCurrentUser, IntPtr.Zero); | ||
91 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyUsers, IntPtr.Zero); | ||
92 | } | ||
93 | else | ||
94 | { | ||
95 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyCurrentUser, IntPtr.Zero); | ||
96 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyClassesRoot, IntPtr.Zero); | ||
97 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyLocalMachine, IntPtr.Zero); | ||
98 | NativeMethods.OverrideRegistryKey(NativeMethods.HkeyUsers, IntPtr.Zero); | ||
99 | } | ||
100 | |||
101 | this.RemoveRemappedKey(); | ||
102 | } | ||
103 | |||
104 | /// <summary> | ||
105 | /// Dispose the RegistryHarvester. | ||
106 | /// </summary> | ||
107 | public void Dispose() | ||
108 | { | ||
109 | this.Close(); | ||
110 | } | ||
111 | |||
112 | /// <summary> | ||
113 | /// Harvest all registry roots supported by Windows Installer. | ||
114 | /// </summary> | ||
115 | /// <returns>The registry keys and values in the registry.</returns> | ||
116 | public Wix.RegistryValue[] HarvestRegistry() | ||
117 | { | ||
118 | ArrayList registryValues = new ArrayList(); | ||
119 | |||
120 | this.HarvestRegistryKey(Registry.ClassesRoot, registryValues); | ||
121 | this.HarvestRegistryKey(Registry.CurrentUser, registryValues); | ||
122 | this.HarvestRegistryKey(Registry.LocalMachine, registryValues); | ||
123 | this.HarvestRegistryKey(Registry.Users, registryValues); | ||
124 | |||
125 | return (Wix.RegistryValue[])registryValues.ToArray(typeof(Wix.RegistryValue)); | ||
126 | } | ||
127 | |||
128 | /// <summary> | ||
129 | /// Harvest a registry key. | ||
130 | /// </summary> | ||
131 | /// <param name="path">The path of the registry key to harvest.</param> | ||
132 | /// <returns>The registry keys and values under the key.</returns> | ||
133 | public Wix.RegistryValue[] HarvestRegistryKey(string path) | ||
134 | { | ||
135 | RegistryKey registryKey = null; | ||
136 | ArrayList registryValues = new ArrayList(); | ||
137 | |||
138 | string[] parts = GetPathParts(path); | ||
139 | |||
140 | try | ||
141 | { | ||
142 | switch (parts[0]) | ||
143 | { | ||
144 | case "HKEY_CLASSES_ROOT": | ||
145 | registryKey = Registry.ClassesRoot; | ||
146 | break; | ||
147 | case "HKEY_CURRENT_USER": | ||
148 | registryKey = Registry.CurrentUser; | ||
149 | break; | ||
150 | case "HKEY_LOCAL_MACHINE": | ||
151 | registryKey = Registry.LocalMachine; | ||
152 | break; | ||
153 | case "HKEY_USERS": | ||
154 | registryKey = Registry.Users; | ||
155 | break; | ||
156 | default: | ||
157 | // TODO: put a better exception here | ||
158 | throw new Exception(); | ||
159 | } | ||
160 | |||
161 | if (1 < parts.Length) | ||
162 | { | ||
163 | registryKey = registryKey.OpenSubKey(parts[1]); | ||
164 | |||
165 | if (null == registryKey) | ||
166 | { | ||
167 | throw new WixException(HarvesterErrors.UnableToOpenRegistryKey(parts[1])); | ||
168 | } | ||
169 | } | ||
170 | |||
171 | this.HarvestRegistryKey(registryKey, registryValues); | ||
172 | } | ||
173 | finally | ||
174 | { | ||
175 | if (null != registryKey) | ||
176 | { | ||
177 | registryKey.Close(); | ||
178 | } | ||
179 | } | ||
180 | |||
181 | return (Wix.RegistryValue[])registryValues.ToArray(typeof(Wix.RegistryValue)); | ||
182 | } | ||
183 | |||
184 | /// <summary> | ||
185 | /// Gets the parts of a registry key's path. | ||
186 | /// </summary> | ||
187 | /// <param name="path">The registry key path.</param> | ||
188 | /// <returns>The root and key parts of the registry key path.</returns> | ||
189 | private static string[] GetPathParts(string path) | ||
190 | { | ||
191 | return path.Split(@"\".ToCharArray(), 2); | ||
192 | } | ||
193 | |||
194 | /// <summary> | ||
195 | /// Harvest a registry key. | ||
196 | /// </summary> | ||
197 | /// <param name="registryKey">The registry key to harvest.</param> | ||
198 | /// <param name="registryValues">The collected registry values.</param> | ||
199 | private void HarvestRegistryKey(RegistryKey registryKey, ArrayList registryValues) | ||
200 | { | ||
201 | // harvest the sub-keys | ||
202 | foreach (string subKeyName in registryKey.GetSubKeyNames()) | ||
203 | { | ||
204 | using (RegistryKey subKey = registryKey.OpenSubKey(subKeyName)) | ||
205 | { | ||
206 | this.HarvestRegistryKey(subKey, registryValues); | ||
207 | } | ||
208 | } | ||
209 | |||
210 | string[] parts = GetPathParts(registryKey.Name); | ||
211 | |||
212 | Wix.RegistryRootType root; | ||
213 | switch (parts[0]) | ||
214 | { | ||
215 | case "HKEY_CLASSES_ROOT": | ||
216 | root = Wix.RegistryRootType.HKCR; | ||
217 | break; | ||
218 | case "HKEY_CURRENT_USER": | ||
219 | root = Wix.RegistryRootType.HKCU; | ||
220 | break; | ||
221 | case "HKEY_LOCAL_MACHINE": | ||
222 | // HKLM\Software\Classes is equivalent to HKCR | ||
223 | if (1 < parts.Length && parts[1].StartsWith(HKCRPathInHKLM, StringComparison.OrdinalIgnoreCase)) | ||
224 | { | ||
225 | root = Wix.RegistryRootType.HKCR; | ||
226 | parts[1] = parts[1].Remove(0, HKCRPathInHKLM.Length); | ||
227 | |||
228 | if (0 < parts[1].Length) | ||
229 | { | ||
230 | parts[1] = parts[1].TrimStart('\\'); | ||
231 | } | ||
232 | |||
233 | if (String.IsNullOrEmpty(parts[1])) | ||
234 | { | ||
235 | parts = new [] { parts[0] }; | ||
236 | } | ||
237 | } | ||
238 | else | ||
239 | { | ||
240 | root = Wix.RegistryRootType.HKLM; | ||
241 | } | ||
242 | break; | ||
243 | case "HKEY_USERS": | ||
244 | root = Wix.RegistryRootType.HKU; | ||
245 | break; | ||
246 | default: | ||
247 | // TODO: put a better exception here | ||
248 | throw new Exception(); | ||
249 | } | ||
250 | |||
251 | // harvest the values | ||
252 | foreach (string valueName in registryKey.GetValueNames()) | ||
253 | { | ||
254 | Wix.RegistryValue registryValue = new Wix.RegistryValue(); | ||
255 | |||
256 | registryValue.Action = Wix.RegistryValue.ActionType.write; | ||
257 | |||
258 | registryValue.Root = root; | ||
259 | |||
260 | if (1 < parts.Length) | ||
261 | { | ||
262 | registryValue.Key = parts[1]; | ||
263 | } | ||
264 | |||
265 | if (null != valueName && 0 < valueName.Length) | ||
266 | { | ||
267 | registryValue.Name = valueName; | ||
268 | } | ||
269 | |||
270 | object value = registryKey.GetValue(valueName); | ||
271 | |||
272 | if (value is byte[]) // binary | ||
273 | { | ||
274 | StringBuilder hexadecimalValue = new StringBuilder(); | ||
275 | |||
276 | // convert the byte array to hexadecimal | ||
277 | foreach (byte byteValue in (byte[])value) | ||
278 | { | ||
279 | hexadecimalValue.Append(byteValue.ToString("X2", CultureInfo.InvariantCulture.NumberFormat)); | ||
280 | } | ||
281 | |||
282 | registryValue.Type = Wix.RegistryValue.TypeType.binary; | ||
283 | registryValue.Value = hexadecimalValue.ToString(); | ||
284 | } | ||
285 | else if (value is int) // integer | ||
286 | { | ||
287 | registryValue.Type = Wix.RegistryValue.TypeType.integer; | ||
288 | registryValue.Value = ((int)value).ToString(CultureInfo.InvariantCulture); | ||
289 | } | ||
290 | else if (value is string[]) // multi-string | ||
291 | { | ||
292 | registryValue.Type = Wix.RegistryValue.TypeType.multiString; | ||
293 | |||
294 | if (0 == ((string[])value).Length) | ||
295 | { | ||
296 | Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue(); | ||
297 | |||
298 | multiStringValue.Content = String.Empty; | ||
299 | |||
300 | registryValue.AddChild(multiStringValue); | ||
301 | } | ||
302 | else | ||
303 | { | ||
304 | foreach (string multiStringValueContent in (string[])value) | ||
305 | { | ||
306 | Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue(); | ||
307 | |||
308 | multiStringValue.Content = multiStringValueContent; | ||
309 | |||
310 | registryValue.AddChild(multiStringValue); | ||
311 | } | ||
312 | } | ||
313 | } | ||
314 | else if (value is string) // string, expandable (there is no way to differentiate a string and expandable value in .NET 1.1) | ||
315 | { | ||
316 | registryValue.Type = Wix.RegistryValue.TypeType.@string; | ||
317 | registryValue.Value = (string)value; | ||
318 | } | ||
319 | else | ||
320 | { | ||
321 | // TODO: put a better exception here | ||
322 | throw new Exception(); | ||
323 | } | ||
324 | |||
325 | registryValues.Add(registryValue); | ||
326 | } | ||
327 | |||
328 | // If there were no subkeys and no values, we still need an element for this empty registry key. | ||
329 | // But specifically avoid SOFTWARE\Classes because it shouldn't be harvested as an empty key. | ||
330 | if (parts.Length > 1 && registryKey.SubKeyCount == 0 && registryKey.ValueCount == 0 && | ||
331 | !String.Equals(parts[1], HKCRPathInHKLM, StringComparison.OrdinalIgnoreCase)) | ||
332 | { | ||
333 | Wix.RegistryValue emptyRegistryKey = new Wix.RegistryValue(); | ||
334 | emptyRegistryKey.Root = root; | ||
335 | emptyRegistryKey.Key = parts[1]; | ||
336 | emptyRegistryKey.Type = Wix.RegistryValue.TypeType.@string; | ||
337 | emptyRegistryKey.Value = String.Empty; | ||
338 | emptyRegistryKey.Action = Wix.RegistryValue.ActionType.write; | ||
339 | registryValues.Add(emptyRegistryKey); | ||
340 | } | ||
341 | } | ||
342 | |||
343 | /// <summary> | ||
344 | /// Remap a registry key to an alternative location. | ||
345 | /// </summary> | ||
346 | /// <param name="registryKey">The registry key to remap.</param> | ||
347 | /// <param name="remappedPath">The path to remap the registry key to under HKLM.</param> | ||
348 | private void RemapRegistryKey(IntPtr registryKey, string remappedPath) | ||
349 | { | ||
350 | IntPtr remappedKey = IntPtr.Zero; | ||
351 | |||
352 | try | ||
353 | { | ||
354 | remappedKey = NativeMethods.OpenRegistryKey(this.regRootToOverride, remappedPath); | ||
355 | |||
356 | NativeMethods.OverrideRegistryKey(registryKey, remappedKey); | ||
357 | } | ||
358 | finally | ||
359 | { | ||
360 | if (IntPtr.Zero != remappedKey) | ||
361 | { | ||
362 | NativeMethods.CloseRegistryKey(remappedKey); | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | |||
367 | /// <summary> | ||
368 | /// Remove the remapped registry key. | ||
369 | /// </summary> | ||
370 | private void RemoveRemappedKey() | ||
371 | { | ||
372 | try | ||
373 | { | ||
374 | this.regKeyToOverride.DeleteSubKeyTree(this.remappedPath); | ||
375 | } | ||
376 | catch (ArgumentException) | ||
377 | { | ||
378 | // ignore the error where the key does not exist | ||
379 | } | ||
380 | } | ||
381 | |||
382 | /// <summary> | ||
383 | /// The native methods for re-mapping registry keys. | ||
384 | /// </summary> | ||
385 | private sealed class NativeMethods | ||
386 | { | ||
387 | internal static readonly IntPtr HkeyClassesRoot = (IntPtr)unchecked((Int32)0x80000000); | ||
388 | internal static readonly IntPtr HkeyCurrentUser = (IntPtr)unchecked((Int32)0x80000001); | ||
389 | internal static readonly IntPtr HkeyLocalMachine = (IntPtr)unchecked((Int32)0x80000002); | ||
390 | internal static readonly IntPtr HkeyUsers = (IntPtr)unchecked((Int32)0x80000003); | ||
391 | |||
392 | private const uint GenericRead = 0x80000000; | ||
393 | private const uint GenericWrite = 0x40000000; | ||
394 | private const uint GenericExecute = 0x20000000; | ||
395 | private const uint GenericAll = 0x10000000; | ||
396 | private const uint StandardRightsAll = 0x001F0000; | ||
397 | |||
398 | /// <summary> | ||
399 | /// Opens a registry key. | ||
400 | /// </summary> | ||
401 | /// <param name="key">Base key to open.</param> | ||
402 | /// <param name="path">Path to subkey to open.</param> | ||
403 | /// <returns>Handle to new key.</returns> | ||
404 | internal static IntPtr OpenRegistryKey(IntPtr key, string path) | ||
405 | { | ||
406 | IntPtr newKey = IntPtr.Zero; | ||
407 | uint disposition = 0; | ||
408 | uint sam = StandardRightsAll | GenericRead | GenericWrite | GenericExecute | GenericAll; | ||
409 | |||
410 | if (0 != RegCreateKeyEx(key, path, 0, null, 0, sam, 0, out newKey, out disposition)) | ||
411 | { | ||
412 | throw new Exception(); | ||
413 | } | ||
414 | |||
415 | return newKey; | ||
416 | } | ||
417 | |||
418 | /// <summary> | ||
419 | /// Closes a previously open registry key. | ||
420 | /// </summary> | ||
421 | /// <param name="key">Handle to key to close.</param> | ||
422 | internal static void CloseRegistryKey(IntPtr key) | ||
423 | { | ||
424 | if (0 != RegCloseKey(key)) | ||
425 | { | ||
426 | throw new Exception(); | ||
427 | } | ||
428 | } | ||
429 | |||
430 | /// <summary> | ||
431 | /// Override a registry key. | ||
432 | /// </summary> | ||
433 | /// <param name="key">Handle of the key to override.</param> | ||
434 | /// <param name="newKey">Handle to override key.</param> | ||
435 | internal static void OverrideRegistryKey(IntPtr key, IntPtr newKey) | ||
436 | { | ||
437 | if (0 != RegOverridePredefKey(key, newKey)) | ||
438 | { | ||
439 | throw new Exception(); | ||
440 | } | ||
441 | } | ||
442 | |||
443 | /// <summary> | ||
444 | /// Interop to RegCreateKeyW. | ||
445 | /// </summary> | ||
446 | /// <param name="key">Handle to base key.</param> | ||
447 | /// <param name="subkey">Subkey to create.</param> | ||
448 | /// <param name="reserved">Always 0</param> | ||
449 | /// <param name="className">Just pass null.</param> | ||
450 | /// <param name="options">Just pass 0.</param> | ||
451 | /// <param name="desiredSam">Rights to registry key.</param> | ||
452 | /// <param name="securityAttributes">Just pass null.</param> | ||
453 | /// <param name="openedKey">Opened key.</param> | ||
454 | /// <param name="disposition">Whether key was opened or created.</param> | ||
455 | /// <returns>Handle to registry key.</returns> | ||
456 | [DllImport("advapi32.dll", EntryPoint = "RegCreateKeyExW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
457 | private static extern int RegCreateKeyEx(IntPtr key, string subkey, uint reserved, string className, uint options, uint desiredSam, uint securityAttributes, out IntPtr openedKey, out uint disposition); | ||
458 | |||
459 | /// <summary> | ||
460 | /// Interop to RegCloseKey. | ||
461 | /// </summary> | ||
462 | /// <param name="key">Handle to key to close.</param> | ||
463 | /// <returns>0 if success.</returns> | ||
464 | [DllImport("advapi32.dll", EntryPoint = "RegCloseKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
465 | private static extern int RegCloseKey(IntPtr key); | ||
466 | |||
467 | /// <summary> | ||
468 | /// Interop to RegOverridePredefKey. | ||
469 | /// </summary> | ||
470 | /// <param name="key">Handle to key to override.</param> | ||
471 | /// <param name="newKey">Handle to override key.</param> | ||
472 | /// <returns>0 if success.</returns> | ||
473 | [DllImport("advapi32.dll", EntryPoint = "RegOverridePredefKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] | ||
474 | private static extern int RegOverridePredefKey(IntPtr key, IntPtr newKey); | ||
475 | } | ||
476 | } | ||
477 | } | ||