首页 文章

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

提问于
浏览
87

我有一个为AnyCPU编译的c#单元测试项目 . 我们的构建服务器是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外部的注册表?

5 回答

  • 125

    创建/打开注册表项时,必须使用KEY_WOW64_64KEY参数 . 但AFAIK对于Registry类是不可能的,但仅在直接使用API时才有效 .

    This可能有助于您入门 .

  • 18

    在使用 .NET Framework 4.x 的64位Windows下,仍然支持注册表访问 . 以下代码使用Windows 7,64位以及Windows 10,64位进行测试 . 要访问 64 bit registry ,您可以使用:

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

    如果要访问 32bit registry ,请使用:

    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 作为第一个参数,您可以区分 2nd parameter2nd parameterRegistryView.Registry64RegistryView.Registry32 )之间的 64 bit32 bit .

    Note 那个

    • 在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 显然确实存在 not .

    • 由于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");
    

    Additional information for practical use:

    我想在评论中添加一个有趣的方法Johny Skovdal,我已经选择通过使用他的方法开发一些有用的函数:在某些情况下,无论是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);
    }
    

    现在您可以简单地使用上面的函数,如下所示:

    Example 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中值名称和值的列表 .

    Note: 如果省略上述相应功能中的 ValueName 参数,则可以访问键的 default 值(由命令行工具 REGEDT32.EXE 显示为 (Default) ) .

    要在注册表项中获取 SubKeys 列表,请使用函数 GetRegKeyNamesGetAllRegKeyNames . 您可以使用此列表遍历注册表中的其他键 .

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

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

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

    函数中需要 Notice the null handling ,因为SQL Server可以安装为32位或64位(上面的示例1) . 函数被重载,因此如果需要,您仍然可以传递32位或64位参数 - 但是,如果省略它,它将尝试读取64位,如果失败(空值),它将读取32位值 .

    这里有一个特性:因为 GetAllRegValueNames 通常用在循环上下文中(参见上面的例1),它返回一个空的枚举而不是 null 来简化 foreach 循环:如果它不会被这样处理,循环将不得不以 if 语句为前缀检查 null ,这样做很麻烦 - 所以在函数中处理一次 .

    Why bothering about null? 因为如果你没有更多的麻烦找出为什么在你的代码中引发了空引用异常 - 你'd spend a lot of time finding out where and why it happened. And if it happened in production you'将非常忙于研究日志文件或事件日志(我希望你已经实现了日志记录)...更好的避免你可以采取防御方式的空问题 . 运营商 ?.?[ ... ]?? 可以提供帮助你很多(见上面提供的代码) . 有一篇很好的相关文章讨论了新的nullable reference types in C#,我建议阅读,以及this one关于Elvis算子 .


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

    Tip: 要熟悉新的 null handling operators, 试用(并调试)LinqPad中的以下代码:

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

    Try it with .Net fiddle

    如果您有兴趣, here 是我放在一起的一些示例,展示了您可以使用该工具做些什么 .

  • 5

    我没有足够的回复评论,但值得指出的是,它在使用OpenRemoteBaseKey打开远程注册表时有效 . 添加RegistryView.Registry64参数允许机器A上的32位程序访问机器B上的64位注册表 . 在我传递该参数之前,我的程序在OpenRemoteBaseKey之后读取了32位,并且没有找到密钥I之后 .

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

  • 4

    试试这个(来自32位进程):

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

    (发现here) .

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

相关问题