How to use REG_OPTION_OPEN_LINK in C# Registry class - c#

I want to open a registry key that is a symbolic link.
According to Microsoft I need to use REG_OPTION_OPEN_LINK to open it.
I searched for an option to add it to the OpenSubKey function but I didn't find an option. There are only fiver overload functions but none of them allow to add an optional parameter:
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(string name)
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(string name, bool writable)
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck)
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(string name, RegistryRights rights)
Microsoft.Win32.Registry.CurrentUser.OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, RegistryRights rights)
The only way I can think of is to use p\invoke but maybe I am missing it and there is an option in C# classes.

You can't do this with the normal RegistryKey functions. Having checked in the source code, it seems that the ulOptions parameter is always passed as 0.
The only way is to call RegOpenKeyEx yourself, and pass the resulting SafeRegistryHandle to RegistryKey.FromHandle
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.ComponentModel;
using Microsoft.Win32;
using Microsoft.Win32.SafeHandles;
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
static extern int RegOpenKeyExW(SafeRegistryHandle hKey, String lpSubKey,
int ulOptions, int samDesired, out SafeRegistryHandle hkResult);
public static RegistryKey OpenSubKeySymLink(this RegistryKey key, string name, RegistryRights rights = RegistryRights.ReadKey, RegistryView view = 0)
{
const int REG_OPTION_OPEN_LINK = 0x0008;
var error = RegOpenKeyExW(key.Handle, name, REG_OPTION_OPEN_LINK, ((int)rights) | ((int)view), out var subKey);
if (error != 0)
{
subKey.Dispose();
throw new Win32Exception(error);
}
return RegistryKey.FromHandle(subKey); // RegistryKey will dispose subKey
}
It is an extension function, so you can call it on either an existing sub-key, or on one of the main keys, such as Registry.CurrentUser. Don't forget to put a using on the returned RegistryKey:
using (var key = Registry.CurrentUser.OpenSubKeySymLink(#"SOFTWARE\Microsoft\myKey", RegistryRights.ReadKey))
{
// do stuff with key
}

Related

How to delete registry symbolic link key from C#: "An error is preventing this key from being opened. Details: Access is denied"

I created a symbolic registry key by using the NtObjectManager library like that:
using NtApiDotNet;
using System;
namespace poc
{
class Program
{
const string SrcKey = #"HKEY_CURRENT_USER\SOFTWARE\ABC";
const string TargetKey = #"HKEY_LOCAL_MACHINE\SOFTWARE\XYZ";
static NtKey CreateSymbolicLink(string name, string target)
{
name = NtKeyUtils.Win32KeyNameToNt(name);
target = NtKeyUtils.Win32KeyNameToNt(target);
return NtKey.CreateSymbolicLink(name, null, target);
}
static void Main(string[] args)
{
var link = CreateSymbolicLink(SrcKey, TargetKey)
}
}
}
When I tried to delete the key from Registry (Regedit.exe) it failed with error:
ABC cannot be opened. An error is preventing this key from being
opened. Details: Access is denied
I tried to delete it even with SYSTEM permissions (using psexec to launch a SYSTEM cmd) but I still received the same error.
The function NtKey.CreateSymbolicLink is calling SetSymbolicLinkTarget which calls eventually to SetValue like that:
SetValue(SymbolicLinkValueName, RegistryValueType.Link, Encoding.Unicode.GetBytes(target), throw_on_error);
I didn't figure out yet how to delete it.
I found an answer about deleting symbolic registry key with C++ but it just calls lpfnZwDeleteKey and I don't know what is the equivalent to C#.
I tried the function NtKey.UnloadKey function, I thought it might help but it didn't.
I was able to delete it using James's tool CreateRegSymlink like that:
CreateRegSymlink.exe -d "HKCU\Software\XYZ"
I noticed that it is being done by calling DeleteRegSymlink.
When I checked what is inside it, I noticed it convert the registry path to a real path by calling RegPathToNative:
bstr_t symlink = RegPathToNative(lpSymlink);
Here you can see what the RegPathToNative work.
Then it calls:
InitializeObjectAttributes(&obj_attr, &name, OBJ_CASE_INSENSITIVE | OBJ_OPENLINK, nullptr, nullptr);
Which is I think where the magic happens.
If you have any suggestion how to find the real link from a symbolic registry path, let me know.
EDIT(10/1/2022) - thanks to #RbMm:
I created a function to open symlink using REG_OPTION_OPEN_LINK and then deletes it with ZwDeleteKey but the important thing was to set the rights to RegistryRights.Delete as #RbMm mentioned:
const int REG_OPTION_OPEN_LINK = 0x0008;
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
static extern int RegOpenKeyExW(SafeRegistryHandle hKey, String lpSubKey,
int ulOptions, int samDesired, out SafeRegistryHandle hkResult);
[DllImport("ntdll.dll")]
private static extern int ZwDeleteKey(SafeRegistryHandle hKey);
public static RegistryKey OpenSubKeySymLink(this RegistryKey key, string name, RegistryRights rights = RegistryRights.ReadKey, RegistryView view = 0)
{
var error = RegOpenKeyExW(key.Handle, name, REG_OPTION_OPEN_LINK, ((int)rights) | ((int)view), out var subKey);
if (error != 0)
{
subKey.Dispose();
throw new Win32Exception(error);
}
return RegistryKey.FromHandle(subKey); // RegistryKey will dispose subKey
}
static void Main(string[] args)
{
RegistryKey key;
key = OpenSubKeySymLink(Microsoft.Win32.Registry.CurrentUser, #"SOFTWARE\Microsoft\Windows\ABC", RegistryRights.Delete, 0);
ZwDeleteKey(key.Handle);
}

Getting the exact display name of the metro style apps

I am using PackageManager class to list the installed metro style applications in the system.
PackageId.Name does not return the actual name of the package, Neither is Package.DisplayName. Package.DisplayName returns empty string. It does return display name only for Package.Current.
When I tried to use AppxManifest.xml, I could not get the display name from
Package->Properties->Displayname too.
<Properties>
<DisplayName>ms-resource:///Resources/AppStoreName</DisplayName>
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
<Logo>Assets\WindowsIcons\StoreLogo.png</Logo>
</Properties>
Where can I get the exact display name of metro style applications ?
If you already have a reference to a Package instance, you can get the Display Name using an interop method and the registry. Here's a complete example:
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
using System.Text;
using Windows.ApplicationModel;
public class PackageUtil
{
[DllImport("shlwapi.dll", BestFitMapping = false, CharSet = CharSet.Unicode,
ExactSpelling = true, SetLastError = false, ThrowOnUnmappableChar = true)]
private static extern int SHLoadIndirectString(string pszSource,
StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
private static string GetPackageDisplayName(Package package)
{
using (var key = Registry.ClassesRoot.OpenSubKey(
#"Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel" +
$#"\Repository\Packages\{package.Id.FullName}\App\Capabilities"))
{
var sb = new StringBuilder(256);
Shlwapi.SHLoadIndirectString((string)key.GetValue("ApplicationName"),
sb, sb.Capacity, IntPtr.Zero);
return sb.ToString();
}
}
}

Constructing a FileStream from a handle produced by CreateFile gives an empty stream

I'm attempting to write a simple program that would take a CSV consisting of two columns, and given a keyword from the first column, return the corresponding value from the second one. The problem is that I need the CSV to be in an alternate data stream to make the program as portable as possible (I want to make it so that when the user drops a CSV file on the executable, the CSV is overwritten). That is why I'm trying to use WinAPI's CreateFile function -- .NET doesn't support alternate data streams. Unfortunately, I'm failing miserably.
In the current state, the program is supposed to read a file named test.csv. I want to do CreateFile on it, convert the intPtr handle to a SafeFileHandle and then pass the SafeFileHandle to a FileStream constructor. The best I've been able to achieve is an empty stream though. It doesn't seem to me that the program is actually getting the right handle. When I try "CREATE_ALWAYS" or "CREATE_NEW" instead of "OPEN_ALWAYS", I'm getting an "Invalid Handle" error, no matter what I do with the rest of the parameters. With "OPEN_ALWAYS", when I check the value of "stream.Name", I'm getting "Unknown".
Here is the code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Searcher
{
public partial class Searcher : Form
{
public static List<string> keywords;
public static List<string> values;
public Searcher()
{
InitializeComponent();
//byte[] path = Encoding.UTF8.GetBytes("C:\\Users\\as\\Documents\\test.csv");
SafeFileHandle safeADSHandle = NativeMethods.CreateFileW("test.csv",
NativeConstants.GENERIC_READ,
NativeConstants.FILE_SHARE_READ,
IntPtr.Zero,
NativeConstants.OPEN_ALWAYS,
0,
IntPtr.Zero);
//var safeADSHandle = new SafeFileHandle(handle, true);
if (safeADSHandle.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
var stream = new FileStream(safeADSHandle, FileAccess.Read);
MessageBox.Show(stream.Name);
var reader = new StreamReader(stream);
Searcher.keywords = new List<string>();
Searcher.values = new List<string>();
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
var values = line.Split(',');
Searcher.keywords.Add(values[0]);
Searcher.values.Add(values[1]);
}
cbKeyword.DataSource = Searcher.keywords;
cbKeyword.AutoCompleteSource = AutoCompleteSource.ListItems;
}
private void btnSearch_Click(object sender, EventArgs e)
{
tbResult.Text = Searcher.values[cbKeyword.SelectedIndex];
}
}
}
public partial class NativeMethods
{
[DllImportAttribute("kernel32.dll", SetLastError = true, EntryPoint = "CreateFile")]
public static extern SafeFileHandle CreateFileW(
[InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
[InAttribute()] System.IntPtr lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
[InAttribute()] System.IntPtr hTemplateFile
);
}
public partial class NativeConstants
{
/// GENERIC_WRITE -> (0x40000000L)
public const int GENERIC_WRITE = 1073741824;
public const uint GENERIC_READ = 2147483648;
/// FILE_SHARE_DELETE -> 0x00000004
public const int FILE_SHARE_DELETE = 4;
/// FILE_SHARE_WRITE -> 0x00000002
public const int FILE_SHARE_WRITE = 2;
/// FILE_SHARE_READ -> 0x00000001
public const int FILE_SHARE_READ = 1;
/// OPEN_ALWAYS -> 4
public const int OPEN_ALWAYS = 4;
public const int CREATE_NEW = 1;
}
Edit I've changed the code above slightly to reflect the changes after the comments. Now I'm not converting from IntPtr to SafeFileHandle. One thing to mention perhaps is that I before this change I tried reading the value of handle.ToString() to see if it was changing and it was -- it's a random number.
The reason this is not working is because you have specified an entry point without a character set. When you don't specify a character set in DllImport, the default is CharSet.Ansi, which means platform invoke will search for the function as follows:
First for an unmangled exported function, i.e. the name CreateFile from the entry point that you specified (which does not exist in kernel32.dll)
Next if the unmangled name was not found, it will search for the mangled name based on the charset, so it will search for CreateFileA
So it will find the CreateFileA exported function, which assumes any strings passed in to it are in 1-byte character ANSI format. However, you are marshalling the string as a wide string. Note that the wide-character version of the function is called CreateFileW (this distinction between ANSI and wide character versions of functions is common in the Windows API).
To fix this you just need to make sure that the marshalling of your parameters matches up with what the function you are importing expects. So you could remove the EntryPoint field, in which case it will use the C# method name to find the exported function instead (so it will find CreateFileW).
However to make it even clearer, I would write your platform invoke code as follows:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CreateFile(
[MarshalAs(UnmanagedType.LPTStr)] string filename,
[MarshalAs(UnmanagedType.U4)] FileAccess access,
[MarshalAs(UnmanagedType.U4)] FileShare share,
IntPtr securityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes flagsAndAttributes,
IntPtr templateFile);
I took this from the pinvoke.net website, which should be your go-to for writing pinvoke code rather than trying to bash it out yourself (especially if you aren't familiar with the Windows API or marshalling :).
The reason there was no error is probably because CreateFile was creating the file since it wouldn't have been able to find it.
The file name will appear as "[Unknown]" though. I suspect this is because the code to get a file name from a handle is non-trivial.

Help using PInvoke CreateDirectory () in C#

I'm using C# for WinForms app in VS2010 and I needed to create a directory in which the path was too large for the .NET methods (248 char limit, I believe) and ran across suggestions from google to use the Unicode Win32 CreateDirectory(). I had initially tried calling it using Unicode and passed parameters but after several failed attempts, I've reduced the code and am using EXACTLY the code found here:
http://www.pinvoke.net/default.aspx/Structures/SECURITY_ATTRIBUTES.html
I am still getting the same error:
System.AccessViolationException was caught
Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Admittedly, I don't know anything about calling the Win32 functions, I'm really just pulling what I can find online and trying to learn. Can anyone tell me what I'm doing wrong? Removing non-essential code for the question, I have:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Configuration;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Text;
namespace RFCGenerator
{
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
public class RFC
{
[DllImport("kernel32.dll")]
static extern bool CreateDirectory(string lpPathName, SECURITY_ATTRIBUTES lpSecurityAttributes);
protected void CopyDirectory(Uri Source, Uri Destination)
{
SECURITY_ATTRIBUTES lpSecurityAttributes = new SECURITY_ATTRIBUTES();
DirectorySecurity security = new DirectorySecurity();
lpSecurityAttributes.nLength = Marshal.SizeOf(lpSecurityAttributes);
byte[] src = security.GetSecurityDescriptorBinaryForm();
IntPtr dest = Marshal.AllocHGlobal(src.Length);
Marshal.Copy(src, 0, dest, src.Length);
lpSecurityAttributes.lpSecurityDescriptor = dest;
string path = #"C:\Test";
CreateDirectory(path, lpSecurityAttributes);
}
}
}
UPDATE: using Hans' suggestion, I did get this to work locally. However, when I attempt to create a directory using a UNC address, such as passing in:
path = #"\\mydomain.com\foo\bar\newfolder"
I now get:
System.ComponentModel.Win32Exception was caught
Message=The filename, directory name, or volume label syntax is incorrect
I have verified that \\mydomain.com\foo\bar\ does exist.
SOLUTION:
Using Hans' code and a minor modification to check if it's UNC path (reference: http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx, under "Maximum Path Length Limitation"):
string UnicodePath = (path.StartsWith(#"\\")) ? #"\\?\UNC\" + (path.Remove(0, 2)) : #"\\?\" + path;
if (!CreateDirectory(UnicodePath, IntPtr.Zero))
throw new System.ComponentModel.Win32Exception();
You are not using the Unicode version, that requires CharSet = CharSet.Unicode in the [DllImport] declaration. Furthermore, creating directories with long names has nothing to do with the security attribute. You have to prefix the name with #"\\?\". Thus:
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
static extern bool CreateDirectory(string lpPathName, IntPtr lpSecurityAttributes);
...
if (!CreateDirectory(#"\\?\" + path, IntPtr.Zero))
throw new Win32Exception();

Programmatically Set Browser Proxy Settings in C#

I'm writing an winforms app that needs to set internet explorer's proxy settings and then open a new browser window. At the moment, I'm applying the proxy settings by going into the registry:
RegistryKey registry = Registry.CurrentUser.OpenSubKey("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", true);
registry.SetValue("ProxyEnable", 1);
registry.SetValue("ProxyServer", "127.0.0.1:8080");
Is going into the registry the best way to do this, or is there a more recommended approach? I'd like to avoid registry changes if there's an alternative solution.
This depends somewhat on your exact needs. If you are writing a C# app and simply want to set the default proxy settings that your app will use, use the class System.Net.GlobalProxySelection (http://msdn.microsoft.com/en-us/library/system.net.globalproxyselection.aspx). You can also set the proxy for any particular connection with System.Net.WebProxy (http://msdn.microsoft.com/en-us/library/system.net.webproxy.aspx).
If you actually want to update the proxy settings in the registry, I believe that you'll need to use P/Invoke to call the WinAPI function WinHttpSetDefaultProxyConfiguration (http://msdn.microsoft.com/en-us/library/aa384113.aspx).
from: http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/19517edf-8348-438a-a3da-5fbe7a46b61a
Add these lines at the beginning of your code:
using System.Runtime.InteropServices;
using Microsoft.Win32;
[DllImport("wininet.dll")]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
public const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
public const int INTERNET_OPTION_REFRESH = 37;
bool settingsReturn, refreshReturn;
And imply the code:
RegKey.SetValue("ProxyServer", YOURPROXY);
RegKey.SetValue("ProxyEnable", 1);
// These lines implement the Interface in the beginning of program
// They cause the OS to refresh the settings, causing IP to realy update
settingsReturn = InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
refreshReturn = InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
I wrote a 10 lines program to do that, feel free to try https://github.com/131/proxytoggle
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace ProxyToggle
{
class Program
{
[DllImport("wininet.dll")]
public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);
public const int INTERNET_OPTION_SETTINGS_CHANGED = 39;
public const int INTERNET_OPTION_REFRESH = 37;
static void setProxy(string proxyhost, bool proxyEnabled)
{
const string userRoot = "HKEY_CURRENT_USER";
const string subkey = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings";
const string keyName = userRoot + "\\" + subkey;
if(proxyhost.Length != 0)
Registry.SetValue(keyName, "ProxyServer", proxyhost);
Registry.SetValue(keyName, "ProxyEnable", proxyEnabled ? "1" : "0", RegistryValueKind.DWord);
// These lines implement the Interface in the beginning of program
// They cause the OS to refresh the settings, causing IP to realy update
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_SETTINGS_CHANGED, IntPtr.Zero, 0);
InternetSetOption(IntPtr.Zero, INTERNET_OPTION_REFRESH, IntPtr.Zero, 0);
}
static void Main(string[] args)
{
if (args.Length == 0)
{
setProxy("", false);
return;
}
setProxy(args[0], true);
}
}
}
Check out this KB article specifically tagged at what you're trying to do.
http://support.microsoft.com/kb/226473
The short version is you want to use the InternetOpen, InternetSetOption API's to update the proxy settings.
You can use this useful method existing since FW 2.0:
(i've just discovered and i'm another man now...)
http://msdn.microsoft.com/en-us/library/system.net.webrequest.getsystemwebproxy.aspx
Quick Code example (from msdn):
WebProxy proxyObject = new WebProxy("http://proxyserver:80/",true);
WebRequest req = WebRequest.Create("http://www.contoso.com");
req.Proxy = proxyObject;

Categories

Resources