How to remotely access the Win64 registry from a Win32 machine in managed code (Part II)

This is a continuation of a previous post. As you recall (or not), we had some test code that would open a remote registry on a server machine and read values. These values would then be used on the client machine to run tests against that server. We hit a wall when the OS was x64 because of registry redirection done by Win64 when running in Win32 compatibility mode.

In the previous post, I showed how we were able to solve the problem for a case where the calls are made locally – we used the RegDisableReflectionKey API function to make sure we get access to the correct key. That API call, however, does not work on keys opened on remote machines.

A flag, a flag, my kingdom for a flag

There’s a solution for remote registry calls as well – with 2k3 SP2, MS also introduced two flags that can be used when opening a registry key to specify which registry you wish to open (the 64bit one, or the 32bit one).

The flags are:

KEY_WOW64_32KEY and

KEY_WOW64_64KEY

 

Now, these flags need to be passed in whenever you call one of these functions:

RegCreateKeyEx

RegDeleteKeyEx

RegOpenKeyEx

 

Again with the dead kittens

As described in the previous post, since managed code does not support these flags, the correct solution is to rewrite all your registry code using native P/Invokes – wrap the registry functionality you are interested in with classes, using SafeHandles and you should be gold.

That said, you can also work around this by using reflection to directly muck with the .NET Registry class. I highly suggest you do not use this method since it will break at some point. It can be the next Service Pack, it can be the next version of .NET, but it will break and you will not get a compiler warning about it. Remember: Every time you work around a .NET limitation by using reflection, somewhere, somehow, a kitten dies.

 

Following are the steps that will make the Microsoft.Win32.Registry class able to access a 64bit registry from a 32bit program:

  1. First, we will need to define a bunch of flags – what flags you end up using depends on what access you want or can get to the registry in question. In the complete code below, I defined a few of those for you.
  2. On top of the standard flags, we also have the two new flags I just described above. One will give you access to the 32bit registry (KEY_WOW64_32KEY) and one will give you the 64bit registry (KEY_WOW64_64KEY).
  3. We need to import the RegOpenKeyEx() API function.
  4. Now comes the yucky stuff – we need to create a key that already has the correct flags set (KEY_WOW_64_64KEY in our case) and plug it into the Registry class. With .NET 1.1, this was relatively easy to do – all you needed was one reflection call (since .NET 1.1 wrapped the registry using an IntPtr as the handle). However, with .NET 2.0, we now have SafeHandles – these guys make for much safer code, however, they make a kitten-killer’s life harder
  5. We now need to get a FieldInfo reference to the hkey member of the RegistryKey class. We will do that by calling GetField() on the typeof(RegistryKey) .
  6. Next, we will actually open the registry key using the imported function and the correct flags (remembering to pass in the KEY_WOW64_64KEY) flag.
  7. We will now create a SafeHandle out of the IntPtr returned to us from the P/Invoke. This is also somewhat tricky since we need to actually create a RegistrySafeHandle. Now, since this class is private we will again need to resort to reflection..
  8. Once we have the SafeHandle, we can plug it into a new instance of a RegistryClass type (which we will have to create, again, via reflection since the ctor that gets a SafeHandle is private).
  9. Voila, you now have a RegistryKey instance that will allow you to access the 64bit registry from a 32bit machine.

The sample program..

      /// <summary>

      /// This class is an example of how to access the 64bit registry remotely from a 32bit machine.

      /// It is important to note that this is a HACK - it will probably stop working when Whidbey SP1 comes out

      /// and even if it wont stop working then, it will at some point.

      /// Also, the example is not the safest example in the world. Were you to hit an exception after calling the

      /// OpenKey function but before plugging it into the SafeHandle, you will leak the handle.

      /// </summary>

    class Program

    {

        public const int STANDARD_RIGHTS_REQUIRED = 0x000F0000;

        public const int READ_CONTROL = 0x00020000;

        public const int SYNCHRONIZE = 0x00100000;

        public const int STANDARD_RIGHTS_READ = READ_CONTROL;

        public const int STANDARD_RIGHTS_WRITE = READ_CONTROL;

        public const int STANDARD_RIGHTS_EXECUTE = READ_CONTROL;

        public const int STANDARD_RIGHTS_ALL = 0x001F0000;

        public const int KEY_QUERY_VALUE = 0x0001;

        public const int KEY_SET_VALUE = 0x0002;

        public const int KEY_CREATE_SUB_KEY = 0x0004;

        public const int KEY_ENUMERATE_SUB_KEYS = 0x0008;

        public const int KEY_NOTIFY = 0x0010;

        public const int KEY_CREATE_LINK = 0x0020;

        public const int KEY_WOW64_32KEY = 0x0200;

        public const int KEY_WOW64_64KEY = 0x0100;

        public const int KEY_ALL_ACCESS = STANDARD_RIGHTS_ALL | KEY_QUERY_VALUE | KEY_SET_VALUE | KEY_CREATE_SUB_KEY | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY | KEY_CREATE_LINK & (~SYNCHRONIZE);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegOpenKeyEx")]

        static extern int RegOpenKeyEx(

            IntPtr hKey,

            string subKey,

            uint options,

            int sam,

            out IntPtr phkResult);

        static void Main(string[] args)

        {

            FieldInfo hkeyInfo = GetHkeyField();

            RegistryKey remoteKey = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, "[MachineName]");

            SafeHandle remoteKeyHandle = (SafeHandle)hkeyInfo.GetValue(remoteKey);

            IntPtr hRemoteKey = remoteKeyHandle.DangerousGetHandle();

            IntPtr hTargetKey = IntPtr.Zero;

            int l = RegOpenKeyEx(hRemoteKey, @"[Your Key Here]", 0, KEY_QUERY_VALUE | KEY_WOW64_64KEY, out hTargetKey);

            SafeHandle sh = CreateRegistrySafeHandle(hTargetKey);

          RegistryKey targetKey = (RegistryKey)Activator.CreateInstance(typeof(RegistryKey), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { sh, true }, null);

            Console.WriteLine(targetKey.GetValue("Location"));

        }

        private static SafeHandle CreateRegistrySafeHandle(IntPtr handle)

        {

            Assembly ass = typeof(SafeHandle).Assembly;

            Type type = ass.GetType("Microsoft.Win32.SafeHandles.SafeRegistryHandle");

            SafeHandle sh = (SafeHandle)Activator.CreateInstance(type, BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { handle, true }, null);

            return sh;

        }

        private static FieldInfo GetHkeyField()

        {

            Type type = typeof(RegistryKey);

            FieldInfo info = type.GetField("hkey", BindingFlags.NonPublic | BindingFlags.Instance);

            return info;

        }

    }