Reading 64bit Registry from a 32bit application
Asked Answered
G

6

105

I have a c# unit test project that is compiled for AnyCPU. Our build server is a 64bit machine, and has a 64bit SQL Express instance installed.

The test project uses code similar to the following to identify the path to the .MDF files:

private string GetExpressPath()
{
    RegistryKey sqlServerKey = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" );
    string sqlExpressKeyName = (string) sqlServerKey.GetValue( "SQLEXPRESS" );
    RegistryKey sqlInstanceSetupKey = sqlServerKey.OpenSubKey( sqlExpressKeyName + @"\Setup" );
    return sqlInstanceSetupKey.GetValue( "SQLDataRoot" ).ToString();
}

This code works fine on our 32bit workstations, and did work ok on the build server until I recently enabled code coverage analysis with NCover. Because NCover uses a 32bit COM component, the test runner (Gallio) runs as a 32bit process.

Checking the registry, there is no "Instance Names" key under

HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server

Is there a way for an application running in 32bit mode to access the registry outside Wow6432Node?

Glutathione answered 10/6, 2009 at 6:48 Comment(0)
R
20

you have to use the KEY_WOW64_64KEY param when creating/opening the registry key. But AFAIK that's not possible with the Registry class but only when using the API directly.

This might help to get you started.

Rentfree answered 10/6, 2009 at 7:13 Comment(0)
F
179

Reading the 64 bit registry is possible because of WOW64 which is a Windows subsystem providing access to 64 bit from within 32 bit applications. (Likewise, in older NT-based Windows versions it was called WOW and was an emulation layer inside 32 bit Windows to support 16 bit applications).

There is still native support for registry access under 64 bit Windows using .NET Framework 4.x and for newer .NET versions (such as .NET Core, .NET 5 and 6) as well. The following code is tested with  Windows 7, 64 bit  and also with  Windows 10, 64 bit. It should also work with Windows 11.

Instead of using "Wow6432Node", which emulates a node by mapping one registry tree into another making it appear there virtually, you can do the follwing:

Decide, whether you need to access the 64 bit or the 32 bit registry, and use it as described below. You may also use the code I mentioned later (Additional information section), which creates a union query to get registry keys from both nodes in one query - so you can still query them by using their real path.

64 bit registry

To access the 64 bit registry, you can use RegistryView.Registry64 as follows:

// using Microsoft.Win32
string value64 = string.Empty; 
RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
localKey = localKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey != null) 
{ 
    value64 = localKey.GetValue("RegisteredOrganization").ToString(); 
    localKey.Close();
} 
Console.WriteLine(String.Format("RegisteredOrganization [value64]: {0}",value64));

32 bit registry

If you want to access the 32bit registry, use RegistryView.Registry32 as follows:

// using Microsoft.Win32
string value32 = string.Empty; 
RegistryKey localKey32 = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry32); 
localKey32 = localKey32.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion"); 
if (localKey32 != null) 
{ 
    value32 = localKey32.GetValue("RegisteredOrganization").ToString(); 
    localKey32.Close();
} 
Console.WriteLine(String.Format("RegisteredOrganization [value32]: {0}",value32));

Don't be confused, both versions are using Microsoft.Win32.RegistryHive.LocalMachine as first parameter, you make the distinction whether to use 64 bit or 32 bit by the 2nd parameter (RegistryView.Registry64 versus RegistryView.Registry32).

Note that

  • On a 64bit Windows, HKEY_LOCAL_MACHINE\Software\Wow6432Node contains values used by 32 bit applications running on the 64 bit system. Only true 64 bit applications store their values in HKEY_LOCAL_MACHINE\Software directly. The subtree Wow6432Node is entirely transparent for 32 bit applications, 32 bit applications still see HKEY_LOCAL_MACHINE\Software as they expect it (it is a kind of redirection). In older versions of Windows as well as 32 bit Windows 7 (and Vista 32 bit) the subtree Wow6432Node obviously does not exist.

  • Due to a bug in Windows 7 (64 bit), the 32 bit source code version always returns "Microsoft" regardless which organization you have registered while the 64 bit source code version returns the right organization.

Coming back to the example you've provided, do it the following way to access the 64 bit branch:

RegistryKey localKey = 
    RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine, 
        RegistryView.Registry64); 
