获取Windows序列号(原题:从注册表中获取MachineGuid)

13

我正在尝试从注册表中获取MachineGuid,以便为我的许可证系统创建一定程度的绑定。根据文档,我可以使用

string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography";
string r = (string)Registry.GetValue(key, "MachineGuid", (object)"default");

获取它。而且,文档告诉我当名称未找到时,我会得到"default",或者如果键不存在则为null。如果我没有访问权限,我应该会收到安全异常。

上述代码给了我"default",这意味着名称未被找到。但是,如果我使用RegEdit在注册表中查看,它是存在的。如何在没有管理员权限的情况下从应用程序中获取MachineGuid值?

更新:当使用reg.exe时,我可以轻松获取值。

更新:我已更新标题,以便寻找确定Windows安装的独特方法的人也可以找到此处。


2
似乎是注册表重定向问题:https://dev59.com/GlXTa4cB1Zd3GeqPyRiW。好像注册表已经够烦人的了。 - Bart Friederichs
1
你是否在64位机器上运行32位进程? - Steve
@Clemens 你使用的是哪个操作系统?如果和 OP 使用的版本不同,那么这个问题能够在你的电脑上运行而在他的电脑上不能运行很可能有一个很好的原因。 - RhysW
@BartFriederichs 你用的是什么操作系统?和Clemens一样吗? - RhysW
3个回答

22

正如其他人已经指出的那样,你不应该直接从注册表中获取该值(这可能就是为什么它在不同版本的Windows中不能可靠地工作的原因)。

简单搜索后,我找到了Win32_OperatingSystem WMI类。使用此类,您实际上可以获取Windows序列号。我进行了一些搜索和实验才弄清楚如何在C#中使用它。

确保在项目中引用了System.Management.dll

using System.Management;

...

ManagementObject os = new ManagementObject("Win32_OperatingSystem=@");
string serial = (string)os["SerialNumber"];

使用[]操作符,您可以获取类中的任何属性。

由于某种原因,此方法在Windows XP (.NET3.5)上崩溃。 - CodeArtist
如果我有像Ghost这样的克隆工具,每次部署此Windows Syspreped克隆映像时,这个序列号会改变吗?它是Windows副本的唯一标识符吗? - Ahmed Nazmy
1
经过更多的调查,Windows产品ID并不是每个Windows克隆版本都是唯一的。 - Ahmed Nazmy
4
你说“你不应该直接从注册表中获取该值”……是谁说的?.NET中有一整套用于处理注册表的类。 - Jonathan Alfaro
当您使用操作系统的GHOST映像时,此方法不正确,它永远不会给您唯一的操作系统序列号。 - Soni Vimalkumar
这在 Windows 10 中无法工作。串行字符串不是注册表编辑器中的 MachineGuid。 - Bobin

22
“这可能就是为什么它在不同版本的Windows之间不能可靠工作的原因。” 不是的。这个问题是由于您的EXE项目的平台目标选择引起的。点击项目->属性,在生成选项卡中,选择平台目标下拉框。您将其设置为x86而不是AnyCPU。在VS2012中,“优先32位”复选框很重要。此设置强制使您的程序在64位Windows上以32位模式运行。这有许多副作用,其中最重要的是访问注册表键被重定向。实际上,您的程序正在读取HKEY_LOCAL_MACHINE \ SOFTWARE \ Wow6432Node \ Microsoft \ Cryptography \ MachineGuid的值。它不存在。x86选择是VS2010及更高版本的默认选择,之前AnyCPU是默认选择。Microsoft更喜欢x86,Visual Studio与32位模式进程配合得更好。特别是在调试时,VS本身是一个32位进程,因此如果您的程序在64位模式下执行,则需要远程调试器。这有一些限制,例如不支持混合模式调试。只有32位代码才能使用编辑+继续功能。但是如果您将设置为AnyCPU,则您的程序本身将“更好”,包括不受内置于Windows中的文件系统和注册表重定向应用兼容性功能的影响。如果您真的被限制在x86模式下,通常是因为您依赖于无法更新的32位本地代码,则下一个解决方法是使用.NET 4+的RegistryKey.OpenBaseKey()方法。这允许您传递RegistryView.Registry64,确保您将读取非重定向的键。当然,使用WMI也是一种解决方法。只需记住,当您使用Win32_OperatingSystem.SerialNumber时,您不会读取相同的信息。在不同计算机上可靠地随机化该密钥的程度对我来说不太清楚,让我们说这个值对那些不太想为您的产品支付许可费用的用户来说是一个非常有吸引力的目标。

