aboutsummaryrefslogtreecommitdiff
path: root/src/tools/heat/RegistryHarvester.cs
blob: 71dd3962b67b5a94dd51f3960f35968e410fe0a4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
// 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.

namespace WixToolset.Harvesters
{
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.Text;
    using Microsoft.Win32;
    using WixToolset.Data;
    using WixToolset.Harvesters.Data;
    using Wix = WixToolset.Harvesters.Serialize;

    /// <summary>
    /// Harvest WiX authoring from the registry.
    /// </summary>
    public sealed class RegistryHarvester : IDisposable
    {
        private const string HKCRPathInHKLM = @"Software\Classes";
        private string remappedPath;
        private static readonly int majorOSVersion = Environment.OSVersion.Version.Major;
        private RegistryKey regKeyToOverride = Registry.LocalMachine;
        private IntPtr regRootToOverride = NativeMethods.HkeyLocalMachine;

        /// <summary>
        /// Instantiate a new RegistryHarvester.
        /// </summary>
        /// <param name="remap">Set to true to remap the entire registry to a private location for this process.</param>
        public RegistryHarvester(bool remap)
        {
            // Detect OS major version and set the hive to use when
            // redirecting registry writes. We want to redirect registry
            // writes to HKCU on Windows Vista and higher to avoid UAC
            // problems, and to HKLM on downlevel OS's.
            if (majorOSVersion >= 6)
            {
                this.regKeyToOverride = Registry.CurrentUser;
                this.regRootToOverride = NativeMethods.HkeyCurrentUser;
            }

            // create a path in the registry for redirected keys which is process-specific
            if (remap)
            {
                this.remappedPath = String.Concat(@"SOFTWARE\WiX\heat\", Process.GetCurrentProcess().Id.ToString(CultureInfo.InvariantCulture));

                // remove the previous remapped key if it exists
                this.RemoveRemappedKey();

                // remap the registry roots supported by MSI
                // note - order is important here - the hive being used to redirect
                // to must be overridden last to avoid creating the other override
                // hives in the wrong location in the registry. For example, if HKLM is
                // the redirect destination, overriding it first will cause other hives
                // to be overridden under HKLM\Software\WiX\heat\HKLM\Software\WiX\HKCR
                // instead of under HKLM\Software\WiX\heat\HKCR
                if (majorOSVersion < 6)
                {
                    this.RemapRegistryKey(NativeMethods.HkeyClassesRoot, String.Concat(this.remappedPath, @"\\HKEY_CLASSES_ROOT"));
                    this.RemapRegistryKey(NativeMethods.HkeyCurrentUser, String.Concat(this.remappedPath, @"\\HKEY_CURRENT_USER"));
                    this.RemapRegistryKey(NativeMethods.HkeyUsers, String.Concat(this.remappedPath, @"\\HKEY_USERS"));
                    this.RemapRegistryKey(NativeMethods.HkeyLocalMachine, String.Concat(this.remappedPath, @"\\HKEY_LOCAL_MACHINE"));
                }
                else
                {
                    this.RemapRegistryKey(NativeMethods.HkeyClassesRoot, String.Concat(this.remappedPath, @"\\HKEY_CLASSES_ROOT"));
                    this.RemapRegistryKey(NativeMethods.HkeyLocalMachine, String.Concat(this.remappedPath, @"\\HKEY_LOCAL_MACHINE"));
                    this.RemapRegistryKey(NativeMethods.HkeyUsers, String.Concat(this.remappedPath, @"\\HKEY_USERS"));
                    this.RemapRegistryKey(NativeMethods.HkeyCurrentUser, String.Concat(this.remappedPath, @"\\HKEY_CURRENT_USER"));

                    // Typelib registration on Windows Vista requires that the key
                    // HKLM\Software\Classes exist, so add it to the remapped root
                    Registry.LocalMachine.CreateSubKey(HKCRPathInHKLM);
                }
            }
        }

        /// <summary>
        /// Close the RegistryHarvester and remove any remapped registry keys.
        /// </summary>
        public void Close()
        {
            // note - order is important here - we must quit overriding the hive
            // being used to redirect first
            if (majorOSVersion < 6)
            {
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyLocalMachine, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyClassesRoot, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyCurrentUser, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyUsers, IntPtr.Zero);
            }
            else
            {
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyCurrentUser, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyClassesRoot, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyLocalMachine, IntPtr.Zero);
                NativeMethods.OverrideRegistryKey(NativeMethods.HkeyUsers, IntPtr.Zero);
            }