RegistryKey sqlServerKey = localKey.OpenSubKey(
    @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL");
string sqlExpressKeyName = (string) sqlServerKey.GetValue("SQLEXPRESS");

Additional information - for practical use:

I'd like to add an interesting approach Johny Skovdal has suggested in the comments, which I've picked up to develop some useful functions by using his approach: In some situations you want to get back all keys regardless whether it is 32 bit or 64 bit. The SQL instance names are such an example. You can use a union query in that case as follows (C#6 or higher):

// using Microsoft.Win32;
public static IEnumerable<string> GetRegValueNames(RegistryView view, string regPath,
                                  RegistryHive hive = RegistryHive.LocalMachine) 
{ 
    return RegistryKey.OpenBaseKey(hive, view)
                     ?.OpenSubKey(regPath)?.G‌​etValueNames();
}

public static IEnumerable<string> GetAllRegValueNames(string RegPath,
                                  RegistryHive hive = RegistryHive.LocalMachine) 
{
    var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
    var reg32 = GetRegValueNames(RegistryView.Re‌​gistry32, RegPath, hive);
    var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
    return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
}

public static object GetRegValue(RegistryView view, string regPath, string ValueName="",
                                 RegistryHive hive = RegistryHive.LocalMachine)
{
    return RegistryKey.OpenBaseKey(hive, view)
                       ?.OpenSubKey(regPath)?.G‌​etValue(ValueName);
}

public static object GetRegValue(string RegPath, string ValueName="",
                                 RegistryHive hive = RegistryHive.LocalMachine)
{   
    return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive) 
                     ?? GetRegValue(RegistryView.Re‌​gistry32, RegPath, ValueName, hive);
}

public static IEnumerable<string> GetRegKeyNames(RegistryView view, string regPath,
                   RegistryHive hive = RegistryHive.LocalMachine)
{
    return RegistryKey.OpenBaseKey(hive, view)
        ?.OpenSubKey(regPath)?.GetSubKeyNames(); 
}

public static IEnumerable<string> GetAllRegKeyNames(string RegPath,
                                  RegistryHive hive = RegistryHive.LocalMachine)
{
    var reg64 = GetRegKeyNames(RegistryView.Registry64, RegPath, hive);
    var reg32 = GetRegKeyNames(RegistryView.Re‌​gistry32, RegPath, hive);
    var result = (reg64 != null && reg32 != null) ? reg64.Union(reg32) : (reg64 ?? reg32);
    return (result ?? new List<string>().AsEnumerable()).OrderBy(x => x);
}

Now you can simply use the functions above as follows:

Example 1: Get SQL instance names

var sqlRegPath=@"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
foreach (var valueName in GetAllRegValueNames(sqlRegPath))
{
    var value=GetRegValue(sqlRegPath, valueName);
    Console.WriteLine($"{valueName}={value}");
}

will give you a list of the value names and values in sqlRegPath.

Note: You can access the default value of a key (displayed by the commandline tool REGEDT32.EXE as (Default)) if you omit the ValueName parameter in the corresponding functions above.

To get a list of SubKeys within a registry key, use the function GetRegKeyNamesor GetAllRegKeyNames. You can use this list to traverse further keys in the registry.

Example 2: Get uninstall information of installed software

var currentVersionRegPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = $@"{currentVersionRegPath}\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);

will get all 32 bit and 64 bit uninstall keys.

Notice the null handling required in the functions because SQL server can be installed as 32 bit or as 64 bit (Example 1 above). The functions are overloaded so you can still pass the 32 bit or 64 bit parameter if required - however, if you omit it then it will try to read 64 bit, if that fails (null value), it reads the 32 bit values.

There is one speciality here: Because GetAllRegValueNames is usually used in a loop context (see Example 1 above), it returns an empty enumerable rather than null to simplify foreach loops: if it wouldn't be handled that way, the loop would have to be prefixed by an if statement checking for null which would be cumbersome having to do that - so that is dealt with once in the function.

Why bothering about null? Because if you don't care, you'll have a lot more headaches finding out why that null reference exception was thrown in your code - you'd spend a lot of time finding out where and why it happened. And if it happened in production you'll be very busy studying log files or event logs (I hope you have logging implemented) ... better avoid null issues where you can in a defensive way. The operators ?., ?[...] and ?? can help you a lot (see the code provided above). There is a nice related article discussing the new nullable reference types in C#, which I recommend to read and also this one about the Elvis operator (a nickname for the ?. operator, sometimes also called safe navigation operator).


Hint: You can use the free edition of Linqpad to test all examples under Windows. It doesn't require an installation. Don't forget to press F4 and enter Microsoft.Win32 in the Namespace import tab. In Visual Studio, you require using Microsoft.Win32; at the top of your code.

Tip: To familiarize yourself with the new null handling operators, try out (and debug) the following code in LinqPad:

Example 3: Demonstrating null handling operators

static string[] test { get { return null;} } // property used to return null
static void Main()
{
    test.Dump();                    // output: null
    // "elvis" operator:
    test?.Dump();                   // output: 
    // "elvis" operator for arrays
    test?[0].Dump();                // output: 
    (test?[0]).Dump();              // output: null
    // combined with null coalescing operator (brackets required):
    (test?[0]??"<null>").Dump();    // output: "<null>"
}

Try it with .Net fiddle

If you're interested, here are some examples I put together showing what else you can do with the tool.

Feed answered 5/11, 2012 at 12:42 Comment(11)
Thanks for that comprehensive answer. From memory I think I was using .NET 3.5 when I posted the question, but good to see .NET 4 has improved the situationGlutathione
You're welcome. I had a similar issue with the 64 bit registry recently which I had already solved so I thought it is worth sharing the solution.Feed
This is exactly what I've been looking for. I'm doing this in windows 9.1 and it works great.Separable
I wanted to test for the presence of keys, and from the example here, ended up with this approach: private static IEnumerable<string> GetIisComponents(RegistryView view) { return RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view).OpenSubKey(@"SOFTWARE\Microsoft\InetStp\Components").GetValueNames(); } and call it like this: GetIisComponents(RegistryView.Registry64).Union(GetIisComponents(RegistryView.Registry32)).ToArray(). This can of course be done with arbitrary keys, and this way I don't have to think about 32 or 64 bit again.Homunculus
@JohnySkovdal - Sure, if you just want to test if a key exists you can use a union query. A .Union is distinct per default, so you're getting each key only once. You could add .Any(...) to your LINQ query if you just need to check for existance of a particular key. But if you have to change a key's value then you need to consider 32bit or 64bit again ...Feed
Completely agree @Matt, just wanted to throw it in there, for those who didn't need the full amount of features, to reduce the complexity.Homunculus
@AZ_ - thank you for the edit, you're right, the key needs to be closed!Feed
@JohnySkovdal - I have changed the headline to make clear I am just providing additional (optional) information - for those who want to dig deeper into the matter.Feed
In my opinion, checking the registers like this: "SOFTWARE \ Microsoft \ Microsoft SQL Server \ Instance Names \ SQL" is wrong, because it happens that you uninstall the program and it will still be in the registers.Deter
@SilnyToJa - If your observed this behavior when uninstalling SQL server, then not the checking is wrong, the SQL uninstall mechanism has a bug. Report this as an issue to Microsoft. What you can do if you want to know reliably if an instance exists or not, check the file system additionally to verify if the SQL Server instance is still present in C:\Program Files\Microsoft SQL Server: For each instance there exists one subdirectory with the instance name.Feed
You are right that this is not a script error. At the bottom I added my solution.Deter
R
20

