summaryrefslogtreecommitdiff
path: root/src/tools/heat/RegistryHarvester.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/heat/RegistryHarvester.cs')
-rw-r--r--src/tools/heat/RegistryHarvester.cs477
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
3namespace 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}