由于WOW64是一个 Windows 子系统,可以从 32 位应用程序中访问 64 位,因此可以读取 64 位注册表。(同样,在旧的基于 NT 的 Windows 版本中,它被称为WOW,是 32 位 Windows 中的一个模拟层,用于支持 16 位应用程序)。
在 64 位 Windows 下使用.NET Framework 4.x和较新的 .NET 版本(例如 .NET Core、.NET 5 和 6)仍然提供对注册表访问的本机支持。以下代码在 Windows 7 64 位 和 Windows 10 64 位上进行了测试。它也应该适用于 Windows 11。
而不是 using "Wow6432Node"
,它通过将一个注册表树映射到另一个注册表树来模拟一个节点,使其虚拟地出现在那里,您可以执行以下操作:
决定是否需要访问 64 位或 32 位注册表,并按如下所述使用它。您还可以使用我稍后提到的代码(附加信息部分),它创建一个联合查询以在一个查询中从两个节点获取注册表项 - 因此您仍然可以使用它们的真实路径来查询它们。
64位注册表
要访问64 位注册表,可以使用RegistryView.Registry64
如下:
// 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位注册表
如果要访问32 位注册表,使用RegistryView.Registry32
如下:
// 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));
不要混淆,两个版本都用作第一个参数,您可以通过第二个参数(vs )Microsoft.Win32.RegistryHive.LocalMachine
来区分是使用64 位还是32 位。RegistryView.Registry64
RegistryView.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 位还是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)?.GetValueNames();
}
public static IEnumerable<string> GetAllRegValueNames(string RegPath,
RegistryHive hive = RegistryHive.LocalMachine)
{
var reg64 = GetRegValueNames(RegistryView.Registry64, RegPath, hive);
var reg32 = GetRegValueNames(RegistryView.Registry32, 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)?.GetValue(ValueName);
}
public static object GetRegValue(string RegPath, string ValueName="",
RegistryHive hive = RegistryHive.LocalMachine)
{
return GetRegValue(RegistryView.Registry64, RegPath, ValueName, hive)
?? GetRegValue(RegistryView.Registry32, 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.Registry32, 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
要获取注册表项中的子键列表,请使用函数GetRegKeyNames
或GetAllRegKeyNames
。您可以使用此列表来遍历注册表中的其他键。
示例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:演示 null 处理运算符
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 fiddle 试试
如果你有兴趣,这里有一些我放在一起的例子,展示了你可以用这个工具做些什么。