最后一点,需要考虑到生成自己独特的ID是相当容易的,完全不依赖于Windows。这有一个非常明显的优势:当客户更新他的机器上的Windows操作系统时,你就不会惹他不高兴了。只需使用Guid.NewGuid()一次,并将其值存储在文件中即可。尽管这个值会随着驱动器损坏而丢失,但通常会导致你的产品也失败。


感谢您的详细解释。我的许可代码使用两种独立的安全措施。首先,它是加密签名的,以防止篡改。因此,当有人只是将序列号放入许可文件中时,它不会被接受。其次,它(现在)使用Windows序列号来防止从一台计算机复制到另一台计算机。对于我们的用例(相当小众的产品),这应该足够了。 - Bart Friederichs
嗯,我确实试图指出不同的机器有不同的Windows序列号并不是完全保证的。也许你没有看到答案的结尾?但是,确实会有这样一种情况,你会对处理这个问题感到厌烦。然后你只需购买一个加密狗。 - Hans Passant
Windows序列号的差异不会是一个很大的问题。如果两个人碰巧拥有相同的序列号,并且彼此认识并愿意盗版一款工业级软件,那就这样吧。我的许可证系统只是为了防止基本的复制和许可文件编辑。(许可文件还包含一些使用限制。)在某些时候,花费不知道多少时间来保护软件可能并不值得,因为也许只有1或5%的客户愿意盗版。 - Bart Friederichs

13

依我卑微的意见,没有一个答案能够满足问题;问题非常直接,是在询问如何从注册表中读取MachineGuid...以下是我的回答:您需要添加对"Microsoft.Win32"的引用。该代码仅供演示目的编写,应相应地进行调整。

编辑:有人错误地表示x64代码无用。
在64位操作系统中,那里才是正确的键。

因此,这个答案是唯一满足问题的答案。

private void buttonGetMachineGuid_Click(object sender, RoutedEventArgs e)
{
  try
  {
    string x64Result = string.Empty;
    string x86Result = string.Empty;
    RegistryKey keyBaseX64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
    RegistryKey keyBaseX86 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
    RegistryKey keyX64 = keyBaseX64.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography", RegistryKeyPermissionCheck.ReadSubTree);
    RegistryKey keyX86 = keyBaseX86.OpenSubKey(@"SOFTWARE\Microsoft\Cryptography", RegistryKeyPermissionCheck.ReadSubTree);
    object resultObjX64 = keyX64.GetValue("MachineGuid", (object)"default");
    object resultObjX86 = keyX86.GetValue("MachineGuid", (object)"default");
    keyX64.Close();
    keyX86.Close();
    keyBaseX64.Close();
    keyBaseX86.Close();
    keyX64.Dispose();
    keyX86.Dispose();
    keyBaseX64.Dispose();
    keyBaseX86.Dispose();
    keyX64 = null;
    keyX86 = null;
    keyBaseX64 = null;
    keyBaseX86 = null;
    if(resultObjX64 != null && resultObjX64.ToString() != "default")
    {
      MessageBox.Show(resultObjX64.ToString());
    }
    if(resultObjX86 != null && resultObjX86.ToString() != "default")
    {
      MessageBox.Show(resultObjX86.ToString());
    }
  }
  catch(Exception)
  {
  }
}

希望这能帮助到某个人。


你不需要从Registry64读取,因为MachineGuid存在于仅32位注册表区域。 - Romil Kumar Jain
2
还要指出的是,其他答案没有示例代码;或者不是直接从注册表中获取。因此,在我看来,这是唯一符合手头问题的答案。 - Jonathan Alfaro
4
Romil错了,64位进程需要Registry64。我处于resultObjX86是默认值,而resultObjX64是正确的GUID字符串的情况下。 - Patrick from NDepend team
1
@TimSexton为什么不发表您自己的答案呢?您的编辑对原始代码进行了重大更改。 - jpaugh
1
谢谢@Darkonekt!你的回答非常到位! - Jordi Espada
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接