11

I've been using the value of key MachineGuid from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography to uniquely identify hosts, but from 32-bit processes running on 64-bit computers, the value appears to be missing. I guess it's searching under Wow6432Node, where it is indeed missing. According to this you should be able to get to the right key by adding a flag, but below code still doesn't appear to do the job. What am I missing?

const
  KEY_WOW64_64KEY=$0100;
var
  r:HKEY;
  s:string;
  i,l:integer;
begin
  //use cryptography machineguid, keep a local copy of this in initialization?
  l:=40;
  if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar('Software\Microsoft\Cryptography'),
    0,KEY_QUERY_VALUE,r)=ERROR_SUCCESS then
   begin
    SetLength(s,l);
    if RegQueryValue(r,'MachineGuid',PChar(s),l)=ERROR_SUCCESS then
     begin
      SetLength(s,l);
      RegCloseKey(r);
     end
    else
     begin
      //try from-32-to-64
      RegCloseKey(r);
      if RegOpenKeyEx(HKEY_LOCAL_MACHINE,PChar('Software\Microsoft\Cryptography'),
        0,KEY_QUERY_VALUE or KEY_WOW64_64KEY,r)=ERROR_SUCCESS then
       begin
        l:=40;
        if RegQueryValue(r,'MachineGuid',PChar(s),l)=ERROR_SUCCESS then
          SetLength(s,l)
        else
          l:=0;
        RegCloseKey(r);
       end;
     end;
   end;
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Stijn Sanders
  • 35,982
  • 11
  • 45
  • 67
  • 5
    Why don't you use TRegistry? Also your logic is a real mess. You want a single function to call to read the value. Call it twice. First time pass 0. Second time pass KEY_WOW64_64KEY. Only call second time if first time fails. This is extract method refactoring. – David Heffernan Feb 02 '12 at 22:59
  • I used TRegistry before, but didn't know you cound add KEY_WOW64_64KEY via the constructor with parameter. – Stijn Sanders Feb 03 '12 at 10:02
  • 1
    Sure it does. See my answer. EDIT: Looks like you found my answer now. Also note that you can modify the `Access` property at any time to switch views within an already created registry object. – David Heffernan Feb 03 '12 at 10:07

4 Answers4

11

I would suggest you use the IsWow64Process() function to know when you are a 32-process running on a 64-bit OS, and then only apply the KEY_WOW64_64KEY flags in that specific condition. If the app is a 32-bit process on a 32-bit OS, or a 64-bit process on a 64-bit OS, the flags is not needed.

For example:

const 
  KEY_WOW64_64KEY = $0100; 
var 
  key: HKEY; 
  str: string; 
  len: DWORD; 
  flag: REGSAM;
  wow64: BOOL;