            this.RemoveRemappedKey();
        }

        /// <summary>
        /// Dispose the RegistryHarvester.
        /// </summary>
        public void Dispose()
        {
            this.Close();
        }

        /// <summary>
        /// Harvest all registry roots supported by Windows Installer.
        /// </summary>
        /// <returns>The registry keys and values in the registry.</returns>
        public Wix.RegistryValue[] HarvestRegistry()
        {
            ArrayList registryValues = new ArrayList();

            this.HarvestRegistryKey(Registry.ClassesRoot, registryValues);
            this.HarvestRegistryKey(Registry.CurrentUser, registryValues);
            this.HarvestRegistryKey(Registry.LocalMachine, registryValues);
            this.HarvestRegistryKey(Registry.Users, registryValues);

            return (Wix.RegistryValue[])registryValues.ToArray(typeof(Wix.RegistryValue));
        }

        /// <summary>
        /// Harvest a registry key.
        /// </summary>
        /// <param name="path">The path of the registry key to harvest.</param>
        /// <returns>The registry keys and values under the key.</returns>
        public Wix.RegistryValue[] HarvestRegistryKey(string path)
        {
            RegistryKey registryKey = null;
            ArrayList registryValues = new ArrayList();

            string[] parts = GetPathParts(path);

            try
            {
                switch (parts[0])
                {
                    case "HKEY_CLASSES_ROOT":
                        registryKey = Registry.ClassesRoot;
                        break;
                    case "HKEY_CURRENT_USER":
                        registryKey = Registry.CurrentUser;
                        break;
                    case "HKEY_LOCAL_MACHINE":
                        registryKey = Registry.LocalMachine;
                        break;
                    case "HKEY_USERS":
                        registryKey = Registry.Users;
                        break;
                    default:
                        // TODO: put a better exception here
                        throw new Exception();
                }

                if (1 < parts.Length)
                {
                    registryKey = registryKey.OpenSubKey(parts[1]);

                    if (null == registryKey)
                    {
                        throw new WixException(HarvesterErrors.UnableToOpenRegistryKey(parts[1]));
                    }
                }

                this.HarvestRegistryKey(registryKey, registryValues);
            }
            finally
            {
                if (null != registryKey)
                {
                    registryKey.Close();
                }
            }

            return (Wix.RegistryValue[])registryValues.ToArray(typeof(Wix.RegistryValue));
        }

