从32位应用程序读取64位注册表


98

我有一个为AnyCPU编译的ac#单元测试项目。我们的构建服务器是一台64位计算机,并安装了64位SQL Express实例。

测试项目使用与以下类似的代码来标识.MDF文件的路径:

    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();
    }

该代码在我们的32位工作站上可以正常工作,并且在构建服务器上也可以正常工作,直到我最近启用了NCover的代码覆盖率分析为止。因为NCover使用32位COM组件,所以测试运行程序(Gallio)作为32位进程运行。

检查注册表,下面没有“实例名称”键

HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Microsoft SQL Server

以32位模式运行的应用程序是否可以访问Wow6432Node外部的注册表?

Answers:


21

您必须在创建/打开注册表项时使用KEY_WOW64_64KEY参数。但是,只有在直接使用API​​时,Registry类才能实现AFAIK。

可能有助于您入门。


151

在使用.NET Framework 4.x的 64位Windows下,仍对注册表访问提供本机支持。以下代码已在   Windows 7(64位)   和   Windows 10(64位)上进行了测试

"Wow6432Node"您可以执行以下操作,而不是使用通过将一个注册表树映射到另一个注册表树以使其虚拟地出现而模拟一个节点,而是执行以下操作:

确定是否需要访问64位或32位注册表,并按如下所述使用它。您还可以使用我稍后提到的代码(其他信息部分),该代码创建一个联合查询以在一个查询中从两个节点获取注册表项-因此您仍然可以使用它们的真实路径来查询它们。

64位注册表

要访问64位注册表,可以使用RegistryView.Registry64以下方法:

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位注册表

如果要访问32位注册表,请RegistryView.Registry32按以下方式使用:

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));

请勿混淆,两个版本都Microsoft.Win32.RegistryHive.LocalMachine用作第一个参数,您通过第二个参数(vs )来区分使用64位还是32位RegistryView.Registry64RegistryView.Registry32

注意的是

  • 在64位Windows上,HKEY_LOCAL_MACHINE\Software\Wow6432Node包含在64位系统上运行的32位应用程序使用的值。只有真正的64位应用程序才能HKEY_LOCAL_MACHINE\Software直接将其值存储在其中。子树Wow6432Node对于32位应用程序是完全透明的,但32位应用程序仍可以HKEY_LOCAL_MACHINE\Software按他们的期望进行查看(这是一种重定向)。在旧版本的Windows和32位Windows 7(和Vista 32位)的子树Wow6432Node显然是存在的。

  • 由于Windows 7(64位)中的错误,无论您注册了哪个组织,32位源代码版本始终返回“ Microsoft”,而64位源代码版本返回正确的组织。

回到您提供的示例,以以下方式访问64位分支:

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");

附加信息-实用:

我想添加一种有趣的方法,Johny Skovdal在评论中建议了该方法,我通过他的方法选择了该方法来开发一些有用的功能:在某些情况下,无论32位还是32位,您都希望获取所有密钥。 64位。SQL实例名称就是这样的示例。在这种情况下,您可以按以下方式使用联合查询(C#6或更高版本):

// 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);
}

现在,您可以简单地使用上述功能,如下所示:

示例1:获取SQL实例名称

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}");
}

将为您提供sqlRegPath中的值名称和值的列表。

注意:如果您在上述相应功能中省略了参数,则可以访问键的默认值(由命令行工具显示REGEDT32.EXE(Default)ValueName

为了得到列表的子项注册表项中,使用功能GetRegKeyNamesGetAllRegKeyNames。您可以使用此列表遍历注册表中的其他项。

示例2:获取已安装软件的卸载信息

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

将获得所有32位和64位卸载密钥。

请注意函数中所需的空处理,因为SQL Server可以安装为32位或64位(上面的示例1)。这些函数已重载,因此您仍然可以根据需要传递32位或64位参数-但是,如果省略它,则它将尝试读取64位,如果失败(空值),它将读取32位值。

这里有一个特长:因为GetAllRegValueNames通常在循环上下文中使用(请参见上面的示例1),所以它返回一个空的可枚举而不是null简化foreach循环:如果不这样处理,则循环必须以前缀一条if语句,检查该语句是否null很麻烦,因此必须在函数中处理一次。

为什么要烦恼null?因为如果您不在乎,那么找出为什么在您的代码中引发空引用异常的麻烦就更大了,您将花费大量的时间来找出它发生的位置和原因。而且,如果它在生产中发生,您将非常忙于研究日志文件或事件日志(我希望您已实现日志记录)...最好避免以防御性方式解决空问题。操作员?.?[... ]??可以为您提供很多帮助(请参阅上面提供的代码)。有一个很好的相关文章讨论新的C#可空引用类型,我建议阅读,也这一个关于Elvis操作符。


提示:您可以使用免费版的Linqpad在Windows下测试所有示例。不需要安装。不要忘记按下F4并输入Microsoft.Win32“命名空间导入”选项卡。在Visual Studio中,您需要using Microsoft.Win32;在代码的顶部。

提示:要熟悉新的null处理运算符,请在LinqPad中尝试(和调试)以下代码:

示例3:演示空处理运算符

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>"
}

用.Net小提琴试试

如果你有兴趣,这里有一些例子我放在一起展示你可以用工具做什么。


2
感谢您的全面答复。从内存中,我认为我在发布问题时正在使用.NET 3.5,但很高兴看到.NET 4改善了这种情况
David Gardiner 2012年

2
别客气。最近我已经解决了64位注册表的类似问题,因此我认为值得分享该解决方案。
马特

2
这正是我一直在寻找的东西。我在Windows 9.1中执行此操作,效果很好。
米歇尔·布格

1
@AZ_-谢谢您的编辑,是的,需要关闭密钥!
马特

1
@JohnySkovdal-我已更改标题,以表明我只是在提供其他(可选)信息,以供那些希望深入研究此问题的人使用。
马特

6

我没有足够的代表对此发表评论,但是值得注意的是,使用OpenRemoteBaseKey打开远程注册表时,它可以工作。添加RegistryView.Registry64参数允许计算机A上的32位程序访问计算机B上的64位注册表。在传递该参数之前,我的程序在OpenRemoteBaseKey之后读取了32位,并且找不到键I是之后。

注意:在我的测试中,远程计算机实际上是我的计算机,但是我通过OpenRemoteBaseKey访问它,就像在另一台计算机上一样。


4

试试看(从32位进程):

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

(在这里找到)。


1
很好的提示,它允许批量操作注册表。使用reg.exe /?以获取更多信息...
Matt

4

如果您不能将.NET 4与它RegistryKey.OpenBaseKey(..., RegistryView.Registry64)一起使用,则需要直接使用Windows API。

最小互操作类似:

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);

像这样使用它:

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);
    }
}

0

从我已阅读的内容和我自己的测试中,我看来应该在此路径“ SOFTWARE \ Microsoft \ Windows \ CurrentVersion \ Uninstall”中检查注册表。因为在其他路径中,卸载程序后不会删除寄存器。

这样,我得到了32位配置的64个寄存器。

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();
}

对于32个寄存器是:

registryKey = @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.