begin 
  flag := 0;
  wow64 := 0;
  IsWow64Process(GetCurrentProcess(), @wow64);
  if wow64 <> 0 then flag := KEY_WOW64_64KEY;

  if RegOpenKeyEx(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Cryptography', 0, KEY_QUERY_VALUE or flag, key) = ERROR_SUCCESS then 
  try
    SetLength(str, 40); 
    len := Length(str) * SizeOf(Char); 
    if RegQueryValueEx(key, 'MachineGuid', nil, nil, PByte(Pointer(s)), @len) <> ERROR_SUCCESS then len := 0;
    SetLength(str, len div SizeOf(Char)); 
  finally
    RegCloseKey(key); 
  end; 
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 3
    I think you can simply always apply the flag, it is ignored on x86 os – Remko Feb 03 '12 at 11:49
  • 1
    Exactly. If you always want to read from the native view, you can use `KEY_WOW64_64KEY` unconditionally – David Heffernan May 22 '14 at 15:25
  • 2
    Only on XP and later, which recognize the existence of 64bit systems, even in the 32bit versions. If you specify the flag on Win2k or earlier, it will fail as an unknown parameter. On those systems, `IsWow64Process()` needs to be dynamically loaded anyway to detect if WOW64 is even present. – Remy Lebeau May 22 '14 at 15:46
8

Your code is needlessly complex, largely because you are not taking advantage of the built-in TRegistry class which shields you from all the complexities of the low-level registry API. For example, consider the following code:

type
  TRegistryView = (rvDefault, rvRegistry64, rvRegistry32);

function RegistryViewAccessFlag(View: TRegistryView): LongWord;
begin
  case View of
  rvDefault:
    Result := 0;
  rvRegistry64:
    Result := KEY_WOW64_64KEY;
  rvRegistry32:
    Result := KEY_WOW64_32KEY;
  end;
end;

function ReadRegStr(const Root: HKEY; const Key, Name: string;
  const View: TRegistryView=rvDefault): string;
var
  Registry: TRegistry;
begin
  Registry := TRegistry.Create(KEY_READ or RegistryViewAccessFlag(View));
  try
    Registry.RootKey := Root;
    if not Registry.OpenKey(Key) then
      raise ERegistryException.CreateFmt('Key not found: %s', [Key]);
    if not Registry.ValueExists(Name) then
      raise ERegistryException.CreateFmt('Name not found: %s\%s', [Key, Name]);
    Result := Registry.ReadString(Name);//will raise exception in case of failure
  finally
    Registry.Free;
  end;
end;

The function ReadRegStr will return the string value named Name from the key Key relative to the root key Root. If there is an error, for example if the key or name do not exists, or if the value is of the wrong type, then an exception will be raised.

The View parameter is an enumeration that makes it simple for you to access native, 32-bit or 64-bit views of the registry. Note that native means native to the process that is running. So it will be the 32-bit view for a 32-bit process and the 64-bit view for a 64-bit process. This enumeration mirrors the equivalent definition in .net.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • nope still doesn't work, ReadString gives empty string (could it my a problem witht his laptop?): const KEY_WOW64_64KEY=$0100; var r:TRegistry; s:string; begin r:=TRegistry.Create(KEY_READ or KEY_WOW64_64KEY); r.RootKey:=HKEY_LOCAL_MACHINE; if r.OpenKeyReadOnly('Software\Microsoft\Cryptography') then s:=r.ReadString('MachineGuid'); – Stijn Sanders Feb 03 '12 at 18:08
  • There was a bug in my code which I just fixed re. RootKey. But you already got that. Your code should work. Is that key there?! I'll check on my machine in an hour or so. Dinner time here!! – David Heffernan Feb 03 '12 at 18:27
  • I've tested the code. There was another bug which I fixed because I didn't understand quite how `TRegistry` works when a value is not present. But if you are seeing an empty string then there is something wrong. Your code (and my code) return the correct value when I run them. I do wonder what version of Delphi you are on. Perhaps you are on an old Delphi for which `TRegistry` doesn't respect `KEY_WOW64_64KEY`. That would surprise me though. I am a bit suspicious of this since you are defining `KEY_WOW64_64KEY` but I am just getting it from Windows.pas. So, what Delphi are you using? – David Heffernan Feb 03 '12 at 19:31
  • Delphi 7, I checked Registry.pas, and the FAccess value is properly passed for samDesired parameter in the OpenKey method, but overwritten in the OpenKeyReadOnly method! So if you change... Strange, I can change you're answer form OpenKeyReadOnly to OpenKey, guess I have enough rep. – Stijn Sanders Feb 03 '12 at 21:18
  • 1
    Accepted! In case you're interested: https://github.com/stijnsanders/TMongoWire/commit/e65a3af4c35948f5eb2a2cf311c1e3febd95b05d – Stijn Sanders Feb 03 '12 at 21:22
  • @StijnSanders Excellent work. Well done and thanks for teaching me something new. – David Heffernan Feb 03 '12 at 21:28
4

In my use of this registry key I went a step further. If the value didn't exist I created it: not in HKEY_LOCAL_MACHINE, that would require elevation, but in HKEY_CURRENT_USER. Anyone seeing the introduced key there is unlikely to realise that it's a dummy.

function GetComputerGUID: String;
var
  Reg: TRegistry;
  oGuid: TGUID;
  sGuid: String;
begin
  Result := '';
  // Attempt to retrieve the real key
  Reg := TRegistry.Create(KEY_READ OR KEY_WOW64_64KEY);
  try
    Reg.RootKey := HKEY_LOCAL_MACHINE;
    if Reg.OpenKeyReadOnly('SOFTWARE\Microsoft\Cryptography') and Reg.ValueExists('MachineGuid') then
      Result := Reg.ReadString('MachineGuid');
    Reg.CloseKey;
  finally
    Reg.Free;
  end;
  // If retrieval fails, look for the surrogate
  if Result = '' then begin
    Reg := TRegistry.Create;
    try
      Reg.RootKey := HKEY_CURRENT_USER;
      if Reg.OpenKey('SOFTWARE\Microsoft\Cryptography', True) then begin
        if Reg.ValueExists('MachineGuid') then
          Result := Reg.ReadString('MachineGuid')
        else begin
          // If the surrogate doesn't exist, create it
          if CreateGUID(oGUID) = 0 then begin
            sGuid := Lowercase(GUIDToString(oGUID));
            Reg.WriteString('MachineGuid', Copy(sGuid, 2, Length(sGUID) - 2));
            Result := Reg.ReadString('MachineGuid');
          end;
        end;
      end;
      Reg.CloseKey;
    finally
      Reg.Free;
    end;
  end;
  if Result = '' then
    raise Exception.Create('Unable to access registry value in GetComputerGUID');
end;

That's a good point from @Remy Lebeau - TeamB though; I should mod the above code appropriately.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
SourceMaid
  • 473
  • 3
  • 6
  • You don't need to check `ValueExists()` before calling `ReadString()`. It returns an empty string if the value does not exist, it does not raise an exception like the other reading methods do. – Remy Lebeau May 22 '14 at 15:49
1

Call reg.exe using this path C:\Windows\sysnative\reg.exe For example:

C:\Windows\sysnative\reg.exe QUERY "HKLM\SOFTWARE\JavaSoft\JDK" /v CurrentVersion

source: https://stackoverflow.com/a/25103599

JohnP2
  • 1,899
  • 19
  • 17