I've been using the below code in order to get the Windows License Key. It worked pretty well a long time. But now I discovered that it works on Windows XP (x86) but not on Windows 7 x64.
Reason: The DigitalProductID regisitry value contains only zeroes within the range we are looking for on the 64 bit operating system. Therefore the result it BBBBB-BBBBB-BBBBB-BBBBB-BBBBB. Why is it so and how can I fix this?
public static string LicenseCDKey
{
get
{
try
{
byte[] rpk = (byte[])Registry.LocalMachine
.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion")
.GetValue("DigitalProductId");
string serial = "";
const string possible = "BCDFGHJKMPQRTVWXY2346789";
for (int i = 0; i < 25; i++)
{
int accu = 0;
for (int a = 0; a < 15; a++)
{
accu <<= 8;
accu += rpk[66 - a];
rpk[66 - a] = (byte)(accu / 24 & 0xff);
accu %= 24;
}
serial = possible[accu] + serial;
if (i % 5 == 4 && i < 24)
{
serial = "-" + serial;
}
}
return serial;
}
catch
{
return ErrorString;
}
}
}
As user287107 pointed out x86 applications (32 bit) running on a x64 operating system are using a different registry (registry view).
In order to access the x64 registry you have a few options:
Change your platform target to x64 (Visual Studio project settings).
If you are using .Net Framework 4.0 you could use the RegistryKey class and RegistryView enum to access the x64 registry.
RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine,
RegistryView.Registry64);
string keyPath = #"Software\Microsoft\Windows NT\CurrentVersion";
byte[] rpk = (byte[])key.OpenSubKey(keyPath).GetValue("DigitalProductId");
If you are not using the .Net Framework 4.0 and you do not want to set your platform target to x64 you have to use Interop (RegOpenKeyEx() Win32 API function with the KEY_WOW64_32KEY flag) to access the x64 registry.
BEGIN EDIT
I've just found an interesting post explaining why the DigitialProductId key could be null/empty:
You are using an Volume License Key to activate your Windows 7 operating system. The VLC key is deleted from the registry after activation.
Someone deleted the registry key (modified the content of this key) manually using the command slmgr –cpky
END EDIT
32 bit applications use a different registry path
a 32 bit application accesses the registry path in
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion
where it does not find the product key.
changing the processor type to x64 worked for me to get the real key.
Related
I Use C#. I try to get the current version of the OS:
OperatingSystem os = Environment.OSVersion;
Version ver = os.Version;
I get on the Windows 10: 6.2.
But 6.2 is Windows 8 or WindowsServer 2012 (Detect Windows version in .net)
I found the following solution (How can I detect if my app is running on Windows 10).
static bool IsWindows10()
{
var reg = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
string productName = (string)reg.GetValue("ProductName");
return productName.StartsWith("Windows 10");
}
This is the best way to get the current version in C#?
Add application manifest to your application and add the supportedOS Id of Windows 8.1 and Windows 10 to the manifest:
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
</application>
</compatibility>
Now Environment.OSVersion includes the correct data for Windows 8.1 and Windows 10 and not 6.2 to indicate you run Windows 8. This is a change since Windows 8.1.
Here is a link from Microsoft offical, indicating how to get the System Version. It actually is a call to the Version API Helper Functions
So basically you must convert this code into C# because it's in C++, then keep only the Windows 10 part...
#include <windows.h>
#include <stdio.h>
#include <VersionHelpers.h>
int
__cdecl
wmain(
__in int argc,
__in_ecount(argc) PCWSTR argv[]
)
{
UNREFERENCED_PARAMETER(argc);
UNREFERENCED_PARAMETER(argv);
if (IsWindows10OrGreater())
{
printf("Windows10OrGreater\n");
}
}
And if you like trying to read code, you can check out this one link. This DLL can be used to get information on the OS...
I have created this simple method in C# and it has worked for me.
public static string GetWindowsVersion()
{
string registryPath = "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion";
string build = null;
int number = 0;
try
{
build = Registry.GetValue(registryPath, "CurrentBuild", null).ToString();
}
catch { return null; }
number = Int32.Parse(build);
if (number == 7601)
return "Windows 7";
else if (number == 9200)
return "Windows 8";
else if (number == 9600)
return "Windows 8.1";
else if (number >= 10240 && number <= 19045)
return "Windows 10";
else if (number >= 22000)
return "Windows 11";
else
return "Older version";
/* Go here to find more build numbers and evaluate more conditions
*
* https://en.wikipedia.org/wiki/List_of_Microsoft_Windows_versions
*
*/
}
I am going crazy with an issue with LABEL of mapping a drive to windows with windows 7 OS. Scenario;
We need to map the drive as soon as user logs in to the machine. That seems to be working fine with other os versions except windows 7. Steps for windows 7;
EXE (c# made by us) launched in windows 7
EXE has mapped drive correctly
User logged out
Logged in back
Again exe is trying to map drive (Its registered as a startup exe)
The mapped drive label becomes "Network Drive" (Not sure how)
We are setting the proper values in registry as well as shown in below figure;
Problem is here;
Issue is only occurring when we do logout and login. If we manually launch exe, it works fine...
I have also tried to DELETE all these registry before mapping driving assuming it might be cache or something but nothing helped..
We are using zMapDrive to map a drive;
//create struct data
structNetResource stNetRes = new structNetResource();
stNetRes.iScope = 2;
stNetRes.iType = RESOURCETYPE_DISK;
stNetRes.iDisplayType = 3;
stNetRes.iUsage = 1;
stNetRes.sRemoteName = ls_ShareName;
stNetRes.sLocalName = ls_Drive;
//prepare params
int iFlags = 0;
if (lf_SaveCredentials) { iFlags += CONNECT_CMD_SAVECRED; }
if (lf_Persistent) { iFlags += CONNECT_UPDATE_PROFILE; }
if (ls_PromptForCredentials) { iFlags += CONNECT_INTERACTIVE + CONNECT_PROMPT; }
if (psUsername == "") { psUsername = null; }
if (psPassword == "") { psPassword = null; }
//if force, unmap ready for new connection
if (lf_Force) { try { zUnMapDrive(true); } catch { } }
//call and return
int i = WNetAddConnection2A(ref stNetRes, psPassword, psUsername, iFlags);
if (i > 0) { throw new System.ComponentModel.Win32Exception(i); }
Maybe a simple powershell script, renaming network drive will work for you? You can then use Task Scheduler to run it every time a user logs in.
$Rename = New-Object -ComObject Shell.Application
$Net = New-Object -ComObject WScript.Network
# map the drive if the path doesn't exist
If (!(Test-Path Z:))
{
$Net.MapNetworkDrive("Z:", '\\SERVER_ADDRESS\Directory', $false, "user", "password")
}
# change the drive name
$Rename.NameSpace("Z:\").Self.Name = "MyNetDriveLabel"
From my experience, support for network mapped drives is somewhat buggy in Windows 7, so I use similar workaround on a few of our Win7 machines.
This question already has answers here:
Detect Windows version in .NET
(18 answers)
Closed 6 years ago.
I want to know which Windows version the PC has.. in C# Framework 3.5
I have tried using
OperatingSystem os = Environment.OSVersion;
Version ver = os.Version;
But the result is
Plataform: WIN32NT
version 6.2.9200
Version minor: 2
Version Major: 6
The problem is that I have "Windows 8 Pro"...
How can I detect it?
Thanks
You will have to match version numbers with the appropriate string value yourself.
Here is a list of the most recent Windows OS and their corresponding version number:
Windows Server 2016 & 2019 - 10.0*
Windows 10 - 10.0*
Windows 8.1 - 6.3*
Windows Server 2012 R2 - 6.3*
Windows 8 - 6.2
Windows Server 2012 - 6.2
Windows 7 - 6.1
Windows Server 2008 R2 - 6.1
Windows Server 2008 - 6.0
Windows Vista - 6.0
Windows Server 2003 R2 - 5.2
Windows Server 2003 - 5.2
Windows XP 64-Bit Edition - 5.2
Windows XP - 5.1
Windows 2000 - 5.0
*For applications that have been manifested for Windows 8.1 or 10. Applications not manifested for 8.1 / 10 will return the Windows 8 OS version value (6.2).
Here's the source.
Also, from the same source:
Identifying the current operating system is usually not the best way
to determine whether a particular operating system feature is present.
This is because the operating system may have had new features added
in a redistributable DLL. Rather than using the Version API Helper
functions to determine the operating system platform or version
number, test for the presence of the feature itself.
In my scenario I needed my application to capture computer info for possible bug-reports and statistics.
I did not find the solutions where an application manifest had to be added satisfactory. Most of the suggestions I found while googling this suggested just that, unfortunately.
Thing is, when using a manifest, each OS version has to be added manually to it in order for that particular OS version to be able to report itself at runtime.
In other words, this becomes a race condition: A user of my app may very well be using a version of my app that pre-dates the OS in use. I would have to upgrade the app immediately when a new OS version was launched by Microsoft. I would also have to force the users to upgrade the app at the same time as they updated the OS.
In other words, not very feasible.
After browsing through the options I found some references (surprisingly few compared to the app manifest) that instead suggested using registry lookups.
My (chopped down) ComputerInfo class with only WinMajorVersion, WinMinorVersion and IsServer properties looks like this:
using Microsoft.Win32;
namespace Inspection
{
/// <summary>
/// Static class that adds convenient methods for getting information on the running computers basic hardware and os setup.
/// </summary>
public static class ComputerInfo
{
/// <summary>
/// Returns the Windows major version number for this computer.
/// </summary>
public static uint WinMajorVersion
{
get
{
dynamic major;
// The 'CurrentMajorVersionNumber' string value in the CurrentVersion key is new for Windows 10,
// and will most likely (hopefully) be there for some time before MS decides to change this - again...
if (TryGeRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMajorVersionNumber", out major))
{
return (uint) major;
}
// When the 'CurrentMajorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'
dynamic version;
if (!TryGeRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", out version))
return 0;
var versionParts = ((string) version).Split('.');
if (versionParts.Length != 2) return 0;
uint majorAsUInt;
return uint.TryParse(versionParts[0], out majorAsUInt) ? majorAsUInt : 0;
}
}
/// <summary>
/// Returns the Windows minor version number for this computer.
/// </summary>
public static uint WinMinorVersion
{
get
{
dynamic minor;
// The 'CurrentMinorVersionNumber' string value in the CurrentVersion key is new for Windows 10,
// and will most likely (hopefully) be there for some time before MS decides to change this - again...
if (TryGeRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentMinorVersionNumber",
out minor))
{
return (uint) minor;
}
// When the 'CurrentMinorVersionNumber' value is not present we fallback to reading the previous key used for this: 'CurrentVersion'
dynamic version;
if (!TryGeRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CurrentVersion", out version))
return 0;
var versionParts = ((string) version).Split('.');
if (versionParts.Length != 2) return 0;
uint minorAsUInt;
return uint.TryParse(versionParts[1], out minorAsUInt) ? minorAsUInt : 0;
}
}
/// <summary>
/// Returns whether or not the current computer is a server or not.
/// </summary>
public static uint IsServer
{
get
{
dynamic installationType;
if (TryGeRegistryKey(#"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "InstallationType",
out installationType))
{
return (uint) (installationType.Equals("Client") ? 0 : 1);
}
return 0;
}
}
private static bool TryGeRegistryKey(string path, string key, out dynamic value)
{
value = null;
try
{
var rk = Registry.LocalMachine.OpenSubKey(path);
if (rk == null) return false;
value = rk.GetValue(key);
return value != null;
}
catch
{
return false;
}
}
}
}
I released the OsInfo nuget to easily compare Windows versions.
bool win8OrLess = Environment.OSVersion.IsLessThanOrEqualTo(OsVersion.Win8);
bool winXp = Environment.OSVersion.IsEqualTo(OsVersion.WinXP);
int? servicePack = Environment.OSVersion.GetServicePackVersion();
bool is64bit = Environment.OSVersion.Is64Bit(); // Already covered in .NET 4.5+
Try this:
using System.Management;
private string fnGetFriendlyName()
{
var name = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
return name != null ? name.ToString() : "Unknown";
}
Source: https://stackoverflow.com/a/2016557/3273962
My question is very simple, but i dint found an answer googling long time.
How to set REG_KEY_DONT_VIRTUALIZE flag to registry key created by me (i.e. HKLM\Software\MyApp)?
I want my program to be user-independent. Every user starting my app should have access to the same configuration options located in that location).
Changing application manifest I can disable registry virtualization by running program as administrator, but I want normal user be able to run the program and read registry values.
If you don't want your app to be virtualized then you use a manifest to indicate that. If you use REG_KEY_DONT_VIRTUALIZE on your key then all that will happen is that all the writes will fail because your users won't have write access to HKLM.
If you want all your users to share configuration then you'll have to store the configuration in a file rather than the registry. There's nowhere appropriate in the registry that is shared by all users and allows standard users write access.
This is pretty unclear, virtualization is only enabled for legacy non-UAC compatible programs and reading is always permitted. I have to assume that writing is the problem. Change the permissions on the key with, say, your installer or Regedit.exe so that Everybody has write access.
Without changing or adding ACLs to the key, you can ensure that the key you are using programmatically is viewing the 64-bit part of the registry by using the RegistryKey.OpenBaseKey API with the RegistryView.Registry64 flag.
This appears to work properly for 32-bit applications regardless of whether or not registry virtualization is enabled for the app.
private const string MyRegistryKeyPath = "Software\\My Company\\My App";
private static RegistryKey OpenMyAppRegistryKey(bool requireWriteAccess = false)
{
using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64))
{
return requireWriteAccess
? baseKey.CreateSubKey(MyRegistryKeyPath, RegistryKeyPermissionCheck.ReadWriteSubTree)
: baseKey.OpenSubKey(MyRegistryKeyPath, RegistryKeyPermissionCheck.ReadSubTree);
}
}
If requireWriteAccess is false, this method will return null if the specified key does not exist.
I should also point out that this code will require elevated permissions to open the key for write access. But I believe it ensures that unelevated reads using keys opened in this fashion will only come from the 64-bit view of the registry.
To date, there is no C# or C API to set the registry key flags.
I assume the safest way is to launch the REG.exe command line tool using CreateProcess.
But, for the record, I have pasted some 'C' code from this blog which demonstrates another way using an undocumented API:
typedef enum _CONTROL_FLAGS {
RegKeyClearFlags = 0,
RegKeyDontVirtualize = 2,
RegKeyDontSilentFail = 4,
RegKeyRecurseFlag = 8
} CONTROL_FLAGS;
typedef struct _KEY_CONTROL_FLAGS_INFORMATION {
ULONG ControlFlags;
} KEY_CONTROL_FLAGS_INFORMATION, *PKEY_CONTROL_FLAGS_INFORMATION;
typedef enum _KEY_SET_INFORMATION_CLASS {
KeyWriteTimeInformation,
KeyWow64FlagsInformation,
KeyControlFlagsInformation,
KeySetVirtualizationInformation,
KeySetDebugInformation,
MaxKeySetInfoClass // MaxKeySetInfoClass should always be the last enum
} KEY_SET_INFORMATION_CLASS;
NTSYSAPI NTSTATUS NTAPI NtSetInformationKey(
IN HANDLE KeyHandle,
IN KEY_SET_INFORMATION_CLASS InformationClass,
IN PVOID KeyInformationData,
IN ULONG DataLength );
typedef NTSYSAPI NTSTATUS (NTAPI* FuncNtSetInformationKey) (
HANDLE KeyHandle,
KEY_SET_INFORMATION_CLASS InformationClass,
PVOID KeyInformationData,
ULONG DataLength );
BOOL CRegLonMigration::SetDontVirtualizeFlag(LPCTSTR keyPath)
{
FuncNtSetInformationKey ntsik = (FuncNtSetInformationKey)GetProcAddress(GetModuleHandle( _T("ntdll.dll") ), "NtSetInformationKey" );
KEY_CONTROL_FLAGS_INFORMATION kcfi = {0};
kcfi.ControlFlags = RegKeyDontVirtualize | RegKeyRecurseFlag;
HKEY hKey = NULL;
LSTATUS status;
if (ERROR_SUCCESS == (status = ::RegOpenKeyEx(ROOT_KEY, keyPath, 0, KEY_ALL_ACCESS, &hKey)))
{
NTSTATUS status = ntsik( hKey, KeyControlFlagsInformation, &kcfi, sizeof( KEY_CONTROL_FLAGS_INFORMATION ) );
RegCloseKey( hKey );
return TRUE;
}
return FALSE;
}
I have a c# unit test project that is compiled for AnyCPU. Our build server is a 64bit machine, and has a 64bit SQL Express instance installed.
The test project uses code similar to the following to identify the path to the .MDF files:
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();
}
This code works fine on our 32bit workstations, and did work ok on the build server until I recently enabled code coverage analysis with NCover. Because NCover uses a 32bit COM component, the test runner (Gallio) runs as a 32bit process.
Checking the registry, there is no "Instance Names" key under
HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Microsoft SQL Server
Is there a way for an application running in 32bit mode to access the registry outside Wow6432Node?
Reading the 64 bit registry is possible because of WOW64 which is a Windows subsystem providing access to 64 bit from within 32 bit applications. (Likewise, in older NT-based Windows versions it was called WOW and was an emulation layer inside 32 bit Windows to support 16 bit applications).
There is still native support for registry access under 64 bit Windows using .NET Framework 4.x and for newer .NET versions (such as .NET Core, .NET 5 and 6) as well. The following code is tested with Windows 7, 64 bit and also with Windows 10, 64 bit. It should also work with Windows 11.
Instead of using "Wow6432Node", which emulates a node by mapping one registry tree into another making it appear there virtually, you can do the follwing:
Decide, whether you need to access the 64 bit or the 32 bit registry, and use it as described below. You may also use the code I mentioned later (Additional information section), which creates a union query to get registry keys from both nodes in one query - so you can still query them by using their real path.
64 bit registry
To access the 64 bit registry, you can use RegistryView.Registry64 as follows:
// 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 bit registry
If you want to access the 32bit registry, use RegistryView.Registry32 as follows:
// 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));
Don't be confused, both versions are using Microsoft.Win32.RegistryHive.LocalMachine as first parameter, you make the distinction whether to use 64 bit or 32 bit by the 2nd parameter (RegistryView.Registry64 versus RegistryView.Registry32).
Note that
On a 64bit Windows, HKEY_LOCAL_MACHINE\Software\Wow6432Node contains values used by 32 bit applications running on the 64 bit system. Only true 64 bit applications store their values in HKEY_LOCAL_MACHINE\Software directly. The subtree Wow6432Node is entirely transparent for 32 bit applications, 32 bit applications still see HKEY_LOCAL_MACHINE\Software as they expect it (it is a kind of redirection). In older versions of Windows as well as 32 bit Windows 7 (and Vista 32 bit) the subtree Wow6432Node obviously does not exist.
Due to a bug in Windows 7 (64 bit), the 32 bit source code version always returns "Microsoft" regardless which organization you have registered while the 64 bit source code version returns the right organization.
Coming back to the example you've provided, do it the following way to access the 64 bit branch:
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:
I'd like to add an interesting approach Johny Skovdal has suggested in the comments, which I've picked up to develop some useful functions by using his approach: In some situations you want to get back all keys regardless whether it is 32 bit or 64 bit. The SQL instance names are such an example. You can use a union query in that case as follows (C#6 or higher):
// 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);
}
Now you can simply use the functions above as follows:
Example 1: Get SQL instance names
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}");
}
will give you a list of the value names and values in sqlRegPath.
Note: You can access the default value of a key (displayed by the commandline tool REGEDT32.EXE as (Default)) if you omit the ValueName parameter in the corresponding functions above.
To get a list of SubKeys within a registry key, use the function GetRegKeyNamesor GetAllRegKeyNames. You can use this list to traverse further keys in the registry.
Example 2: Get uninstall information of installed software
var currentVersionRegPath = #"SOFTWARE\Microsoft\Windows\CurrentVersion";
var uninstallRegPath = $#"{currentVersionRegPath}\Uninstall";
var regKeys = Registry.GetAllRegKeyNames(RegPath: uninstallRegPath);
will get all 32 bit and 64 bit uninstall keys.
Notice the null handling required in the functions because SQL server can be installed as 32 bit or as 64 bit (Example 1 above). The functions are overloaded so you can still pass the 32 bit or 64 bit parameter if required - however, if you omit it then it will try to read 64 bit, if that fails (null value), it reads the 32 bit values.
There is one speciality here: Because GetAllRegValueNames is usually used in a loop context (see Example 1 above), it returns an empty enumerable rather than null to simplify foreach loops: if it wouldn't be handled that way, the loop would have to be prefixed by an if statement checking for null which would be cumbersome having to do that - so that is dealt with once in the function.
Why bothering about null? Because if you don't care, you'll have a lot more headaches finding out why that null reference exception was thrown in your code - you'd spend a lot of time finding out where and why it happened. And if it happened in production you'll be very busy studying log files or event logs (I hope you have logging implemented) ... better avoid null issues where you can in a defensive way. The operators ?., ?[...] and ?? can help you a lot (see the code provided above). There is a nice related article discussing the new nullable reference types in C#, which I recommend to read and also this one about the Elvis operator (a nickname for the ?. operator, sometimes also called safe navigation operator).
Hint: You can use the free edition of Linqpad to test all examples under Windows. It doesn't require an installation. Don't forget to press F4 and enter Microsoft.Win32 in the Namespace import tab. In Visual Studio, you require using Microsoft.Win32; at the top of your code.
Tip: To familiarize yourself with the new null handling operators, try out (and debug) the following code in LinqPad:
Example 3: Demonstrating null handling operators
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
If you're interested, here are some examples I put together showing what else you can do with the tool.
you have to use the KEY_WOW64_64KEY param when creating/opening the registry key. But AFAIK that's not possible with the Registry class but only when using the API directly.
This might help to get you started.
I don't have enough rep to comment, but it's worth pointing out that it works when opening a remote registry using OpenRemoteBaseKey. Adding the RegistryView.Registry64 parameter allows a 32-bit program on Machine A to access the 64-bit registry on Machine B. Before I passed that parameter, my program was reading the 32-bit after OpenRemoteBaseKey, and did not find the key I was after.
Note: In my test, the remote machine was actually my machine, but I accessed it via OpenRemoteBaseKey, just as I would for a different machine.
try this (from a 32bit process):
> %WINDIR%\sysnative\reg.exe query ...
(found that here).
If you cannot use .NET 4 with its RegistryKey.OpenBaseKey(..., RegistryView.Registry64), you need to use Windows API directly.
The minimal interop is like:
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);
Use it like:
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);
}
}
From what I have read and from my own tests, it seems to me that registry should be checked in this path "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall". Because in other paths the registers are not deleted after uninstalling the program.
In this way I got 64 registers with 32 bit configuration.
string registryKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
RegistryKey key64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
RegistryKey key = key64.OpenSubKey(registryKey);
if (key != null)
{
var list = key.GetSubKeyNames().Select(keyName => key.OpenSubKey(keyName).GetValue("DisplayName")).ToList();
key.Close();
}
For 32 registers is:
registryKey = #"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
key = Registry.LocalMachine.OpenSubKey(registryKey);