you have to use the KEY_WOW64_64KEY param when creating/opening the registry key. But AFAIK that's not possible with the Registry class but only when using the API directly.

This might help to get you started.

Rentfree answered 10/6, 2009 at 7:13 Comment(0)
T
6

I don't have enough rep to comment, but it's worth pointing out that it works when opening a remote registry using OpenRemoteBaseKey. Adding the RegistryView.Registry64 parameter allows a 32-bit program on Machine A to access the 64-bit registry on Machine B. Before I passed that parameter, my program was reading the 32-bit after OpenRemoteBaseKey, and did not find the key I was after.

Note: In my test, the remote machine was actually my machine, but I accessed it via OpenRemoteBaseKey, just as I would for a different machine.

Terrenceterrene answered 13/1, 2015 at 17:31 Comment(0)
J
4

try this (from a 32bit process):

> %WINDIR%\sysnative\reg.exe query ...

(found that here).

Jasik answered 10/5, 2010 at 10:38 Comment(1)
Nice hint, it allows to manipulate the registry in a batch. Use reg.exe /? to get more info...Feed
E
4

If you cannot use .NET 4 with its RegistryKey.OpenBaseKey(..., RegistryView.Registry64), you need to use Windows API directly.

The minimal interop is like:

internal enum RegistryFlags
{
    ...
    RegSz = 0x02,
    ...
    SubKeyWow6464Key = 0x00010000,
    ...
}

internal enum RegistryType
{
    RegNone = 0,
    ...
}

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern int RegGetValue(
    UIntPtr hkey, string lpSubKey, string lpValue, RegistryFlags dwFlags, 
    out RegistryType pdwType, IntPtr pvData, ref uint pcbData);

Use it like:

IntPtr data = IntPtr.Zero;
RegistryType type;
uint len = 0;
RegistryFlags flags = RegistryFlags.RegSz | RegistryFlags.SubKeyWow6464Key;
UIntPtr key = (UIntPtr)((uint)RegistryHive.LocalMachine);

const string subkey= @"SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL";
const string value = "SQLEXPRESS";

if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
{
    data = Marshal.AllocHGlobal((int)len);
    if (RegGetValue(key, subkey, value, flags, out type, data, ref len) == 0)
    {
        string sqlExpressKeyName = Marshal.PtrToStringUni(data);
    }
}
Edmon answered 27/4, 2016 at 9:42 Comment(0)
D
0

From what I have read and from my own tests, it seems to me that registry should be checked in this path "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall". Because in other paths the registers are not deleted after uninstalling the program.

In this way I got 64 registers with 32 bit configuration.

string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
if (key != null)
{
    var list = key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName).GetValue("DisplayName")).ToList();

    key.Close();
}

For 32 registers is:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
Deter answered 11/5, 2020 at 8:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.