        /// <summary>
        /// Gets the parts of a registry key's path.
        /// </summary>
        /// <param name="path">The registry key path.</param>
        /// <returns>The root and key parts of the registry key path.</returns>
        private static string[] GetPathParts(string path)
        {
            return path.Split(@"\".ToCharArray(), 2);
        }

        /// <summary>
        /// Harvest a registry key.
        /// </summary>
        /// <param name="registryKey">The registry key to harvest.</param>
        /// <param name="registryValues">The collected registry values.</param>
        private void HarvestRegistryKey(RegistryKey registryKey, ArrayList registryValues)
        {
            // harvest the sub-keys
            foreach (string subKeyName in registryKey.GetSubKeyNames())
            {
                using (RegistryKey subKey = registryKey.OpenSubKey(subKeyName))
                {
                    this.HarvestRegistryKey(subKey, registryValues);
                }
            }

            string[] parts = GetPathParts(registryKey.Name);

            Wix.RegistryRootType root;
            switch (parts[0])
            {
                case "HKEY_CLASSES_ROOT":
                    root = Wix.RegistryRootType.HKCR;
                    break;
                case "HKEY_CURRENT_USER":
                    root = Wix.RegistryRootType.HKCU;
                    break;
                case "HKEY_LOCAL_MACHINE":
                    // HKLM\Software\Classes is equivalent to HKCR
                    if (1 < parts.Length && parts[1].StartsWith(HKCRPathInHKLM, StringComparison.OrdinalIgnoreCase))
                    {
                        root = Wix.RegistryRootType.HKCR;
                        parts[1] = parts[1].Remove(0, HKCRPathInHKLM.Length);

                        if (0 < parts[1].Length)
                        {
                            parts[1] = parts[1].TrimStart('\\');
                        }

                        if (String.IsNullOrEmpty(parts[1]))
                        {
                            parts = new [] { parts[0] };
                        }
                    }
                    else
                    {
                        root = Wix.RegistryRootType.HKLM;
                    }
                    break;
                case "HKEY_USERS":
                    root = Wix.RegistryRootType.HKU;
                    break;
                default:
                    // TODO: put a better exception here
                    throw new Exception();
            }

            // harvest the values
            foreach (string valueName in registryKey.GetValueNames())
            {
                Wix.RegistryValue registryValue = new Wix.RegistryValue();

                registryValue.Action = Wix.RegistryValue.ActionType.write;

                registryValue.Root = root;

                if (1 < parts.Length)
                {
                    registryValue.Key = parts[1];
                }

                if (null != valueName && 0 < valueName.Length)
                {
                    registryValue.Name = valueName;
                }

                object value = registryKey.GetValue(valueName);

                if (value is byte[]) // binary
                {
                    StringBuilder hexadecimalValue = new StringBuilder();

                    // convert the byte array to hexadecimal
                    foreach (byte byteValue in (byte[])value)
                    {
                        hexadecimalValue.Append(byteValue.ToString("X2", CultureInfo.InvariantCulture.NumberFormat));
                    }

                    registryValue.Type = Wix.RegistryValue.TypeType.binary;
                    registryValue.Value = hexadecimalValue.ToString();
                }
                else if (value is int) // integer
                {
                    registryValue.Type = Wix.RegistryValue.TypeType.integer;
                    registryValue.Value = ((int)value).ToString(CultureInfo.InvariantCulture);
                }
                else if (value is string[]) // multi-string
                {
                    registryValue.Type = Wix.RegistryValue.TypeType.multiString;

                    if (0 == ((string[])value).Length)
                    {
                        Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue();

                        multiStringValue.Value = String.Empty;

                        registryValue.AddChild(multiStringValue);
                    }
                    else
                    {
                        foreach (string multiStringValueContent in (string[])value)
                        {
                            Wix.MultiStringValue multiStringValue = new Wix.MultiStringValue();

                            multiStringValue.Value = multiStringValueContent;

                            registryValue.AddChild(multiStringValue);
                        }
                    }
                }
                else if (value is string) // string, expandable (there is no way to differentiate a string and expandable value in .NET 1.1)
                {
                    registryValue.Type = Wix.RegistryValue.TypeType.@string;
                    registryValue.Value = (string)value;
                }
                else
                {
                    // TODO: put a better exception here
                    throw new Exception();
                }

                registryValues.Add(registryValue);
            }

            // If there were no subkeys and no values, we still need an element for this empty registry key.
            // But specifically avoid SOFTWARE\Classes because it shouldn't be harvested as an empty key.
            if (parts.Length > 1 && registryKey.SubKeyCount == 0 && registryKey.ValueCount == 0 &&
                !String.Equals(parts[1], HKCRPathInHKLM, StringComparison.OrdinalIgnoreCase))
            {
                Wix.RegistryValue emptyRegistryKey = new Wix.RegistryValue();
                emptyRegistryKey.Root = root;
                emptyRegistryKey.Key = parts[1];
                emptyRegistryKey.Type = Wix.RegistryValue.TypeType.@string;
                emptyRegistryKey.Value = String.Empty;
                emptyRegistryKey.Action = Wix.RegistryValue.ActionType.write;
                registryValues.Add(emptyRegistryKey);
            }
        }

        /// <summary>
        /// Remap a registry key to an alternative location.
        /// </summary>
        /// <param name="registryKey">The registry key to remap.</param>
        /// <param name="remappedPath">The path to remap the registry key to under HKLM.</param>
        private void RemapRegistryKey(IntPtr registryKey, string remappedPath)
        {
            IntPtr remappedKey = IntPtr.Zero;

            try
            {
                remappedKey = NativeMethods.OpenRegistryKey(this.regRootToOverride, remappedPath);

                NativeMethods.OverrideRegistryKey(registryKey, remappedKey);
            }
            finally
            {
                if (IntPtr.Zero != remappedKey)
                {
                    NativeMethods.CloseRegistryKey(remappedKey);
                }
            }
        }

        /// <summary>
        /// Remove the remapped registry key.
        /// </summary>
        private void RemoveRemappedKey()
        {
            try
            {
                this.regKeyToOverride.DeleteSubKeyTree(this.remappedPath);
            }
            catch (ArgumentException)
            {
                // ignore the error where the key does not exist
            }
        }

        /// <summary>
        /// The native methods for re-mapping registry keys.
        /// </summary>
        private sealed class NativeMethods
        {
            internal static readonly IntPtr HkeyClassesRoot = (IntPtr)unchecked((Int32)0x80000000);
            internal static readonly IntPtr HkeyCurrentUser = (IntPtr)unchecked((Int32)0x80000001);
            internal static readonly IntPtr HkeyLocalMachine = (IntPtr)unchecked((Int32)0x80000002);
            internal static readonly IntPtr HkeyUsers = (IntPtr)unchecked((Int32)0x80000003);

            private const uint GenericRead = 0x80000000;
            private const uint GenericWrite = 0x40000000;
            private const uint GenericExecute = 0x20000000;
            private const uint GenericAll = 0x10000000;
            private const uint StandardRightsAll = 0x001F0000;

            /// <summary>
            /// Opens a registry key.
            /// </summary>
            /// <param name="key">Base key to open.</param>
            /// <param name="path">Path to subkey to open.</param>
            /// <returns>Handle to new key.</returns>
            internal static IntPtr OpenRegistryKey(IntPtr key, string path)
            {
                IntPtr newKey = IntPtr.Zero;
                uint disposition = 0;
                uint sam = StandardRightsAll | GenericRead | GenericWrite | GenericExecute | GenericAll;

                if (0 != RegCreateKeyEx(key, path, 0, null, 0, sam, 0, out newKey, out disposition))
                {
                    throw new Exception();
                }

                return newKey;
            }

            /// <summary>
            /// Closes a previously open registry key.
            /// </summary>
            /// <param name="key">Handle to key to close.</param>
            internal static void CloseRegistryKey(IntPtr key)
            {
                if (0 != RegCloseKey(key))
                {
                    throw new Exception();
                }
            }

            /// <summary>
            /// Override a registry key.
            /// </summary>
            /// <param name="key">Handle of the key to override.</param>
            /// <param name="newKey">Handle to override key.</param>
            internal static void OverrideRegistryKey(IntPtr key, IntPtr newKey)
            {
                if (0 != RegOverridePredefKey(key, newKey))
                {
                    throw new Exception();
                }
            }

            /// <summary>
            /// Interop to RegCreateKeyW.
            /// </summary>
            /// <param name="key">Handle to base key.</param>
            /// <param name="subkey">Subkey to create.</param>
            /// <param name="reserved">Always 0</param>
            /// <param name="className">Just pass null.</param>
            /// <param name="options">Just pass 0.</param>
            /// <param name="desiredSam">Rights to registry key.</param>
            /// <param name="securityAttributes">Just pass null.</param>
            /// <param name="openedKey">Opened key.</param>
            /// <param name="disposition">Whether key was opened or created.</param>
            /// <returns>Handle to registry key.</returns>
            [DllImport("advapi32.dll", EntryPoint = "RegCreateKeyExW", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
            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);

            /// <summary>
            /// Interop to RegCloseKey.
            /// </summary>
            /// <param name="key">Handle to key to close.</param>
            /// <returns>0 if success.</returns>
            [DllImport("advapi32.dll", EntryPoint = "RegCloseKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
            private static extern int RegCloseKey(IntPtr key);

            /// <summary>
            /// Interop to RegOverridePredefKey.
            /// </summary>
            /// <param name="key">Handle to key to override.</param>
            /// <param name="newKey">Handle to override key.</param>
            /// <returns>0 if success.</returns>
            [DllImport("advapi32.dll", EntryPoint = "RegOverridePredefKey", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
            private static extern int RegOverridePredefKey(IntPtr key, IntPtr newKey);
        }
    }
}