13

I am trying to fetch MachineGuid from the registry, to create some level of binding with the OS for my license system. From the documentation I can use

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

to get it. Also, the docs tell me that I get "default" when the name is not found, or null if the key doesn't exist. I should get a security exception if I have no access.

The above code gives me "default", which means the name isn't found. However, if I look in the registry with RegEdit, it's there. How do I get the MachineGuid value from an application without administrator privileges?

Update: when using reg.exe I have no problems getting the value.

Update: I updated the title, so people looking for a unique way of determining the Windows install get here as well.

Bart Friederichs
  • 33,050
  • 15
  • 95
  • 195
  • 2
    It seems to be a Registry Redirection issue: http://stackoverflow.com/questions/5262830/whats-wrong-with-registry-getvalue. As if the registry isn't broken enough already. – Bart Friederichs Jan 08 '13 at 10:49
  • 1
    Are you running a 32bit process on a 64 bit machine? – Steve Jan 08 '13 at 10:49
  • @Clemens which OS are you using? if its different version to the OP there could well be a good reason for this working for you and not him – RhysW Jan 08 '13 at 10:51
  • @BartFriederichs what OS are you using? was it the same as Clemens? – RhysW Jan 08 '13 at 10:56

3 Answers3

22

As other people have already pointed out, you are not supposed to get that value directly from the registry (which is probably why it doesn't work reliably among different versions of Windows).

A little searching led me to the Win32_OperatingSystem WMI class. Using this class, you can actually get the Windows serial number. It took me some searching and experimenting to get it right, but this is how to use it in C#.

Make sure you have the System.Management.dll reference in your project:

using System.Management;

...

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

Using the [] operator, you can get any property in the class.

Bart Friederichs
  • 33,050
  • 15
  • 95
  • 195
22

which is probably why it doesn't work reliably among different versions of Windows

No, that's not the reason. This problem is caused by the platform target selection for your EXE project. Project + Properties, Build tab, Platform target combobox. You have it set to x86 instead of AnyCPU. On VS2012, the "Prefer 32-bit" checkbox matters. This setting forces your program to run in 32-bit mode on a 64-bit version of Windows. Which has a number of side effects, the one that matters here is that access to registry keys are redirected. Your program is actually reading the value of HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Cryptography\MachineGuid. Which doesn't exist.

The x86 selection is the default for VS2010 and up, previously AnyCPU was the default. Microsoft prefers x86, Visual Studio works better with 32-bit mode processes. Particularly when debugging, VS is a 32-bit process itself so requires the remote debugger if your program executes in 64-bit mode. Which has a few limitations like not supported mixed-mode debugging. And the Edit + Continue feature only works for 32-bit code. Your program itself however works "better" if you have the setting at AnyCPU, including not getting bitten by the file system and registry redirection appcompat features built into Windows.

If you are really stuck with x86 mode, typically because you have a dependency on 32-bit native code that you can't update then the next workaround is to use the .NET 4+ RegistryKey.OpenBaseKey() method. Which allows you to pass RegistryView.Registry64, ensuring that you'll read the non-redirected keys.

Sure, using WMI is a workaround. Just keep in mind that you are not reading the same information when you use Win32_OperatingSystem.SerialNumber. To what degree that key is reliably random on different machines isn't that clear to me, let's just say that this value is a pretty attractive target for the kind of users that are not very interested in paying the license fee for your product either.

Last but not least, do consider that it is pretty easy to generate your own unique id that doesn't depend at all on Windows. With the considerable advantage that you won't piss off your customer when he updates Windows on his machine. Just use Guid.NewGuid() once and store the value in a file. That will be lost when the drive goes bad, but that usually takes out your product as well.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Thanks for the lengthy explanation. My license code uses two separate security measures. First it is cryptographically signed to prevent tampering with it. So, when somebody just puts the serialnumber in the license file, it is not accepted. Second, it (now) uses the Windows serial number to prevent copying from one computer to the other. For our use case (quite a niche product) that's hopefully enough. – Bart Friederichs Jan 08 '13 at 14:01
  • Well, I did try to point out that different machines have different Windows serial numbers isn't exactly guaranteed. Maybe you didn't make it to the end of the answer? But yeah, there does come a point where you get fed up with wanting to deal with this. And then you just buy a dongle. – Hans Passant Jan 08 '13 at 14:08
  • The difference in Windows serial numbers won't be a huge problem. If two people happen to have the same serial, **and** know eachother **and** are willing to pirate a piece of industry-grade software, then so be it. My license system is just to thwart the basic copying and license file editing. (The license file also holds some usage limits.) At some point it's just not worth it to spend i-don't-know-how-much-time in protecting the software, where maybe 1 or 5% of the clients are willing to pirate it. – Bart Friederichs Jan 08 '13 at 14:12
13

I my humble opinion none of the answers satisfies the question; is pretty straight forward asking for a way to read the MachineGuid from the registry... so here is my answer: You will need to add a reference to "Microsoft.Win32". This code was written for demonstration purposes and should be adapted accordingly.

EDIT: Someone stated wrongly that the x64 code is useless.
In 64 bit OS that is where the correct key is found.

So this answer stands as the only one that satisfies the question.

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

Hope this helps some one.

Jonathan Alfaro
  • 4,013
  • 3
  • 29
  • 32
  • you don't need to read from Registry64, because MachineGuid exist in only 32 bit registry area. – Romil Kumar Jain Apr 22 '15 at 13:16
  • It is just demo code so that anyone can see what is being outputed in both the 32 and 64 bit. Thats why I stated it needs to be adapted accordingly. But thanks for pointing that out. – Jonathan Alfaro Oct 26 '15 at 01:36
  • 2
    Also just to point out that the other answers do not have example code; or are not getting it directly from the registry. So in my opinion this is the only answer that conforms to the question at hand. – Jonathan Alfaro Oct 26 '15 at 01:38
  • I agree with your points. but my comment will help to get specific information in regards to question. because poist is lokking for "MachineGuid" from registry. – Romil Kumar Jain Oct 26 '15 at 07:08
  • 4
    Romil is wrong, Registry64 is needed for 64bits process. I am in the situation where resultObjX86 is default, and resultObjX64 is the right GUID string. – Patrick from NDepend team Nov 24 '15 at 14:47
  • You don't need all those `.Dispose()` calls and `variable = null` declarations. You should let the C# garbage collector manage cleaning up these objects. – wooobie May 11 '17 at 19:38
  • pyskell...... not sure what you mean.... you should call dispose on any disposable object or use "using" statements. – Jonathan Alfaro Oct 01 '17 at 01:16
  • 1
    @TimSexton Why not post your own answer? Your edit is a drastic change from the original code. – jpaugh Jan 23 '18 at 23:48
  • 1
    Thanks @Darkonekt ! you've hit the nail on the head with this answer! – Jordi Espada Aug 05 '19 at 11:24