2200 lines
96 KiB
C#
2200 lines
96 KiB
C#
// ==++==
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ==--==
|
|
|
|
/*
|
|
Note on transaction support:
|
|
Eventually we will want to add support for NT's transactions to our
|
|
RegistryKey API's (possibly Whidbey M3?). When we do this, here's
|
|
the list of API's we need to make transaction-aware:
|
|
|
|
RegCreateKeyEx
|
|
RegDeleteKey
|
|
RegDeleteValue
|
|
RegEnumKeyEx
|
|
RegEnumValue
|
|
RegOpenKeyEx
|
|
RegQueryInfoKey
|
|
RegQueryValueEx
|
|
RegSetValueEx
|
|
|
|
We can ignore RegConnectRegistry (remote registry access doesn't yet have
|
|
transaction support) and RegFlushKey. RegCloseKey doesn't require any
|
|
additional work. .
|
|
*/
|
|
|
|
/*
|
|
Note on ACL support:
|
|
The key thing to note about ACL's is you set them on a kernel object like a
|
|
registry key, then the ACL only gets checked when you construct handles to
|
|
them. So if you set an ACL to deny read access to yourself, you'll still be
|
|
able to read with that handle, but not with new handles.
|
|
|
|
Another peculiarity is a Terminal Server app compatibility hack. The OS
|
|
will second guess your attempt to open a handle sometimes. If a certain
|
|
combination of Terminal Server app compat registry keys are set, then the
|
|
OS will try to reopen your handle with lesser permissions if you couldn't
|
|
open it in the specified mode. So on some machines, we will see handles that
|
|
may not be able to read or write to a registry key. It's very strange. But
|
|
the real test of these handles is attempting to read or set a value in an
|
|
affected registry key.
|
|
|
|
For reference, at least two registry keys must be set to particular values
|
|
for this behavior:
|
|
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\RegistryExtensionFlags, the least significant bit must be 1.
|
|
HKLM\SYSTEM\CurrentControlSet\Control\TerminalServer\TSAppCompat must be 1
|
|
There might possibly be an interaction with yet a third registry key as well.
|
|
|
|
*/
|
|
|
|
|
|
namespace Microsoft.Win32 {
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Security;
|
|
#if FEATURE_MACL
|
|
using System.Security.AccessControl;
|
|
#endif
|
|
using System.Security.Permissions;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.IO;
|
|
using System.Runtime.Remoting;
|
|
using System.Runtime.InteropServices;
|
|
using Microsoft.Win32.SafeHandles;
|
|
using System.Runtime.Versioning;
|
|
using System.Globalization;
|
|
using System.Diagnostics.Contracts;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
|
|
#if !FEATURE_PAL
|
|
|
|
/**
|
|
* Registry hive values. Useful only for GetRemoteBaseKey
|
|
*/
|
|
[Serializable]
|
|
[System.Runtime.InteropServices.ComVisible(true)]
|
|
public enum RegistryHive
|
|
{
|
|
ClassesRoot = unchecked((int)0x80000000),
|
|
CurrentUser = unchecked((int)0x80000001),
|
|
LocalMachine = unchecked((int)0x80000002),
|
|
Users = unchecked((int)0x80000003),
|
|
PerformanceData = unchecked((int)0x80000004),
|
|
CurrentConfig = unchecked((int)0x80000005),
|
|
DynData = unchecked((int)0x80000006),
|
|
}
|
|
|
|
/**
|
|
* Registry encapsulation. To get an instance of a RegistryKey use the
|
|
* Registry class's static members then call OpenSubKey.
|
|
*
|
|
* @see Registry
|
|
* @security(checkDllCalls=off)
|
|
* @security(checkClassLinking=on)
|
|
*/
|
|
#if FEATURE_REMOTING
|
|
[ComVisible(true)]
|
|
public sealed class RegistryKey : MarshalByRefObject, IDisposable
|
|
#else
|
|
[ComVisible(true)]
|
|
public sealed class RegistryKey : IDisposable
|
|
#endif
|
|
{
|
|
|
|
// We could use const here, if C# supported ELEMENT_TYPE_I fully.
|
|
internal static readonly IntPtr HKEY_CLASSES_ROOT = new IntPtr(unchecked((int)0x80000000));
|
|
internal static readonly IntPtr HKEY_CURRENT_USER = new IntPtr(unchecked((int)0x80000001));
|
|
internal static readonly IntPtr HKEY_LOCAL_MACHINE = new IntPtr(unchecked((int)0x80000002));
|
|
internal static readonly IntPtr HKEY_USERS = new IntPtr(unchecked((int)0x80000003));
|
|
internal static readonly IntPtr HKEY_PERFORMANCE_DATA = new IntPtr(unchecked((int)0x80000004));
|
|
internal static readonly IntPtr HKEY_CURRENT_CONFIG = new IntPtr(unchecked((int)0x80000005));
|
|
internal static readonly IntPtr HKEY_DYN_DATA = new IntPtr(unchecked((int)0x80000006));
|
|
|
|
// Dirty indicates that we have munged data that should be potentially
|
|
// written to disk.
|
|
//
|
|
private const int STATE_DIRTY = 0x0001;
|
|
|
|
// SystemKey indicates that this is a "SYSTEMKEY" and shouldn't be "opened"
|
|
// or "closed".
|
|
//
|
|
private const int STATE_SYSTEMKEY = 0x0002;
|
|
|
|
// Access
|
|
//
|
|
private const int STATE_WRITEACCESS = 0x0004;
|
|
|
|
// Indicates if this key is for HKEY_PERFORMANCE_DATA
|
|
private const int STATE_PERF_DATA = 0x0008;
|
|
|
|
// Names of keys. This array must be in the same order as the HKEY values listed above.
|
|
//
|
|
private static readonly String[] hkeyNames = new String[] {
|
|
"HKEY_CLASSES_ROOT",
|
|
"HKEY_CURRENT_USER",
|
|
"HKEY_LOCAL_MACHINE",
|
|
"HKEY_USERS",
|
|
"HKEY_PERFORMANCE_DATA",
|
|
"HKEY_CURRENT_CONFIG",
|
|
"HKEY_DYN_DATA"
|
|
};
|
|
|
|
// MSDN defines the following limits for registry key names & values:
|
|
// Key Name: 255 characters
|
|
// Value name: 16,383 Unicode characters
|
|
// Value: either 1 MB or current available memory, depending on registry format.
|
|
private const int MaxKeyLength = 255;
|
|
private const int MaxValueLength = 16383;
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private volatile SafeRegistryHandle hkey = null;
|
|
private volatile int state = 0;
|
|
private volatile String keyName;
|
|
private volatile bool remoteKey = false;
|
|
private volatile RegistryKeyPermissionCheck checkMode;
|
|
private volatile RegistryView regView = RegistryView.Default;
|
|
|
|
/**
|
|
* RegistryInternalCheck values. Useful only for CheckPermission
|
|
*/
|
|
private enum RegistryInternalCheck {
|
|
CheckSubKeyWritePermission = 0,
|
|
CheckSubKeyReadPermission = 1,
|
|
CheckSubKeyCreatePermission = 2,
|
|
CheckSubTreeReadPermission = 3,
|
|
CheckSubTreeWritePermission = 4,
|
|
CheckSubTreeReadWritePermission = 5,
|
|
CheckValueWritePermission = 6,
|
|
CheckValueCreatePermission = 7,
|
|
CheckValueReadPermission = 8,
|
|
CheckKeyReadPermission = 9,
|
|
CheckSubTreePermission = 10,
|
|
CheckOpenSubKeyWithWritablePermission = 11,
|
|
CheckOpenSubKeyPermission = 12
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates a RegistryKey.
|
|
*
|
|
* This key is bound to hkey, if writable is <b>false</b> then no write operations
|
|
* will be allowed.
|
|
*/
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private RegistryKey(SafeRegistryHandle hkey, bool writable, RegistryView view)
|
|
: this(hkey, writable, false, false, false, view) {
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a RegistryKey.
|
|
*
|
|
* This key is bound to hkey, if writable is <b>false</b> then no write operations
|
|
* will be allowed. If systemkey is set then the hkey won't be released
|
|
* when the object is GC'ed.
|
|
* The remoteKey flag when set to true indicates that we are dealing with registry entries
|
|
* on a remote machine and requires the program making these calls to have full trust.
|
|
*/
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey, bool remoteKey, bool isPerfData, RegistryView view) {
|
|
this.hkey = hkey;
|
|
this.keyName = "";
|
|
this.remoteKey = remoteKey;
|
|
this.regView = view;
|
|
if (systemkey) {
|
|
this.state |= STATE_SYSTEMKEY;
|
|
}
|
|
if (writable) {
|
|
this.state |= STATE_WRITEACCESS;
|
|
}
|
|
if (isPerfData)
|
|
this.state |= STATE_PERF_DATA;
|
|
ValidateKeyView(view);
|
|
}
|
|
|
|
/**
|
|
* Closes this key, flushes it to disk if the contents have been modified.
|
|
*/
|
|
public void Close() {
|
|
Dispose(true);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
private void Dispose(bool disposing) {
|
|
if (hkey != null) {
|
|
|
|
if (!IsSystemKey()) {
|
|
try {
|
|
hkey.Dispose();
|
|
}
|
|
catch (IOException){
|
|
// we don't really care if the handle is invalid at this point
|
|
}
|
|
finally
|
|
{
|
|
hkey = null;
|
|
}
|
|
}
|
|
else if (disposing && IsPerfDataKey()) {
|
|
// System keys should never be closed. However, we want to call RegCloseKey
|
|
// on HKEY_PERFORMANCE_DATA when called from PerformanceCounter.CloseSharedResources
|
|
// (i.e. when disposing is true) so that we release the PERFLIB cache and cause it
|
|
// to be refreshed (by re-reading the registry) when accessed subsequently.
|
|
// This is the only way we can see the just installed perf counter.
|
|
// NOTE: since HKEY_PERFORMANCE_DATA is process wide, there is inherent ---- in closing
|
|
// the key asynchronously. While Vista is smart enough to rebuild the PERFLIB resources
|
|
// in this situation the down level OSes are not. We have a small window of ---- between
|
|
// the dispose below and usage elsewhere (other threads). This is By Design.
|
|
// This is less of an issue when OS > NT5 (i.e Vista & higher), we can close the perfkey
|
|
// (to release & refresh PERFLIB resources) and the OS will rebuild PERFLIB as necessary.
|
|
SafeRegistryHandle.RegCloseKey(RegistryKey.HKEY_PERFORMANCE_DATA);
|
|
}
|
|
}
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public void Flush() {
|
|
if (hkey != null) {
|
|
if (IsDirty()) {
|
|
Win32Native.RegFlushKey(hkey);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if FEATURE_CORECLR
|
|
void IDisposable.Dispose()
|
|
#else
|
|
public void Dispose()
|
|
#endif
|
|
{
|
|
Dispose(true);
|
|
}
|
|
|
|
/**
|
|
* Creates a new subkey, or opens an existing one.
|
|
*
|
|
* @param subkey Name or path to subkey to create or open.
|
|
*
|
|
* @return the subkey, or <b>null</b> if the operation failed.
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[SuppressMessage("Microsoft.Concurrency", "CA8001", Justification = "Reviewed for thread safety")]
|
|
public RegistryKey CreateSubKey(String subkey) {
|
|
return CreateSubKey(subkey, checkMode);
|
|
}
|
|
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck)
|
|
{
|
|
return CreateSubKeyInternal(subkey, permissionCheck, null, RegistryOptions.None);
|
|
}
|
|
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistryOptions options)
|
|
{
|
|
return CreateSubKeyInternal(subkey, permissionCheck, null, options);
|
|
}
|
|
|
|
|
|
#if FEATURE_MACL
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public unsafe RegistryKey CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistrySecurity registrySecurity)
|
|
{
|
|
return CreateSubKeyInternal(subkey, permissionCheck, registrySecurity, RegistryOptions.None);
|
|
}
|
|
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public unsafe RegistryKey CreateSubKey(String subkey, RegistryKeyPermissionCheck permissionCheck, RegistryOptions registryOptions, RegistrySecurity registrySecurity)
|
|
{
|
|
return CreateSubKeyInternal(subkey, permissionCheck, registrySecurity, registryOptions);
|
|
}
|
|
#endif
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private unsafe RegistryKey CreateSubKeyInternal(String subkey, RegistryKeyPermissionCheck permissionCheck, object registrySecurityObj, RegistryOptions registryOptions)
|
|
{
|
|
ValidateKeyOptions(registryOptions);
|
|
ValidateKeyName(subkey);
|
|
ValidateKeyMode(permissionCheck);
|
|
EnsureWriteable();
|
|
subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
|
|
|
|
// only keys opened under read mode is not writable
|
|
if (!remoteKey) {
|
|
RegistryKey key = InternalOpenSubKey(subkey, (permissionCheck != RegistryKeyPermissionCheck.ReadSubTree));
|
|
if (key != null) { // Key already exits
|
|
CheckPermission(RegistryInternalCheck.CheckSubKeyWritePermission, subkey, false, RegistryKeyPermissionCheck.Default);
|
|
CheckPermission(RegistryInternalCheck.CheckSubTreePermission, subkey, false, permissionCheck);
|
|
key.checkMode = permissionCheck;
|
|
return key;
|
|
}
|
|
}
|
|
|
|
CheckPermission(RegistryInternalCheck.CheckSubKeyCreatePermission, subkey, false, RegistryKeyPermissionCheck.Default);
|
|
|
|
Win32Native.SECURITY_ATTRIBUTES secAttrs = null;
|
|
#if FEATURE_MACL
|
|
RegistrySecurity registrySecurity = (RegistrySecurity)registrySecurityObj;
|
|
// For ACL's, get the security descriptor from the RegistrySecurity.
|
|
if (registrySecurity != null) {
|
|
secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
|
|
secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
|
|
|
|
byte[] sd = registrySecurity.GetSecurityDescriptorBinaryForm();
|
|
// We allocate memory on the stack to improve the speed.
|
|
// So this part of code can't be refactored into a method.
|
|
byte* pSecDescriptor = stackalloc byte[sd.Length];
|
|
Buffer.Memcpy(pSecDescriptor, 0, sd, 0, sd.Length);
|
|
secAttrs.pSecurityDescriptor = pSecDescriptor;
|
|
}
|
|
#endif
|
|
int disposition = 0;
|
|
|
|
// By default, the new key will be writable.
|
|
SafeRegistryHandle result = null;
|
|
int ret = Win32Native.RegCreateKeyEx(hkey,
|
|
subkey,
|
|
0,
|
|
null,
|
|
(int)registryOptions /* specifies if the key is volatile */,
|
|
GetRegistryKeyAccess(permissionCheck != RegistryKeyPermissionCheck.ReadSubTree) | (int)regView,
|
|
secAttrs,
|
|
out result,
|
|
out disposition);
|
|
|
|
if (ret == 0 && !result.IsInvalid) {
|
|
RegistryKey key = new RegistryKey(result, (permissionCheck != RegistryKeyPermissionCheck.ReadSubTree), false, remoteKey, false, regView);
|
|
CheckPermission(RegistryInternalCheck.CheckSubTreePermission, subkey, false, permissionCheck);
|
|
key.checkMode = permissionCheck;
|
|
|
|
if (subkey.Length == 0)
|
|
key.keyName = keyName;
|
|
else
|
|
key.keyName = keyName + "\\" + subkey;
|
|
return key;
|
|
}
|
|
else if (ret != 0) // syscall failed, ret is an error code.
|
|
Win32Error(ret, keyName + "\\" + subkey); // Access denied?
|
|
|
|
BCLDebug.Assert(false, "Unexpected code path in RegistryKey::CreateSubKey");
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Deletes the specified subkey. Will throw an exception if the subkey has
|
|
* subkeys. To delete a tree of subkeys use, DeleteSubKeyTree.
|
|
*
|
|
* @param subkey SubKey to delete.
|
|
*
|
|
* @exception InvalidOperationException thrown if the subkey has child subkeys.
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public void DeleteSubKey(String subkey) {
|
|
DeleteSubKey(subkey, true);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public void DeleteSubKey(String subkey, bool throwOnMissingSubKey) {
|
|
ValidateKeyName(subkey);
|
|
EnsureWriteable();
|
|
subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
|
|
CheckPermission(RegistryInternalCheck.CheckSubKeyWritePermission, subkey, false, RegistryKeyPermissionCheck.Default);
|
|
|
|
// Open the key we are deleting and check for children. Be sure to
|
|
// explicitly call close to avoid keeping an extra HKEY open.
|
|
//
|
|
RegistryKey key = InternalOpenSubKey(subkey,false);
|
|
if (key != null) {
|
|
try {
|
|
if (key.InternalSubKeyCount() > 0) {
|
|
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_RegRemoveSubKey);
|
|
}
|
|
}
|
|
finally {
|
|
key.Close();
|
|
}
|
|
|
|
int ret;
|
|
|
|
try {
|
|
ret = Win32Native.RegDeleteKeyEx(hkey, subkey, (int)regView, 0);
|
|
}
|
|
catch (EntryPointNotFoundException) {
|
|
//
|
|
ret = Win32Native.RegDeleteKey(hkey, subkey);
|
|
}
|
|
|
|
if (ret!=0) {
|
|
if (ret == Win32Native.ERROR_FILE_NOT_FOUND) {
|
|
if (throwOnMissingSubKey)
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyAbsent);
|
|
}
|
|
else
|
|
Win32Error(ret, null);
|
|
}
|
|
}
|
|
else { // there is no key which also means there is no subkey
|
|
if (throwOnMissingSubKey)
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyAbsent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively deletes a subkey and any child subkeys.
|
|
*
|
|
* @param subkey SubKey to delete.
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public void DeleteSubKeyTree(String subkey) {
|
|
DeleteSubKeyTree(subkey, true /*throwOnMissingSubKey*/);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public void DeleteSubKeyTree(String subkey, Boolean throwOnMissingSubKey) {
|
|
ValidateKeyName(subkey);
|
|
|
|
// Security concern: Deleting a hive's "" subkey would delete all
|
|
// of that hive's contents. Don't allow "".
|
|
if (subkey.Length==0 && IsSystemKey()) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegKeyDelHive);
|
|
}
|
|
|
|
EnsureWriteable();
|
|
|
|
subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
|
|
CheckPermission(RegistryInternalCheck.CheckSubTreeWritePermission, subkey, false, RegistryKeyPermissionCheck.Default);
|
|
|
|
RegistryKey key = InternalOpenSubKey(subkey, true);
|
|
if (key != null) {
|
|
try {
|
|
if (key.InternalSubKeyCount() > 0) {
|
|
String[] keys = key.InternalGetSubKeyNames();
|
|
|
|
for (int i=0; i<keys.Length; i++) {
|
|
key.DeleteSubKeyTreeInternal(keys[i]);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
key.Close();
|
|
}
|
|
|
|
int ret;
|
|
try {
|
|
ret = Win32Native.RegDeleteKeyEx(hkey, subkey, (int)regView, 0);
|
|
}
|
|
catch (EntryPointNotFoundException) {
|
|
//
|
|
ret = Win32Native.RegDeleteKey(hkey, subkey);
|
|
}
|
|
|
|
if (ret!=0) Win32Error(ret, null);
|
|
}
|
|
else if(throwOnMissingSubKey) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyAbsent);
|
|
}
|
|
}
|
|
|
|
// An internal version which does no security checks or argument checking. Skipping the
|
|
// security checks should give us a slight perf gain on large trees.
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private void DeleteSubKeyTreeInternal(string subkey) {
|
|
RegistryKey key = InternalOpenSubKey(subkey, true);
|
|
if (key != null) {
|
|
try {
|
|
if (key.InternalSubKeyCount() > 0) {
|
|
String[] keys = key.InternalGetSubKeyNames();
|
|
|
|
for (int i=0; i<keys.Length; i++) {
|
|
key.DeleteSubKeyTreeInternal(keys[i]);
|
|
}
|
|
}
|
|
}
|
|
finally {
|
|
key.Close();
|
|
}
|
|
|
|
int ret;
|
|
try {
|
|
ret = Win32Native.RegDeleteKeyEx(hkey, subkey, (int)regView, 0);
|
|
}
|
|
catch (EntryPointNotFoundException) {
|
|
//
|
|
ret = Win32Native.RegDeleteKey(hkey, subkey);
|
|
}
|
|
if (ret!=0) Win32Error(ret, null);
|
|
}
|
|
else {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyAbsent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes the specified value from this key.
|
|
*
|
|
* @param name Name of value to delete.
|
|
*/
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
public void DeleteValue(String name) {
|
|
DeleteValue(name, true);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.None)]
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
|
public void DeleteValue(String name, bool throwOnMissingValue) {
|
|
EnsureWriteable();
|
|
CheckPermission(RegistryInternalCheck.CheckValueWritePermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
int errorCode = Win32Native.RegDeleteValue(hkey, name);
|
|
|
|
//
|
|
// From windows 2003 server, if the name is too long we will get error code ERROR_FILENAME_EXCED_RANGE
|
|
// This still means the name doesn't exist. We need to be consistent with previous OS.
|
|
//
|
|
if (errorCode == Win32Native.ERROR_FILE_NOT_FOUND || errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE) {
|
|
if (throwOnMissingValue) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSubKeyValueAbsent);
|
|
}
|
|
// Otherwise, just return giving no indication to the user.
|
|
// (For compatibility)
|
|
}
|
|
// We really should throw an exception here if errorCode was bad,
|
|
// but we can't for compatibility reasons.
|
|
BCLDebug.Correctness(errorCode == 0, "RegDeleteValue failed. Here's your error code: "+errorCode);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a new RegistryKey that represents the requested key. Valid
|
|
* values are:
|
|
*
|
|
* HKEY_CLASSES_ROOT,
|
|
* HKEY_CURRENT_USER,
|
|
* HKEY_LOCAL_MACHINE,
|
|
* HKEY_USERS,
|
|
* HKEY_PERFORMANCE_DATA,
|
|
* HKEY_CURRENT_CONFIG,
|
|
* HKEY_DYN_DATA.
|
|
*
|
|
* @param hKey HKEY_* to open.
|
|
*
|
|
* @return the RegistryKey requested.
|
|
*/
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal static RegistryKey GetBaseKey(IntPtr hKey) {
|
|
return GetBaseKey(hKey, RegistryView.Default);
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal static RegistryKey GetBaseKey(IntPtr hKey, RegistryView view) {
|
|
|
|
int index = ((int)hKey) & 0x0FFFFFFF;
|
|
BCLDebug.Assert(index >= 0 && index < hkeyNames.Length, "index is out of range!");
|
|
BCLDebug.Assert((((int)hKey) & 0xFFFFFFF0) == 0x80000000, "Invalid hkey value!");
|
|
|
|
bool isPerf = hKey == HKEY_PERFORMANCE_DATA;
|
|
// only mark the SafeHandle as ownsHandle if the key is HKEY_PERFORMANCE_DATA.
|
|
SafeRegistryHandle srh = new SafeRegistryHandle(hKey, isPerf);
|
|
|
|
RegistryKey key = new RegistryKey(srh, true, true,false, isPerf, view);
|
|
key.checkMode = RegistryKeyPermissionCheck.Default;
|
|
key.keyName = hkeyNames[index];
|
|
return key;
|
|
}
|
|
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[ComVisible(false)]
|
|
public static RegistryKey OpenBaseKey(RegistryHive hKey, RegistryView view) {
|
|
ValidateKeyView(view);
|
|
CheckUnmanagedCodePermission();
|
|
return GetBaseKey((IntPtr)((int)hKey), view);
|
|
}
|
|
|
|
/**
|
|
* Retrieves a new RegistryKey that represents the requested key on a foreign
|
|
* machine. Valid values for hKey are members of the RegistryHive enum, or
|
|
* Win32 integers such as:
|
|
*
|
|
* HKEY_CLASSES_ROOT,
|
|
* HKEY_CURRENT_USER,
|
|
* HKEY_LOCAL_MACHINE,
|
|
* HKEY_USERS,
|
|
* HKEY_PERFORMANCE_DATA,
|
|
* HKEY_CURRENT_CONFIG,
|
|
* HKEY_DYN_DATA.
|
|
*
|
|
* @param hKey HKEY_* to open.
|
|
* @param machineName the machine to connect to
|
|
*
|
|
* @return the RegistryKey requested.
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, String machineName) {
|
|
return OpenRemoteBaseKey(hKey, machineName, RegistryView.Default);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, String machineName, RegistryView view) {
|
|
if (machineName==null)
|
|
throw new ArgumentNullException("machineName");
|
|
int index = (int)hKey & 0x0FFFFFFF;
|
|
if (index < 0 || index >= hkeyNames.Length || ((int)hKey & 0xFFFFFFF0) != 0x80000000) {
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_RegKeyOutOfRange"));
|
|
}
|
|
ValidateKeyView(view);
|
|
|
|
CheckUnmanagedCodePermission();
|
|
// connect to the specified remote registry
|
|
SafeRegistryHandle foreignHKey = null;
|
|
int ret = Win32Native.RegConnectRegistry(machineName, new SafeRegistryHandle(new IntPtr((int)hKey), false), out foreignHKey);
|
|
|
|
if (ret == Win32Native.ERROR_DLL_INIT_FAILED)
|
|
// return value indicates an error occurred
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_DllInitFailure"));
|
|
|
|
if (ret != 0)
|
|
Win32ErrorStatic(ret, null);
|
|
|
|
if (foreignHKey.IsInvalid)
|
|
// return value indicates an error occurred
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_RegKeyNoRemoteConnect", machineName));
|
|
|
|
RegistryKey key = new RegistryKey(foreignHKey, true, false, true, ((IntPtr) hKey) == HKEY_PERFORMANCE_DATA, view);
|
|
key.checkMode = RegistryKeyPermissionCheck.Default;
|
|
key.keyName = hkeyNames[index];
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a subkey. If readonly is <b>true</b>, then the subkey is opened with
|
|
* read-only access.
|
|
*
|
|
* @param name Name or path of subkey to open.
|
|
* @param readonly Set to <b>true</b> if you only need readonly access.
|
|
*
|
|
* @return the Subkey requested, or <b>null</b> if the operation failed.
|
|
*/
|
|
#if FEATURE_CORECLR
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
#else
|
|
[System.Security.SecuritySafeCritical]
|
|
#endif
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey OpenSubKey(string name, bool writable ) {
|
|
ValidateKeyName(name);
|
|
EnsureNotDisposed();
|
|
name = FixupName(name); // Fixup multiple slashes to a single slash
|
|
|
|
CheckPermission(RegistryInternalCheck.CheckOpenSubKeyWithWritablePermission, name, writable, RegistryKeyPermissionCheck.Default);
|
|
SafeRegistryHandle result = null;
|
|
int ret = Win32Native.RegOpenKeyEx(hkey,
|
|
name,
|
|
0,
|
|
GetRegistryKeyAccess(writable) | (int)regView,
|
|
out result);
|
|
|
|
if (ret == 0 && !result.IsInvalid) {
|
|
RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView);
|
|
key.checkMode = GetSubKeyPermissonCheck(writable);
|
|
key.keyName = keyName + "\\" + name;
|
|
return key;
|
|
}
|
|
|
|
// Return null if we didn't find the key.
|
|
if (ret == Win32Native.ERROR_ACCESS_DENIED || ret == Win32Native.ERROR_BAD_IMPERSONATION_LEVEL) {
|
|
// We need to throw SecurityException here for compatibility reasons,
|
|
// although UnauthorizedAccessException will make more sense.
|
|
ThrowHelper.ThrowSecurityException(ExceptionResource.Security_RegistryPermission);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#if FEATURE_MACL
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey OpenSubKey(String name, RegistryKeyPermissionCheck permissionCheck) {
|
|
ValidateKeyMode(permissionCheck);
|
|
return InternalOpenSubKey(name, permissionCheck, GetRegistryKeyAccess(permissionCheck));
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey OpenSubKey(String name, RegistryKeyPermissionCheck permissionCheck, RegistryRights rights) {
|
|
return InternalOpenSubKey(name, permissionCheck, (int)rights);
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
private RegistryKey InternalOpenSubKey(String name, RegistryKeyPermissionCheck permissionCheck, int rights) {
|
|
ValidateKeyName(name);
|
|
ValidateKeyMode(permissionCheck);
|
|
|
|
ValidateKeyRights(rights);
|
|
|
|
EnsureNotDisposed();
|
|
name = FixupName(name); // Fixup multiple slashes to a single slash
|
|
|
|
CheckPermission(RegistryInternalCheck.CheckOpenSubKeyPermission, name, false, permissionCheck);
|
|
CheckPermission(RegistryInternalCheck.CheckSubTreePermission, name, false, permissionCheck);
|
|
SafeRegistryHandle result = null;
|
|
int ret = Win32Native.RegOpenKeyEx(hkey, name, 0, (rights | (int)regView), out result);
|
|
if (ret == 0 && !result.IsInvalid) {
|
|
RegistryKey key = new RegistryKey(result, (permissionCheck == RegistryKeyPermissionCheck.ReadWriteSubTree), false, remoteKey, false, regView);
|
|
key.keyName = keyName + "\\" + name;
|
|
key.checkMode = permissionCheck;
|
|
return key;
|
|
}
|
|
|
|
// Return null if we didn't find the key.
|
|
if (ret == Win32Native.ERROR_ACCESS_DENIED || ret == Win32Native.ERROR_BAD_IMPERSONATION_LEVEL) {
|
|
// We need to throw SecurityException here for compatiblity reason,
|
|
// although UnauthorizedAccessException will make more sense.
|
|
ThrowHelper.ThrowSecurityException(ExceptionResource.Security_RegistryPermission);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
#endif
|
|
|
|
// This required no security checks. This is to get around the Deleting SubKeys which only require
|
|
// write permission. They call OpenSubKey which required read. Now instead call this function w/o security checks
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
internal RegistryKey InternalOpenSubKey(String name, bool writable) {
|
|
ValidateKeyName(name);
|
|
EnsureNotDisposed();
|
|
|
|
SafeRegistryHandle result = null;
|
|
int ret = Win32Native.RegOpenKeyEx(hkey,
|
|
name,
|
|
0,
|
|
GetRegistryKeyAccess(writable) | (int)regView,
|
|
out result);
|
|
|
|
if (ret == 0 && !result.IsInvalid) {
|
|
RegistryKey key = new RegistryKey(result, writable, false, remoteKey, false, regView);
|
|
key.keyName = keyName + "\\" + name;
|
|
return key;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns a subkey with read only permissions.
|
|
*
|
|
* @param name Name or path of subkey to open.
|
|
*
|
|
* @return the Subkey requested, or <b>null</b> if the operation failed.
|
|
*/
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
public RegistryKey OpenSubKey(String name) {
|
|
return OpenSubKey(name, false);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the count of subkeys.
|
|
*
|
|
* @return a count of subkeys.
|
|
*/
|
|
public int SubKeyCount {
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
get {
|
|
CheckPermission(RegistryInternalCheck.CheckKeyReadPermission, null, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalSubKeyCount();
|
|
}
|
|
}
|
|
|
|
[ComVisible(false)]
|
|
public RegistryView View {
|
|
[System.Security.SecuritySafeCritical]
|
|
get {
|
|
EnsureNotDisposed();
|
|
return regView;
|
|
}
|
|
}
|
|
|
|
#if !FEATURE_CORECLR
|
|
[ComVisible(false)]
|
|
public SafeRegistryHandle Handle {
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[System.Security.SecurityCritical]
|
|
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
|
get {
|
|
EnsureNotDisposed();
|
|
int ret = Win32Native.ERROR_INVALID_HANDLE;
|
|
if (IsSystemKey()) {
|
|
IntPtr baseKey = (IntPtr)0;
|
|
switch (keyName) {
|
|
case "HKEY_CLASSES_ROOT":
|
|
baseKey = HKEY_CLASSES_ROOT;
|
|
break;
|
|
case "HKEY_CURRENT_USER":
|
|
baseKey = HKEY_CURRENT_USER;
|
|
break;
|
|
case "HKEY_LOCAL_MACHINE":
|
|
baseKey = HKEY_LOCAL_MACHINE;
|
|
break;
|
|
case "HKEY_USERS":
|
|
baseKey = HKEY_USERS;
|
|
break;
|
|
case "HKEY_PERFORMANCE_DATA":
|
|
baseKey = HKEY_PERFORMANCE_DATA;
|
|
break;
|
|
case "HKEY_CURRENT_CONFIG":
|
|
baseKey = HKEY_CURRENT_CONFIG;
|
|
break;
|
|
case "HKEY_DYN_DATA":
|
|
baseKey = HKEY_DYN_DATA;
|
|
break;
|
|
default:
|
|
Win32Error(ret, null);
|
|
break;
|
|
}
|
|
// open the base key so that RegistryKey.Handle will return a valid handle
|
|
SafeRegistryHandle result;
|
|
ret = Win32Native.RegOpenKeyEx(baseKey,
|
|
null,
|
|
0,
|
|
GetRegistryKeyAccess(IsWritable()) | (int)regView,
|
|
out result);
|
|
|
|
if (ret == 0 && !result.IsInvalid) {
|
|
return result;
|
|
}
|
|
else {
|
|
Win32Error(ret, null);
|
|
}
|
|
}
|
|
else {
|
|
return hkey;
|
|
}
|
|
throw new IOException(Win32Native.GetMessage(ret), ret);
|
|
}
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[System.Security.SecurityCritical]
|
|
[ComVisible(false)]
|
|
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
|
public static RegistryKey FromHandle(SafeRegistryHandle handle) {
|
|
return FromHandle(handle, RegistryView.Default);
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.Machine)]
|
|
[ResourceConsumption(ResourceScope.Machine)]
|
|
[System.Security.SecurityCritical]
|
|
[ComVisible(false)]
|
|
[SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
|
public static RegistryKey FromHandle(SafeRegistryHandle handle, RegistryView view) {
|
|
if (handle == null) throw new ArgumentNullException("handle");
|
|
ValidateKeyView(view);
|
|
|
|
return new RegistryKey(handle, true /* isWritable */, view);
|
|
}
|
|
#endif
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal int InternalSubKeyCount() {
|
|
EnsureNotDisposed();
|
|
|
|
int subkeys = 0;
|
|
int junk = 0;
|
|
int ret = Win32Native.RegQueryInfoKey(hkey,
|
|
null,
|
|
null,
|
|
IntPtr.Zero,
|
|
ref subkeys, // subkeys
|
|
null,
|
|
null,
|
|
ref junk, // values
|
|
null,
|
|
null,
|
|
null,
|
|
null);
|
|
|
|
if (ret != 0)
|
|
Win32Error(ret, null);
|
|
return subkeys;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of strings containing all the subkey names.
|
|
*
|
|
* @return all subkey names.
|
|
*/
|
|
#if FEATURE_CORECLR
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
#else
|
|
[System.Security.SecuritySafeCritical]
|
|
#endif
|
|
public String[] GetSubKeyNames() {
|
|
CheckPermission(RegistryInternalCheck.CheckKeyReadPermission, null, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalGetSubKeyNames();
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal unsafe String[] InternalGetSubKeyNames() {
|
|
EnsureNotDisposed();
|
|
int subkeys = InternalSubKeyCount();
|
|
String[] names = new String[subkeys]; // Returns 0-length array if empty.
|
|
|
|
if (subkeys > 0) {
|
|
char[] name = new char[MaxKeyLength + 1];
|
|
|
|
int namelen;
|
|
|
|
fixed (char *namePtr = &name[0])
|
|
{
|
|
for (int i=0; i<subkeys; i++) {
|
|
namelen = name.Length; // Don't remove this. The API's doesn't work if this is not properly initialised.
|
|
int ret = Win32Native.RegEnumKeyEx(hkey,
|
|
i,
|
|
namePtr,
|
|
ref namelen,
|
|
null,
|
|
null,
|
|
null,
|
|
null);
|
|
if (ret != 0)
|
|
Win32Error(ret, null);
|
|
names[i] = new String(namePtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the count of values.
|
|
*
|
|
* @return a count of values.
|
|
*/
|
|
public int ValueCount {
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
get {
|
|
CheckPermission(RegistryInternalCheck.CheckKeyReadPermission, null, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalValueCount();
|
|
}
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal int InternalValueCount() {
|
|
EnsureNotDisposed();
|
|
int values = 0;
|
|
int junk = 0;
|
|
int ret = Win32Native.RegQueryInfoKey(hkey,
|
|
null,
|
|
null,
|
|
IntPtr.Zero,
|
|
ref junk, // subkeys
|
|
null,
|
|
null,
|
|
ref values, // values
|
|
null,
|
|
null,
|
|
null,
|
|
null);
|
|
if (ret != 0)
|
|
Win32Error(ret, null);
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Retrieves an array of strings containing all the value names.
|
|
*
|
|
* @return all value names.
|
|
*/
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public unsafe String[] GetValueNames() {
|
|
CheckPermission(RegistryInternalCheck.CheckKeyReadPermission, null, false, RegistryKeyPermissionCheck.Default);
|
|
EnsureNotDisposed();
|
|
|
|
int values = InternalValueCount();
|
|
String[] names = new String[values];
|
|
|
|
if (values > 0) {
|
|
char[] name = new char[MaxValueLength + 1];
|
|
int namelen;
|
|
|
|
fixed (char *namePtr = &name[0])
|
|
{
|
|
for (int i=0; i<values; i++) {
|
|
namelen = name.Length;
|
|
|
|
int ret = Win32Native.RegEnumValue(hkey,
|
|
i,
|
|
namePtr,
|
|
ref namelen,
|
|
IntPtr.Zero,
|
|
null,
|
|
null,
|
|
null);
|
|
|
|
if (ret != 0) {
|
|
// ignore ERROR_MORE_DATA if we're querying HKEY_PERFORMANCE_DATA
|
|
if (!(IsPerfDataKey() && ret == Win32Native.ERROR_MORE_DATA))
|
|
Win32Error(ret, null);
|
|
}
|
|
|
|
names[i] = new String(namePtr);
|
|
}
|
|
}
|
|
}
|
|
|
|
return names;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the specified value. <b>null</b> is returned if the value
|
|
* doesn't exist.
|
|
*
|
|
* Note that <var>name</var> can be null or "", at which point the
|
|
* unnamed or default value of this Registry key is returned, if any.
|
|
*
|
|
* @param name Name of value to retrieve.
|
|
*
|
|
* @return the data associated with the value.
|
|
*/
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public Object GetValue(String name) {
|
|
CheckPermission(RegistryInternalCheck.CheckValueReadPermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalGetValue(name, null, false, true);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the specified value. <i>defaultValue</i> is returned if the value doesn't exist.
|
|
*
|
|
* Note that <var>name</var> can be null or "", at which point the
|
|
* unnamed or default value of this Registry key is returned, if any.
|
|
* The default values for RegistryKeys are OS-dependent. NT doesn't
|
|
* have them by default, but they can exist and be of any type. On
|
|
* Win95, the default value is always an empty key of type REG_SZ.
|
|
* Win98 supports default values of any type, but defaults to REG_SZ.
|
|
*
|
|
* @param name Name of value to retrieve.
|
|
* @param defaultValue Value to return if <i>name</i> doesn't exist.
|
|
*
|
|
* @return the data associated with the value.
|
|
*/
|
|
#if FEATURE_CORECLR
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
#else
|
|
[System.Security.SecuritySafeCritical]
|
|
#endif
|
|
public Object GetValue(String name, Object defaultValue) {
|
|
CheckPermission(RegistryInternalCheck.CheckValueReadPermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalGetValue(name, defaultValue, false, true);
|
|
}
|
|
|
|
#if FEATURE_CORECLR
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
#else
|
|
[System.Security.SecuritySafeCritical]
|
|
#endif
|
|
[ComVisible(false)]
|
|
public Object GetValue(String name, Object defaultValue, RegistryValueOptions options) {
|
|
if( options < RegistryValueOptions.None || options > RegistryValueOptions.DoNotExpandEnvironmentNames) {
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_EnumIllegalVal", (int)options), "options");
|
|
}
|
|
bool doNotExpand = (options == RegistryValueOptions.DoNotExpandEnvironmentNames);
|
|
CheckPermission(RegistryInternalCheck.CheckValueReadPermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
return InternalGetValue(name, defaultValue, doNotExpand, true);
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
internal Object InternalGetValue(String name, Object defaultValue, bool doNotExpand, bool checkSecurity) {
|
|
if (checkSecurity) {
|
|
// Name can be null! It's the most common use of RegQueryValueEx
|
|
EnsureNotDisposed();
|
|
}
|
|
|
|
Object data = defaultValue;
|
|
int type = 0;
|
|
int datasize = 0;
|
|
|
|
int ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize);
|
|
|
|
if (ret != 0) {
|
|
if (IsPerfDataKey()) {
|
|
int size = 65000;
|
|
int sizeInput = size;
|
|
|
|
int r;
|
|
byte[] blob = new byte[size];
|
|
while (Win32Native.ERROR_MORE_DATA == (r = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref sizeInput))) {
|
|
if (size == Int32.MaxValue) {
|
|
// ERROR_MORE_DATA was returned however we cannot increase the buffer size beyond Int32.MaxValue
|
|
Win32Error(r, name);
|
|
}
|
|
else if (size > (Int32.MaxValue / 2)) {
|
|
// at this point in the loop "size * 2" would cause an overflow
|
|
size = Int32.MaxValue;
|
|
}
|
|
else {
|
|
size *= 2;
|
|
}
|
|
sizeInput = size;
|
|
blob = new byte[size];
|
|
}
|
|
if (r != 0)
|
|
Win32Error(r, name);
|
|
return blob;
|
|
}
|
|
else {
|
|
// For stuff like ERROR_FILE_NOT_FOUND, we want to return null (data).
|
|
// Some OS's returned ERROR_MORE_DATA even in success cases, so we
|
|
// want to continue on through the function.
|
|
if (ret != Win32Native.ERROR_MORE_DATA)
|
|
return data;
|
|
}
|
|
}
|
|
|
|
if (datasize < 0) {
|
|
// unexpected code path
|
|
BCLDebug.Assert(false, "[InternalGetValue] RegQueryValue returned ERROR_SUCCESS but gave a negative datasize");
|
|
datasize = 0;
|
|
}
|
|
|
|
|
|
switch (type) {
|
|
case Win32Native.REG_NONE:
|
|
case Win32Native.REG_DWORD_BIG_ENDIAN:
|
|
case Win32Native.REG_BINARY: {
|
|
byte[] blob = new byte[datasize];
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
|
|
data = blob;
|
|
}
|
|
break;
|
|
case Win32Native.REG_QWORD:
|
|
{ // also REG_QWORD_LITTLE_ENDIAN
|
|
if (datasize > 8) {
|
|
// prevent an AV in the edge case that datasize is larger than sizeof(long)
|
|
goto case Win32Native.REG_BINARY;
|
|
}
|
|
long blob = 0;
|
|
BCLDebug.Assert(datasize==8, "datasize==8");
|
|
// Here, datasize must be 8 when calling this
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize);
|
|
|
|
data = blob;
|
|
}
|
|
break;
|
|
case Win32Native.REG_DWORD:
|
|
{ // also REG_DWORD_LITTLE_ENDIAN
|
|
if (datasize > 4) {
|
|
// prevent an AV in the edge case that datasize is larger than sizeof(int)
|
|
goto case Win32Native.REG_QWORD;
|
|
}
|
|
int blob = 0;
|
|
BCLDebug.Assert(datasize==4, "datasize==4");
|
|
// Here, datasize must be four when calling this
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, ref blob, ref datasize);
|
|
|
|
data = blob;
|
|
}
|
|
break;
|
|
|
|
case Win32Native.REG_SZ:
|
|
{
|
|
if (datasize % 2 == 1) {
|
|
// handle the case where the registry contains an odd-byte length (corrupt data?)
|
|
try {
|
|
datasize = checked(datasize + 1);
|
|
}
|
|
catch (OverflowException e) {
|
|
throw new IOException(Environment.GetResourceString("Arg_RegGetOverflowBug"), e);
|
|
}
|
|
}
|
|
char[] blob = new char[datasize/2];
|
|
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
|
|
if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) {
|
|
data = new String(blob, 0, blob.Length - 1);
|
|
}
|
|
else {
|
|
// in the very unlikely case the data is missing null termination,
|
|
// pass in the whole char[] to prevent truncating a character
|
|
data = new String(blob);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case Win32Native.REG_EXPAND_SZ:
|
|
{
|
|
if (datasize % 2 == 1) {
|
|
// handle the case where the registry contains an odd-byte length (corrupt data?)
|
|
try {
|
|
datasize = checked(datasize + 1);
|
|
}
|
|
catch (OverflowException e) {
|
|
throw new IOException(Environment.GetResourceString("Arg_RegGetOverflowBug"), e);
|
|
}
|
|
}
|
|
char[] blob = new char[datasize/2];
|
|
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
|
|
|
|
if (blob.Length > 0 && blob[blob.Length - 1] == (char)0) {
|
|
data = new String(blob, 0, blob.Length - 1);
|
|
}
|
|
else {
|
|
// in the very unlikely case the data is missing null termination,
|
|
// pass in the whole char[] to prevent truncating a character
|
|
data = new String(blob);
|
|
}
|
|
|
|
if (!doNotExpand)
|
|
data = Environment.ExpandEnvironmentVariables((String)data);
|
|
}
|
|
break;
|
|
case Win32Native.REG_MULTI_SZ:
|
|
{
|
|
if (datasize % 2 == 1) {
|
|
// handle the case where the registry contains an odd-byte length (corrupt data?)
|
|
try {
|
|
datasize = checked(datasize + 1);
|
|
}
|
|
catch (OverflowException e) {
|
|
throw new IOException(Environment.GetResourceString("Arg_RegGetOverflowBug"), e);
|
|
}
|
|
}
|
|
char[] blob = new char[datasize/2];
|
|
|
|
ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, blob, ref datasize);
|
|
|
|
// make sure the string is null terminated before processing the data
|
|
if (blob.Length > 0 && blob[blob.Length - 1] != (char)0) {
|
|
try {
|
|
char[] newBlob = new char[checked(blob.Length + 1)];
|
|
for (int i = 0; i < blob.Length; i++) {
|
|
newBlob[i] = blob[i];
|
|
}
|
|
newBlob[newBlob.Length - 1] = (char)0;
|
|
blob = newBlob;
|
|
}
|
|
catch (OverflowException e) {
|
|
throw new IOException(Environment.GetResourceString("Arg_RegGetOverflowBug"), e);
|
|
}
|
|
blob[blob.Length - 1] = (char)0;
|
|
}
|
|
|
|
|
|
IList<String> strings = new List<String>();
|
|
int cur = 0;
|
|
int len = blob.Length;
|
|
|
|
while (ret == 0 && cur < len) {
|
|
int nextNull = cur;
|
|
while (nextNull < len && blob[nextNull] != (char)0) {
|
|
nextNull++;
|
|
}
|
|
|
|
if (nextNull < len) {
|
|
BCLDebug.Assert(blob[nextNull] == (char)0, "blob[nextNull] should be 0");
|
|
if (nextNull-cur > 0) {
|
|
strings.Add(new String(blob, cur, nextNull-cur));
|
|
}
|
|
else {
|
|
// we found an empty string. But if we're at the end of the data,
|
|
// it's just the extra null terminator.
|
|
if (nextNull != len-1)
|
|
strings.Add(String.Empty);
|
|
}
|
|
}
|
|
else {
|
|
strings.Add(new String(blob, cur, len-cur));
|
|
}
|
|
cur = nextNull+1;
|
|
}
|
|
|
|
data = new String[strings.Count];
|
|
strings.CopyTo((String[])data, 0);
|
|
}
|
|
break;
|
|
case Win32Native.REG_LINK:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
[ComVisible(false)]
|
|
public RegistryValueKind GetValueKind(string name) {
|
|
CheckPermission(RegistryInternalCheck.CheckValueReadPermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
EnsureNotDisposed();
|
|
|
|
int type = 0;
|
|
int datasize = 0;
|
|
int ret = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize);
|
|
if (ret != 0)
|
|
Win32Error(ret, null);
|
|
if (type == Win32Native.REG_NONE)
|
|
return RegistryValueKind.None;
|
|
else if (!Enum.IsDefined(typeof(RegistryValueKind), type))
|
|
return RegistryValueKind.Unknown;
|
|
else
|
|
return (RegistryValueKind) type;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current state of the dirty property.
|
|
*
|
|
* A key is marked as dirty if any operation has occured that modifies the
|
|
* contents of the key.
|
|
*
|
|
* @return <b>true</b> if the key has been modified.
|
|
*/
|
|
private bool IsDirty() {
|
|
return (this.state & STATE_DIRTY) != 0;
|
|
}
|
|
|
|
private bool IsSystemKey() {
|
|
return (this.state & STATE_SYSTEMKEY) != 0;
|
|
}
|
|
|
|
private bool IsWritable() {
|
|
return (this.state & STATE_WRITEACCESS) != 0;
|
|
}
|
|
|
|
private bool IsPerfDataKey() {
|
|
return (this.state & STATE_PERF_DATA) != 0;
|
|
}
|
|
|
|
public String Name {
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
get {
|
|
EnsureNotDisposed();
|
|
return keyName;
|
|
}
|
|
}
|
|
|
|
private void SetDirty() {
|
|
this.state |= STATE_DIRTY;
|
|
}
|
|
|
|
/**
|
|
* Sets the specified value.
|
|
*
|
|
* @param name Name of value to store data in.
|
|
* @param value Data to store.
|
|
*/
|
|
public void SetValue(String name, Object value) {
|
|
SetValue(name, value, RegistryValueKind.Unknown);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] //auto-generated
|
|
[ComVisible(false)]
|
|
public unsafe void SetValue(String name, Object value, RegistryValueKind valueKind) {
|
|
if (value==null)
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.value);
|
|
|
|
if (name != null && name.Length > MaxValueLength) {
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_RegValStrLenBug"));
|
|
}
|
|
|
|
if (!Enum.IsDefined(typeof(RegistryValueKind), valueKind))
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_RegBadKeyKind"), "valueKind");
|
|
|
|
EnsureWriteable();
|
|
|
|
if (!remoteKey && ContainsRegistryValue(name)) { // Existing key
|
|
CheckPermission(RegistryInternalCheck.CheckValueWritePermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
}
|
|
else { // Creating a new value
|
|
CheckPermission(RegistryInternalCheck.CheckValueCreatePermission, name, false, RegistryKeyPermissionCheck.Default);
|
|
}
|
|
|
|
if (valueKind == RegistryValueKind.Unknown) {
|
|
// this is to maintain compatibility with the old way of autodetecting the type.
|
|
// SetValue(string, object) will come through this codepath.
|
|
valueKind = CalculateValueKind(value);
|
|
}
|
|
|
|
int ret = 0;
|
|
try {
|
|
switch (valueKind) {
|
|
case RegistryValueKind.ExpandString:
|
|
case RegistryValueKind.String:
|
|
{
|
|
String data = value.ToString();
|
|
ret = Win32Native.RegSetValueEx(hkey,
|
|
name,
|
|
0,
|
|
valueKind,
|
|
data,
|
|
checked(data.Length * 2 + 2));
|
|
break;
|
|
}
|
|
|
|
case RegistryValueKind.MultiString:
|
|
{
|
|
// Other thread might modify the input array after we calculate the buffer length.
|
|
// Make a copy of the input array to be safe.
|
|
string[] dataStrings = (string[])(((string[])value).Clone());
|
|
int sizeInBytes = 0;
|
|
|
|
// First determine the size of the array
|
|
//
|
|
for (int i=0; i<dataStrings.Length; i++) {
|
|
if (dataStrings[i] == null) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetStrArrNull);
|
|
}
|
|
sizeInBytes = checked(sizeInBytes + (dataStrings[i].Length+1) * 2);
|
|
}
|
|
sizeInBytes = checked(sizeInBytes + 2);
|
|
|
|
byte[] basePtr = new byte[sizeInBytes];
|
|
fixed(byte* b = basePtr) {
|
|
IntPtr currentPtr = new IntPtr( (void *) b);
|
|
|
|
// Write out the strings...
|
|
//
|
|
for (int i=0; i<dataStrings.Length; i++) {
|
|
// Assumes that the Strings are always null terminated.
|
|
String.InternalCopy(dataStrings[i],currentPtr,(checked(dataStrings[i].Length*2)));
|
|
currentPtr = new IntPtr((long)currentPtr + (checked(dataStrings[i].Length*2)));
|
|
*(char*)(currentPtr.ToPointer()) = '\0';
|
|
currentPtr = new IntPtr((long)currentPtr + 2);
|
|
}
|
|
|
|
*(char*)(currentPtr.ToPointer()) = '\0';
|
|
currentPtr = new IntPtr((long)currentPtr + 2);
|
|
|
|
ret = Win32Native.RegSetValueEx(hkey,
|
|
name,
|
|
0,
|
|
RegistryValueKind.MultiString,
|
|
basePtr,
|
|
sizeInBytes);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RegistryValueKind.None:
|
|
case RegistryValueKind.Binary:
|
|
byte[] dataBytes = (byte[]) value;
|
|
ret = Win32Native.RegSetValueEx(hkey,
|
|
name,
|
|
0,
|
|
(valueKind == RegistryValueKind.None ? Win32Native.REG_NONE: RegistryValueKind.Binary),
|
|
dataBytes,
|
|
dataBytes.Length);
|
|
break;
|
|
|
|
case RegistryValueKind.DWord:
|
|
{
|
|
// We need to use Convert here because we could have a boxed type cannot be
|
|
// unboxed and cast at the same time. I.e. ((int)(object)(short) 5) will fail.
|
|
int data = Convert.ToInt32(value, System.Globalization.CultureInfo.InvariantCulture);
|
|
|
|
ret = Win32Native.RegSetValueEx(hkey,
|
|
name,
|
|
0,
|
|
RegistryValueKind.DWord,
|
|
ref data,
|
|
4);
|
|
break;
|
|
}
|
|
|
|
case RegistryValueKind.QWord:
|
|
{
|
|
long data = Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture);
|
|
|
|
ret = Win32Native.RegSetValueEx(hkey,
|
|
name,
|
|
0,
|
|
RegistryValueKind.QWord,
|
|
ref data,
|
|
8);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (OverflowException) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind);
|
|
}
|
|
catch (InvalidOperationException) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind);
|
|
}
|
|
catch (FormatException) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind);
|
|
}
|
|
catch (InvalidCastException) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegSetMismatchedKind);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
SetDirty();
|
|
}
|
|
else
|
|
Win32Error(ret, null);
|
|
|
|
}
|
|
|
|
private RegistryValueKind CalculateValueKind(Object value) {
|
|
// This logic matches what used to be in SetValue(string name, object value) in the v1.0 and v1.1 days.
|
|
// Even though we could add detection for an int64 in here, we want to maintain compatibility with the
|
|
// old behavior.
|
|
if (value is Int32)
|
|
return RegistryValueKind.DWord;
|
|
else if (value is Array) {
|
|
if (value is byte[])
|
|
return RegistryValueKind.Binary;
|
|
else if (value is String[])
|
|
return RegistryValueKind.MultiString;
|
|
else
|
|
throw new ArgumentException(Environment.GetResourceString("Arg_RegSetBadArrType", value.GetType().Name));
|
|
}
|
|
else
|
|
return RegistryValueKind.String;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a string representation of this key.
|
|
*
|
|
* @return a string representing the key.
|
|
*/
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public override String ToString() {
|
|
EnsureNotDisposed();
|
|
return keyName;
|
|
}
|
|
|
|
#if FEATURE_MACL
|
|
public RegistrySecurity GetAccessControl() {
|
|
return GetAccessControl(AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public RegistrySecurity GetAccessControl(AccessControlSections includeSections) {
|
|
EnsureNotDisposed();
|
|
return new RegistrySecurity(hkey, keyName, includeSections);
|
|
}
|
|
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
public void SetAccessControl(RegistrySecurity registrySecurity) {
|
|
EnsureWriteable();
|
|
if (registrySecurity == null)
|
|
throw new ArgumentNullException("registrySecurity");
|
|
|
|
registrySecurity.Persist(hkey, keyName);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* After calling GetLastWin32Error(), it clears the last error field,
|
|
* so you must save the HResult and pass it to this method. This method
|
|
* will determine the appropriate exception to throw dependent on your
|
|
* error, and depending on the error, insert a string into the message
|
|
* gotten from the ResourceManager.
|
|
*/
|
|
[System.Security.SecuritySafeCritical] // auto-generated
|
|
internal void Win32Error(int errorCode, String str) {
|
|
switch (errorCode) {
|
|
case Win32Native.ERROR_ACCESS_DENIED:
|
|
if (str != null)
|
|
throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_RegistryKeyGeneric_Key", str));
|
|
else
|
|
throw new UnauthorizedAccessException();
|
|
|
|
case Win32Native.ERROR_INVALID_HANDLE:
|
|
/**
|
|
* For normal RegistryKey instances we dispose the SafeRegHandle and throw IOException.
|
|
* However, for HKEY_PERFORMANCE_DATA (on a local or remote machine) we avoid disposing the
|
|
* SafeRegHandle and only throw the IOException. This is to workaround reentrancy issues
|
|
* in PerformanceCounter.NextValue() where the API could throw {NullReference, ObjectDisposed, ArgumentNull}Exception
|
|
* on reentrant calls because of this error code path in RegistryKey
|
|
*
|
|
* Normally we'd make our caller synchronize access to a shared RegistryKey instead of doing something like this,
|
|
* however we shipped PerformanceCounter.NextValue() un-synchronized in v2.0RTM and customers have taken a dependency on
|
|
* this behavior (being able to simultaneously query multiple remote-machine counters on multiple threads, instead of
|
|
* having serialized access).
|
|
*
|
|
*
|
|
|
|
*/
|
|
if (!IsPerfDataKey()) {
|
|
this.hkey.SetHandleAsInvalid();
|
|
this.hkey = null;
|
|
}
|
|
goto default;
|
|
|
|
case Win32Native.ERROR_FILE_NOT_FOUND:
|
|
throw new IOException(Environment.GetResourceString("Arg_RegKeyNotFound"), errorCode);
|
|
|
|
default:
|
|
throw new IOException(Win32Native.GetMessage(errorCode), errorCode);
|
|
}
|
|
}
|
|
|
|
[SecuritySafeCritical]
|
|
internal static void Win32ErrorStatic(int errorCode, String str) {
|
|
switch (errorCode) {
|
|
case Win32Native.ERROR_ACCESS_DENIED:
|
|
if (str != null)
|
|
throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_RegistryKeyGeneric_Key", str));
|
|
else
|
|
throw new UnauthorizedAccessException();
|
|
|
|
default:
|
|
throw new IOException(Win32Native.GetMessage(errorCode), errorCode);
|
|
}
|
|
}
|
|
|
|
internal static String FixupName(String name)
|
|
{
|
|
BCLDebug.Assert(name!=null,"[FixupName]name!=null");
|
|
if (name.IndexOf('\\') == -1)
|
|
return name;
|
|
|
|
StringBuilder sb = new StringBuilder(name);
|
|
FixupPath(sb);
|
|
int temp = sb.Length - 1;
|
|
if (temp >= 0 && sb[temp] == '\\') // Remove trailing slash
|
|
sb.Length = temp;
|
|
return sb.ToString();
|
|
}
|
|
|
|
|
|
private static void FixupPath(StringBuilder path)
|
|
{
|
|
Contract.Requires(path != null);
|
|
int length = path.Length;
|
|
bool fixup = false;
|
|
char markerChar = (char)0xFFFF;
|
|
|
|
int i = 1;
|
|
while (i < length - 1)
|
|
{
|
|
if (path[i] == '\\')
|
|
{
|
|
i++;
|
|
while (i < length)
|
|
{
|
|
if (path[i] == '\\')
|
|
{
|
|
path[i] = markerChar;
|
|
i++;
|
|
fixup = true;
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (fixup)
|
|
{
|
|
i = 0;
|
|
int j = 0;
|
|
while (i < length)
|
|
{
|
|
if(path[i] == markerChar)
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
path[j] = path[i];
|
|
i++;
|
|
j++;
|
|
}
|
|
path.Length += j - i;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Read/Write/Create SubKey Permission
|
|
//
|
|
private void GetSubKeyReadPermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Read;
|
|
path = keyName + "\\" + subkeyName + "\\.";
|
|
}
|
|
private void GetSubKeyWritePermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
// If we want to open a subkey of a read-only key as writeable, we need to do the check.
|
|
access = RegistryPermissionAccess.Write;
|
|
path = keyName + "\\" + subkeyName + "\\.";
|
|
}
|
|
private void GetSubKeyCreatePermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Create;
|
|
path = keyName + "\\" + subkeyName + "\\.";
|
|
}
|
|
|
|
//
|
|
// Read/Write/ReadWrite SubTree Permission
|
|
//
|
|
private void GetSubTreeReadPermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Read;
|
|
path = keyName + "\\" + subkeyName + "\\";
|
|
}
|
|
private void GetSubTreeWritePermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Write;
|
|
path = keyName + "\\" + subkeyName + "\\";
|
|
}
|
|
private void GetSubTreeReadWritePermission(string subkeyName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Write | RegistryPermissionAccess.Read;
|
|
path = keyName + "\\" + subkeyName;
|
|
}
|
|
|
|
//
|
|
// Read/Write/Create Value Permission
|
|
//
|
|
private void GetValueReadPermission(string valueName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Read;
|
|
path = keyName+"\\"+valueName;
|
|
}
|
|
private void GetValueWritePermission(string valueName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Write;
|
|
path = keyName+"\\"+valueName;
|
|
}
|
|
private void GetValueCreatePermission(string valueName, out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Create;
|
|
path = keyName+"\\"+valueName;
|
|
}
|
|
|
|
// Read Key Permission
|
|
private void GetKeyReadPermission(out RegistryPermissionAccess access, out string path) {
|
|
access = RegistryPermissionAccess.Read;
|
|
path = keyName + "\\.";
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private void CheckPermission(RegistryInternalCheck check, string item, bool subKeyWritable, RegistryKeyPermissionCheck subKeyCheck) {
|
|
bool demand = false;
|
|
RegistryPermissionAccess access = RegistryPermissionAccess.NoAccess;
|
|
string path = null;
|
|
|
|
#if !FEATURE_CORECLR
|
|
if (CodeAccessSecurityEngine.QuickCheckForAllDemands()) {
|
|
return; // full trust fast path
|
|
}
|
|
#endif // !FEATURE_CORECLR
|
|
|
|
switch (check) {
|
|
//
|
|
// Read/Write/Create SubKey Permission
|
|
//
|
|
case RegistryInternalCheck.CheckSubKeyReadPermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode == RegistryKeyPermissionCheck.Default, "Should be called from a key opened under default mode only!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
demand = true;
|
|
GetSubKeyReadPermission(item, out access, out path);
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckSubKeyWritePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode != RegistryKeyPermissionCheck.ReadSubTree, "We shouldn't allow creating sub key under read-only key!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetSubKeyWritePermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckSubKeyCreatePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode != RegistryKeyPermissionCheck.ReadSubTree, "We shouldn't allow creating sub key under read-only key!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetSubKeyCreatePermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
//
|
|
// Read/Write/ReadWrite SubTree Permission
|
|
//
|
|
case RegistryInternalCheck.CheckSubTreeReadPermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetSubTreeReadPermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckSubTreeWritePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode != RegistryKeyPermissionCheck.ReadSubTree, "We shouldn't allow writing value to read-only key!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetSubTreeWritePermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckSubTreeReadWritePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
// If we want to open a subkey of a read-only key as writeable, we need to do the check.
|
|
demand = true;
|
|
GetSubTreeReadWritePermission(item, out access, out path);
|
|
}
|
|
break;
|
|
//
|
|
// Read/Write/Create Value Permission
|
|
//
|
|
case RegistryInternalCheck.CheckValueReadPermission:
|
|
///*** no remoteKey check ***///
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
// only need to check for default mode (dynamice check)
|
|
demand = true;
|
|
GetValueReadPermission(item, out access, out path);
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckValueWritePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode != RegistryKeyPermissionCheck.ReadSubTree, "We shouldn't allow writing value to read-only key!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
// skip the security check if the key is opened under write mode
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetValueWritePermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
case RegistryInternalCheck.CheckValueCreatePermission:
|
|
if (remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
BCLDebug.Assert(checkMode != RegistryKeyPermissionCheck.ReadSubTree, "We shouldn't allow creating value under read-only key!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
// skip the security check if the key is opened under write mode
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
demand = true;
|
|
GetValueCreatePermission(item, out access, out path);
|
|
}
|
|
}
|
|
break;
|
|
//
|
|
// CheckKeyReadPermission
|
|
//
|
|
case RegistryInternalCheck.CheckKeyReadPermission:
|
|
///*** no remoteKey check ***///
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
BCLDebug.Assert(item == null, "CheckKeyReadPermission should never have a non-null item parameter!");
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
|
|
// only need to check for default mode (dynamice check)
|
|
demand = true;
|
|
GetKeyReadPermission(out access, out path);
|
|
}
|
|
break;
|
|
//
|
|
// CheckSubTreePermission
|
|
//
|
|
case RegistryInternalCheck.CheckSubTreePermission:
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
if( subKeyCheck == RegistryKeyPermissionCheck.ReadSubTree) {
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
if( remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
demand = true;
|
|
GetSubTreeReadPermission(item, out access, out path);
|
|
}
|
|
}
|
|
}
|
|
else if(subKeyCheck == RegistryKeyPermissionCheck.ReadWriteSubTree) {
|
|
if( checkMode != RegistryKeyPermissionCheck.ReadWriteSubTree) {
|
|
if( remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
demand = true;
|
|
GetSubTreeReadWritePermission(item, out access, out path);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
//
|
|
// CheckOpenSubKeyWithWritablePermission uses the 'subKeyWritable' parameter
|
|
//
|
|
case RegistryInternalCheck.CheckOpenSubKeyWithWritablePermission:
|
|
BCLDebug.Assert(subKeyCheck == RegistryKeyPermissionCheck.Default, "subKeyCheck should be Default (unused)");
|
|
// If the parent key is not opened under default mode, we have access already.
|
|
// If the parent key is opened under default mode, we need to check for permission.
|
|
if(checkMode == RegistryKeyPermissionCheck.Default) {
|
|
if( remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
demand = true;
|
|
GetSubKeyReadPermission(item, out access, out path);
|
|
}
|
|
break;
|
|
}
|
|
if( subKeyWritable && (checkMode == RegistryKeyPermissionCheck.ReadSubTree)) {
|
|
if( remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
demand = true;
|
|
GetSubTreeReadWritePermission(item, out access, out path);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
//
|
|
// CheckOpenSubKeyPermission uses the 'subKeyCheck' parameter
|
|
//
|
|
case RegistryInternalCheck.CheckOpenSubKeyPermission:
|
|
BCLDebug.Assert(subKeyWritable == false, "subKeyWritable should be false (unused)");
|
|
if(subKeyCheck == RegistryKeyPermissionCheck.Default) {
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
if(remoteKey) {
|
|
CheckUnmanagedCodePermission();
|
|
}
|
|
else {
|
|
demand = true;
|
|
GetSubKeyReadPermission(item, out access, out path);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
BCLDebug.Assert(false, "CheckPermission default switch case should never be hit!");
|
|
break;
|
|
}
|
|
|
|
if (demand) {
|
|
new RegistryPermission(access, path).Demand();
|
|
}
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
static private void CheckUnmanagedCodePermission() {
|
|
#pragma warning disable 618
|
|
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
|
|
#pragma warning restore 618
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private bool ContainsRegistryValue(string name) {
|
|
int type = 0;
|
|
int datasize = 0;
|
|
int retval = Win32Native.RegQueryValueEx(hkey, name, null, ref type, (byte[])null, ref datasize);
|
|
return retval == 0;
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private void EnsureNotDisposed(){
|
|
if (hkey == null) {
|
|
ThrowHelper.ThrowObjectDisposedException(keyName, ExceptionResource.ObjectDisposed_RegKeyClosed);
|
|
}
|
|
}
|
|
|
|
[System.Security.SecurityCritical] // auto-generated
|
|
private void EnsureWriteable() {
|
|
EnsureNotDisposed();
|
|
if (!IsWritable()) {
|
|
ThrowHelper.ThrowUnauthorizedAccessException(ExceptionResource.UnauthorizedAccess_RegistryNoWrite);
|
|
}
|
|
}
|
|
|
|
static int GetRegistryKeyAccess(bool isWritable) {
|
|
int winAccess;
|
|
if (!isWritable) {
|
|
winAccess = Win32Native.KEY_READ;
|
|
}
|
|
else {
|
|
winAccess = Win32Native.KEY_READ | Win32Native.KEY_WRITE;
|
|
}
|
|
|
|
return winAccess;
|
|
}
|
|
|
|
static int GetRegistryKeyAccess(RegistryKeyPermissionCheck mode) {
|
|
int winAccess = 0;
|
|
switch(mode) {
|
|
case RegistryKeyPermissionCheck.ReadSubTree:
|
|
case RegistryKeyPermissionCheck.Default:
|
|
winAccess = Win32Native.KEY_READ;
|
|
break;
|
|
|
|
case RegistryKeyPermissionCheck.ReadWriteSubTree:
|
|
winAccess = Win32Native.KEY_READ| Win32Native.KEY_WRITE;
|
|
break;
|
|
|
|
default:
|
|
BCLDebug.Assert(false, "unexpected code path");
|
|
break;
|
|
}
|
|
|
|
return winAccess;
|
|
}
|
|
|
|
private RegistryKeyPermissionCheck GetSubKeyPermissonCheck(bool subkeyWritable) {
|
|
if( checkMode == RegistryKeyPermissionCheck.Default) {
|
|
return checkMode;
|
|
}
|
|
|
|
if(subkeyWritable) {
|
|
return RegistryKeyPermissionCheck.ReadWriteSubTree;
|
|
}
|
|
else {
|
|
return RegistryKeyPermissionCheck.ReadSubTree;
|
|
}
|
|
}
|
|
|
|
static private void ValidateKeyName(string name) {
|
|
Contract.Ensures(name != null);
|
|
if (name == null) {
|
|
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.name);
|
|
}
|
|
|
|
int nextSlash = name.IndexOf("\\", StringComparison.OrdinalIgnoreCase);
|
|
int current = 0;
|
|
while (nextSlash != -1) {
|
|
if ((nextSlash - current) > MaxKeyLength)
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegKeyStrLenBug);
|
|
|
|
current = nextSlash + 1;
|
|
nextSlash = name.IndexOf("\\", current, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
if ((name.Length - current) > MaxKeyLength)
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RegKeyStrLenBug);
|
|
|
|
}
|
|
|
|
static private void ValidateKeyMode(RegistryKeyPermissionCheck mode) {
|
|
if( mode < RegistryKeyPermissionCheck.Default || mode > RegistryKeyPermissionCheck.ReadWriteSubTree) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidRegistryKeyPermissionCheck, ExceptionArgument.mode);
|
|
}
|
|
}
|
|
|
|
static private void ValidateKeyOptions(RegistryOptions options) {
|
|
if (options < RegistryOptions.None || options > RegistryOptions.Volatile) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidRegistryOptionsCheck, ExceptionArgument.options);
|
|
}
|
|
}
|
|
|
|
static private void ValidateKeyView(RegistryView view) {
|
|
if (view != RegistryView.Default && view != RegistryView.Registry32 && view != RegistryView.Registry64) {
|
|
ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidRegistryViewCheck, ExceptionArgument.view);
|
|
}
|
|
}
|
|
|
|
|
|
#if FEATURE_MACL
|
|
static private void ValidateKeyRights(int rights) {
|
|
if(0 != (rights & ~((int)RegistryRights.FullControl))) {
|
|
// We need to throw SecurityException here for compatiblity reason,
|
|
// although UnauthorizedAccessException will make more sense.
|
|
ThrowHelper.ThrowSecurityException(ExceptionResource.Security_RegistryPermission);
|
|
}
|
|
}
|
|
#endif
|
|
// Win32 constants for error handling
|
|
private const int FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
|
|
private const int FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
|
|
private const int FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000;
|
|
}
|
|
|
|
[Flags]
|
|
public enum RegistryValueOptions {
|
|
None = 0,
|
|
DoNotExpandEnvironmentNames = 1
|
|
}
|
|
|
|
// the name for this API is meant to mimic FileMode, which has similar values
|
|
|
|
public enum RegistryKeyPermissionCheck {
|
|
Default = 0,
|
|
ReadSubTree = 1,
|
|
ReadWriteSubTree = 2
|
|
}
|
|
|
|
#endif // !FEATURE_PAL
|
|
}
|