After reading this post on SO I tried to write a small app I need to read and write hidden registry keys/values.
I checked Registry Manipulation using NT Native APIs and Creating "Hidden" Registry Values links.
First one gave me something to work on, but it's written in C++ while second is a Delphi project working well.
I am not able to convert first and I could try to convert second, but I'd need to find some code to read keys/values too. For this reason I'd like to know if there's something "ready" and tested in C#.
I've also downloaded Proces Hacker v1.11 source code and used it to partially convert Delphi example as shown below, but hidden registry key is accessible (while in Delphi it wasn't) and there are not APIs to write values.
static void Main(string[] args)
{
string KeyNameBuffer = #"\Registry\User\S-1-5-21-3979903645-2167650815-2353538381-1001\SOFTWARE";
string NewKeyNameBuffer = "Systems Internals";
string HiddenKeyNameBuffer = "Can't touch me\0";
string HiddenValueNameBuffer = "Hidden Value";
// Apro la chiave di registro
IntPtr SoftwareKeyHandle = CreateKey(KeyNameBuffer, IntPtr.Zero);
if (SoftwareKeyHandle != IntPtr.Zero)
{
IntPtr SysKeyHandle = CreateKey(NewKeyNameBuffer, SoftwareKeyHandle);
if (SysKeyHandle != IntPtr.Zero)
{
// This key shouldn't be accessible, but it is
IntPtr HiddenKeyHandle = CreateKey(HiddenKeyNameBuffer, SysKeyHandle);
if (HiddenKeyHandle != IntPtr.Zero)
{
// I don't have APIs to write values
}
}
}
}
static IntPtr CreateKey(string keyName, IntPtr rootKey)
{
IntPtr res;
KeyCreationDisposition disp;
ObjectAttributes attributes = new ObjectAttributes(keyName,
ObjectFlags.CaseInsensitive,
new NativeHandle(rootKey));
NtStatus st = Win32.NtCreateKey(out res, KeyAccess.All,
ref attributes, 0,
IntPtr.Zero, RegOptions.NonVolatile, out disp);
return st == NtStatus.Success ? res : IntPtr.Zero;
}
Finally: from Vista on, you cannot write \Registry\Machine part if you're not running your app as Administrator, so in the example I used my user registry key. Is there a way to us native APIs to write that part of the registry if I need to store a per-machine value?
If you want it in HKLM and privileges don't let you, it doesn't matter which API layer you're using, Reg* functions of Nt* ones - it won't let you do that with access denied error.
Related
I need to get a list of all currently opened MSACCESS instances in the system (windows) to be able to close any of them from within my app. I have no problems with EXCEL and WINWORD but can't hook up with Access.
I use Office 2016 and I see that MSACCESS creates separate procss for each opened database file. So I think I have to get application instances from window handles. I've tried to adapt this code: How to iterate through instance of Excel c#
I'm able to get all MSACCESS processes but the Excel or Word code isn't working for MSACCESS. The Code line:
if (buf.ToString() == "EXCEL7")
Always gives me the MsoCommandBarDock value.
Any thoughts on how I can achieve this?
Based on the answer for Excel, the Access version is similar:
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
var procs = new List<Process>(Process.GetProcessesByName("MSACCESS.EXE"));
foreach (var p in procs)
{
var mainHandle = (int)p.MainWindowHandle;
if (mainHandle > 0)
{
var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Microsoft.Office.Interop.Access.Application app = null;
int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
if (res >= 0)
{
Debug.Assert(app.hWndAccessApp == mainHandle);
Console.WriteLine(app.Name);
}
}
}
I tested it with Access 2016 on Windows 10, en-us locale.
The major difference is that the window hierarchy of access is not as convoluted as the one of Excel, therefore you can omit the iteration of child windows.
Disclaimer: This relies on the internal structure of a closed-source Windows application. Microsoft as its vendor discourages this kind of tricks for obvious reasons: they may ship and update or release a new version at any time where the inner structure (the window hierarchy) has changed, breaking code that relies on this. Also, MS Access used to have a single document view mode, which may present you with two versions of window hierarchy in the same release. Don't do this in commercial products / productive software.
According to the answer from Cee McSharpface, in 2021 (Microsoft Access for Microsoft 365 MSO (16.0.14326.20504) 64-bit and Windows 10 20H2) I had to adapt the solution as follows:
[DllImport("oleacc.dll")]
private static extern int AccessibleObjectFromWindow(
int hwnd, uint dwObjectID, byte[] riid,
ref Microsoft.Office.Interop.Access.Application ptr);
const uint OBJID_NATIVEOM = 0xFFFFFFF0;
var procs = new List<Process>(Process.GetProcessesByName("MSACCESS"));
foreach (var p in procs)
{
var mainHandle = (int)p.MainWindowHandle;
if (mainHandle > 0)
{
var IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
Microsoft.Office.Interop.Access.Application app = null;
int res = AccessibleObjectFromWindow(mainHandle, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref app);
if (res >= 0)
{
Debug.Assert(app.hWndAccessApp() == mainHandle);
Console.WriteLine(app.Name);
}
}
}
Please notice the following changes:
GetProcessesByName uses "MSACESS" instead of "MSACCESS.EXE", according to this documentation:
The process name is a friendly name for the process, such as Outlook,
that does not include the .exe extension or the path
AccessibleObjectFromWindow uses a ref Microsoft.Office.Interop.Access.Application ptr because there is no Window object like in the Excel interop.
There are many ways of doing this inlcuding retrieve COM Objects from ROT (running object table). Since your need is "just" to be able to close apps, following code should work fine.
using System.Diagnostics;
using System.Linq;
Process.GetProcessesByName("MSACCESS").All(x => x.CloseMainWindow());
This sends a close message to all Access main windows, which is similar to user closing the app.
I have developed a c# code snippet to determine if the virtual (on-screen) keyboard was showing or not.
The code below worked fine in Windows 7, 8 and 8.1, but in Windows 10, the IsKeyboardVisible always returns true...
public static bool IsKeyboardVisible() {
Process keyboardProc;
UInt32 WS_DISABLED = 0x8000000;
UInt32 WS_VISIBLE = 0X94000000;
int GWL_STYLE = -16;
IntPtr keyboardHandle = GetKeyboardWindowHandle();
bool visible = false;
if (keyboardHandle != IntPtr.Zero) {
UInt32 style = GetWindowLong(keyboardHandle, GWL_STYLE);
// in Win10, this always returns "true", i.e. WS_DISABLED is
//
//visible = ((style & WS_DISABLED) != WS_DISABLED);
// UPDATE: I found this code helping here
visible = (style == WS_VISIBLE);
}
return visible;
}
I used a tutorial on SO, but it's a while ago so sorry for not crediting the author.
Does anyone know about a working code snippet for all recent Windows versions, so I don't have to check the actual OS to switch on the version...?
UPDATE
I found the original post here, which allowed me to correct the code. So now my problem is the same old Win10 issue - I can't show the virtual keyboard using
string progFiles = #"C:\Program Files\Common Files\Microsoft Shared\ink";
string keyboardPath = Path.Combine(progFiles, "TabTip.exe");
keyboardProc = Process.Start(keyboardPath);
... Again, is there any "all-platform" code I can use, or what is the suggested approach for Win10?
UPDATE 2
I found out about issues running a 32-bit application on a 64-bit os. That being said, the error occurs whether I try to run osk.exe in the System32 or the "sysWOW64` folder... Is there any other way than making a 64-bit release???
After much digging about TabTip.exe, osk.exe and x86 and x64 compatibility issues, I found a solution by searching the osk.exe on my system and trying to run each of them. I found 4 version the following folders:
C:\Windows\System32
C:\Windows\SysWOW64
C:\Windows\WinSxS\amd64_microsoft...
C:\Windows\WinSxS\wow64_microsoft...
It appears the one in C:\Windows\WinSxS\amd64_microsoft... works fine (not the other three though)...
Given the "amd64_...." folder might not be the same on different machines (I actually checked and they don't match, I didn't search whether this depends on the machine, the windows build or anything else...).
So basically I did a small routine to look into WinSxS folder and returning the very firs occurrence of osk.exe, which works just fine. I also made the code working on a 32-bit OS using a simple OS-architecture test:
string OSKpath64 = getOskPath(#"C:\Windows\WinSxS");
if (string.IsNullOrWhiteSpace(OSKpath64)) {
OSKpath64 = "osk.exe";
}
string OSKpath32 = #"C:\Windows\System32\osk.exe";
if (!File.Exists(OSKpath32)) {
OSKpath32 = #"osk.exe";
}
System.Diagnostics.Process.Start((Environment.Is64BitOperatingSystem) ? OSKpath64 : OSKpath32);
UPDATE:
The confusion with one working and one non-working version within the WinSxS folder made me nervous. It works just fine because the amd_.. folder is alphabetically before wow64_....
I therefore suggest to add a test in the getOskPath method to return the first native 64-bit osk.exe (not the emulated one).
Using the IsWin64Emulator method found here, the method looks like this:
static string getOskPath(string dir) {
string path = Path.Combine(dir, "osk.exe");
if (File.Exists(path)) {
Process p = System.Diagnostics.Process.Start(path);
if (p.IsWin64Emulator()) {
path = string.Empty;
}
p.Kill();
return path;
}
DirectoryInfo di = new DirectoryInfo(dir);
foreach (DirectoryInfo subDir in di.GetDirectories().Reverse()) {
path = getOskPath(Path.Combine(dir, subDir.Name));
if (!string.IsNullOrWhiteSpace(path)) {
return path;
}
}
return string.Empty;
}
Same problem with me, I try all answer here, but it not work.
After finding solution with google, this is is ok.
// Step 1: For Load On-Screen Keyboard
const string Kernel32dll = "Kernel32.Dll";
[DllImport(Kernel32dll, EntryPoint = "Wow64DisableWow64FsRedirection")]
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport(Kernel32dll, EntryPoint = "Wow64EnableWow64FsRedirection")]
public static extern bool Wow64EnableWow64FsRedirection(IntPtr ptr);
IntPtr wow64Value;
//---------------------------------------
// Step 2: Function-----
if (Environment.Is64BitOperatingSystem)
{
if (Wow64DisableWow64FsRedirection(ref wow64Value))
{
System.Diagnostics.Process.Start("osk.exe");
Wow64EnableWow64FsRedirection(wow64Value);
}
}
else
{
System.Diagnostics.Process.Start("osk.exe");
}
//----------------
I would like to get the Path of the windows which has the focus.
Ex: I have 3 windows Opened
a. C:\Windows
b. C:\Windows\System32
c. C:\Users\COMP-0\Documents
And i am working on c (C:\Users\COMP-0\Documents)
So i would like to get this path (C:\Users\COMP-0\Documents) programmatically in C#.
Expanding on this answer to get the selected files in a folder, you can use a similar approach to get the current folder and therefore it's path.
This needs some COM and requires:
Getting the active window using GetForegroundWindow
Find the current list of InternetExplorer windows using SHDocVw.ShellWindows,
Matching handle pointers to find the current window
Getting hold of the folder path inside the active window using the IShellFolderViewDual2 COM interface.
There are a couple of caveats to be aware of:
Special folders (Favourites, My Computer etc) will give you the file path as "::{GUID}" where the GUID points to the CLSID for that folder in the registry. It is probably possible to convert that value to a path.
Going to "Desktop" will return null for the current folder
Focussing Internet Explorer will trigger a match on the active window so we need to ensure we are in a Shell Folder
If in a special folder or Desktop this code will just return the current window title - usually the name of the special folder - using the details in this answer.
private static string GetActiveExplorerPath()
{
// get the active window
IntPtr handle = GetForegroundWindow();
// Required ref: SHDocVw (Microsoft Internet Controls COM Object) - C:\Windows\system32\ShDocVw.dll
ShellWindows shellWindows = new SHDocVw.ShellWindows();
// loop through all windows
foreach (InternetExplorer window in shellWindows)
{
// match active window
if (window.HWND == (int)handle)
{
// Required ref: Shell32 - C:\Windows\system32\Shell32.dll
var shellWindow = window.Document as Shell32.IShellFolderViewDual2;
// will be null if you are in Internet Explorer for example
if (shellWindow != null)
{
// Item without an index returns the current object
var currentFolder = shellWindow.Folder.Items().Item();
// special folder - use window title
// for some reason on "Desktop" gives null
if (currentFolder == null || currentFolder.Path.StartsWith("::"))
{
// Get window title instead
const int nChars = 256;
StringBuilder Buff = new StringBuilder(nChars);
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
}
else
{
return currentFolder.Path;
}
}
break;
}
}
return null;
}
// COM Imports
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
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);