diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs
index bb154ebf889cc..cbcf4dbf05c25 100644
--- a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs
+++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegEnumKeyEx.cs
@@ -13,10 +13,10 @@ internal static partial class Interop
internal static partial class Advapi32
{
[LibraryImport(Libraries.Advapi32, EntryPoint = "RegEnumKeyExW", StringMarshalling = StringMarshalling.Utf16)]
- internal static partial int RegEnumKeyEx(
+ internal static unsafe partial int RegEnumKeyEx(
SafeRegistryHandle hKey,
int dwIndex,
- [Out] char[] lpName,
+ ref char lpName,
ref int lpcbName,
int[]? lpReserved,
[Out] char[]? lpClass,
diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj b/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
index bd3f1cbcf2773..5920905308d2f 100644
--- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
+++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
@@ -10,29 +10,15 @@
$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))
SR.PlatformNotSupported_Registry
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs
deleted file mode 100644
index f133f0d0c4dd6..0000000000000
--- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.Windows.cs
+++ /dev/null
@@ -1,967 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Win32.SafeHandles;
-using System;
-using System.Buffers;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text;
-
-/*
- Note on transaction support:
- Eventually we will want to add support for NT's transactions to our
- RegistryKey API's. 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
-{
- public sealed partial class RegistryKey : MarshalByRefObject, IDisposable
- {
- private static void ClosePerfDataKey()
- {
- // 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 race 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 race 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.
- Interop.Advapi32.RegCloseKey(HKEY_PERFORMANCE_DATA);
- }
-
- private void FlushCore()
- {
- if (_hkey != null && IsDirty())
- {
- Interop.Advapi32.RegFlushKey(_hkey);
- }
- }
-
- private unsafe RegistryKey CreateSubKeyInternalCore(string subkey, RegistryKeyPermissionCheck permissionCheck, RegistryOptions registryOptions)
- {
- Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;
-
- // By default, the new key will be writable.
- int ret = Interop.Advapi32.RegCreateKeyEx(_hkey,
- subkey,
- 0,
- null,
- (int)registryOptions /* specifies if the key is volatile */,
- GetRegistryKeyAccess(permissionCheck != RegistryKeyPermissionCheck.ReadSubTree) | (int)_regView,
- ref secAttrs,
- out SafeRegistryHandle result,
- out int _);
-
- if (ret == 0 && !result.IsInvalid)
- {
- RegistryKey key = new RegistryKey(result, (permissionCheck != RegistryKeyPermissionCheck.ReadSubTree), false, _remoteKey, false, _regView);
- key._checkMode = permissionCheck;
-
- if (subkey.Length == 0)
- {
- key._keyName = _keyName;
- }
- else
- {
- key._keyName = _keyName + "\\" + subkey;
- }
- return key;
- }
-
- result.Dispose();
-
- if (ret != 0) // syscall failed, ret is an error code.
- {
- Win32Error(ret, _keyName + "\\" + subkey); // Access denied?
- }
-
- Debug.Fail("Unexpected code path in RegistryKey::CreateSubKey");
- return null;
- }
-
- private void DeleteSubKeyCore(string subkey, bool throwOnMissingSubKey)
- {
- int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
-
- if (ret != 0)
- {
- if (ret == Interop.Errors.ERROR_FILE_NOT_FOUND)
- {
- if (throwOnMissingSubKey)
- {
- throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
- }
- }
- else
- {
- Win32Error(ret, null);
- }
- }
- }
-
- private void DeleteSubKeyTreeCore(string subkey)
- {
- int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
- if (ret != 0)
- {
- Win32Error(ret, null);
- }
- }
-
- private void DeleteValueCore(string name, bool throwOnMissingValue)
- {
- int errorCode = Interop.Advapi32.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 == Interop.Errors.ERROR_FILE_NOT_FOUND ||
- errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
- {
- if (throwOnMissingValue)
- {
- throw new ArgumentException(SR.Arg_RegSubKeyValueAbsent);
- }
- else
- {
- // Otherwise, reset and just return giving no indication to the user.
- // (For compatibility)
- errorCode = 0;
- }
- }
- // We really should throw an exception here if errorCode was bad,
- // but we can't for compatibility reasons.
- Debug.Assert(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_* to open.
- /// Which view over the registry to employ.
- /// The RegistryKey requested.
- private static RegistryKey OpenBaseKeyCore(RegistryHive hKeyHive, RegistryView view)
- {
- nint hKey = (int)hKeyHive;
-
- int index = ((int)hKey) & 0x0FFFFFFF;
- Debug.Assert(index >= 0 && index < s_hkeyNames.Length, "index is out of range!");
- Debug.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 = s_hkeyNames[index];
- return key;
- }
-
- private static RegistryKey OpenRemoteBaseKeyCore(RegistryHive hKey, string machineName, RegistryView view)
- {
- int index = (int)hKey & 0x0FFFFFFF;
- if (index < 0 || index >= s_hkeyNames.Length || ((int)hKey & 0xFFFFFFF0) != 0x80000000)
- {
- throw new ArgumentException(SR.Arg_RegKeyOutOfRange);
- }
-
- // connect to the specified remote registry
- int ret = Interop.Advapi32.RegConnectRegistry(machineName, new IntPtr((int)hKey), out SafeRegistryHandle foreignHKey);
- if (ret == 0 && !foreignHKey.IsInvalid)
- {
- RegistryKey key = new RegistryKey(foreignHKey, true, false, true, ((IntPtr)hKey) == HKEY_PERFORMANCE_DATA, view);
- key._checkMode = RegistryKeyPermissionCheck.Default;
- key._keyName = s_hkeyNames[index];
- return key;
- }
-
- foreignHKey.Dispose();
-
- if (ret != 0)
- {
- if (ret == Interop.Errors.ERROR_DLL_INIT_FAILED)
- {
- // return value indicates an error occurred
- throw new ArgumentException(SR.Arg_DllInitFailure);
- }
-
- Win32ErrorStatic(ret, null);
- }
-
- // return value indicates an error occurred
- throw new ArgumentException(SR.Format(SR.Arg_RegKeyNoRemoteConnect, machineName));
- }
-
- private RegistryKey? InternalOpenSubKeyCore(string name, RegistryKeyPermissionCheck permissionCheck, int rights)
- {
- int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, (rights | (int)_regView), out SafeRegistryHandle 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;
- }
-
- result.Dispose();
-
- if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
- {
- // We need to throw SecurityException here for compatibility reason,
- // although UnauthorizedAccessException will make more sense.
- throw new SecurityException(SR.Security_RegistryPermission);
- }
-
- // Return null if we didn't find the key.
- return null;
- }
-
- private RegistryKey? InternalOpenSubKeyCore(string name, bool writable)
- {
- int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, (GetRegistryKeyAccess(writable) | (int)_regView), out SafeRegistryHandle result);
- if (ret == 0 && !result.IsInvalid)
- {
- RegistryKey key = new RegistryKey(result, writable, false, _remoteKey, false, _regView);
- key._checkMode = GetSubKeyPermissionCheck(writable);
- key._keyName = _keyName + "\\" + name;
- return key;
- }
-
- result.Dispose();
-
- if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
- {
- // We need to throw SecurityException here for compatibility reasons,
- // although UnauthorizedAccessException will make more sense.
- throw new SecurityException(SR.Security_RegistryPermission);
- }
-
- // Return null if we didn't find the key.
- return null;
- }
-
- internal RegistryKey? InternalOpenSubKeyWithoutSecurityChecksCore(string name, bool writable)
- {
- int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, (GetRegistryKeyAccess(writable) | (int)_regView), out SafeRegistryHandle result);
- if (ret == 0 && !result.IsInvalid)
- {
- RegistryKey key = new RegistryKey(result, writable, false, _remoteKey, false, _regView);
- key._keyName = _keyName + "\\" + name;
- return key;
- }
-
- result.Dispose();
-
- return null;
- }
-
- private SafeRegistryHandle SystemKeyHandle
- {
- get
- {
- Debug.Assert(IsSystemKey());
-
- int ret = Interop.Errors.ERROR_INVALID_HANDLE;
- 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;
- default:
- Win32Error(ret, null);
- break;
- }
-
- // open the base key so that RegistryKey.Handle will return a valid handle
- ret = Interop.Advapi32.RegOpenKeyEx(baseKey,
- null,
- 0,
- GetRegistryKeyAccess(IsWritable()) | (int)_regView,
- out SafeRegistryHandle result);
-
- if (ret != 0 || result.IsInvalid)
- {
- result.Dispose();
- Win32Error(ret, null);
- }
-
- return result;
- }
- }
-
- private int InternalSubKeyCountCore()
- {
- int subkeys = 0;
- int junk = 0;
- int ret = Interop.Advapi32.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;
- }
-
- private string[] InternalGetSubKeyNamesCore(int subkeys)
- {
- var names = new List(subkeys);
- char[] name = ArrayPool.Shared.Rent(MaxKeyLength + 1);
-
- try
- {
- int result;
- int nameLength = name.Length;
-
- while ((result = Interop.Advapi32.RegEnumKeyEx(
- _hkey,
- names.Count,
- name,
- ref nameLength,
- null,
- null,
- null,
- null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
- {
- switch (result)
- {
- case Interop.Errors.ERROR_SUCCESS:
- names.Add(new string(name, 0, nameLength));
- nameLength = name.Length;
- break;
- default:
- // Throw the error
- Win32Error(result, null);
- break;
- }
- }
- }
- finally
- {
- ArrayPool.Shared.Return(name);
- }
-
- return names.ToArray();
- }
-
- private int InternalValueCountCore()
- {
- int values = 0;
- int junk = 0;
- int ret = Interop.Advapi32.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.
- /// All value names.
- private unsafe string[] GetValueNamesCore(int values)
- {
- var names = new List(values);
-
- // Names in the registry aren't usually very long, although they can go to as large
- // as 16383 characters (MaxValueLength).
- //
- // Every call to RegEnumValue will allocate another buffer to get the data from
- // NtEnumerateValueKey before copying it back out to our passed in buffer. This can
- // add up quickly- we'll try to keep the memory pressure low and grow the buffer
- // only if needed.
-
- char[]? name = ArrayPool.Shared.Rent(100);
-
- try
- {
- int result;
- int nameLength = name.Length;
-
- while ((result = Interop.Advapi32.RegEnumValue(
- _hkey,
- names.Count,
- name,
- ref nameLength,
- IntPtr.Zero,
- null,
- null,
- null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
- {
- switch (result)
- {
- // The size is only ever reported back correctly in the case
- // of ERROR_SUCCESS. It will almost always be changed, however.
- case Interop.Errors.ERROR_SUCCESS:
- names.Add(new string(name, 0, nameLength));
- break;
- case Interop.Errors.ERROR_MORE_DATA:
- if (IsPerfDataKey())
- {
- // Enumerating the values for Perf keys always returns
- // ERROR_MORE_DATA, but has a valid name. Buffer does need
- // to be big enough however. 8 characters is the largest
- // known name. The size isn't returned, but the string is
- // null terminated.
- fixed (char* c = &name[0])
- {
- names.Add(new string(c));
- }
- }
- else
- {
- char[] oldName = name;
- int oldLength = oldName.Length;
- name = null;
- ArrayPool.Shared.Return(oldName);
- name = ArrayPool.Shared.Rent(checked(oldLength * 2));
- }
- break;
- default:
- // Throw the error
- Win32Error(result, null);
- break;
- }
-
- // Always set the name length back to the buffer size
- nameLength = name.Length;
- }
- }
- finally
- {
- if (name != null)
- ArrayPool.Shared.Return(name);
- }
-
- return names.ToArray();
- }
-
- [return: NotNullIfNotNull(nameof(defaultValue))]
- private unsafe object? InternalGetValueCore(string? name, object? defaultValue, bool doNotExpand)
- {
- // Create an initial stack buffer large enough to satisfy many reg keys. We need to call RegQueryValueEx
- // in order to determine the type of the value, and we can avoid further retries if all of the data can be
- // retrieved in that single call with this buffer. If we do need to grow, we grow into a pooled array. The
- // caller is always handed back a copy of this data, either in an array, a string, or a boxed integer.
- Span span = stackalloc byte[512];
- byte[]? pooledArray = null;
- if (IsPerfDataKey())
- {
- // If this is a performance key (rare usage), we're not actually retrieving data stored in the registry:
- // calling RegQueryValueEx causes the system to collect the data from the appropriate provider. The value
- // is expected to be much larger than for other keys, such that our stack-allocated space is less likely
- // to be sufficient, so we immediately grow into the ArrayPool, using the same buffer size chosen
- // in .NET Framework (until such time as a better estimate is selected). Additionally, the returned
- // length when dealing with perf data keys can't be trusted, so the buffer needs to be zero'd.
- span = pooledArray = ArrayPool.Shared.Rent(65_000);
- span.Clear();
- }
-
- try
- {
- // Loop in case we need to try again with a larger buffer size.
- while (true)
- {
- int type = 0;
- int result;
- int dataLength = span.Length;
-
- fixed (byte* lpData = &MemoryMarshal.GetReference(span))
- {
- result = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, &type, lpData, (uint*)&dataLength);
- if (dataLength < 0)
- {
- // Greater than 2GB values aren't supported.
- throw new IOException(SR.Arg_RegValueTooLarge);
- }
- }
-
- // If RegQueryValueEx told us we need a larger buffer, get one and then loop around to try again.
- if (result == Interop.Errors.ERROR_MORE_DATA)
- {
- if (IsPerfDataKey())
- {
- // In the case of a performance key, dataLength is not reliable, but we know the existing
- // size was insufficient, so double the buffer size.
- dataLength = span.Length * 2;
- }
-
- if (pooledArray is not null)
- {
- // This should only happen if the registry key was changed concurrently, such that
- // we called RegQueryValueEx with our initial buffer size that was too small, we then
- // rented a buffer of the reportedly right size and called RegQueryValueEx again, but
- // it still came back with ERROR_MORE_DATA again.
- byte[] toReturn = pooledArray;
- pooledArray = null;
- ArrayPool.Shared.Return(toReturn);
- }
-
- // Greater than 2GB values aren't supported.
- if (dataLength < 0)
- {
- throw new IOException(SR.Arg_RegValueTooLarge);
- }
-
- span = pooledArray = ArrayPool.Shared.Rent(dataLength);
- if (IsPerfDataKey())
- {
- span.Clear();
- }
-
- continue;
- }
-
- // For any other error, return the default value. This might be ERROR_FILE_NOT_FOUND if the reg key
- // wasn't found, or any other system error value for unspecified reasons. For compat, an exception
- // is thrown for perf keys rather than returning the default value.
- if (result != Interop.Errors.ERROR_SUCCESS)
- {
- if (IsPerfDataKey())
- {
- Win32Error(result, name);
- }
- return defaultValue;
- }
-
- // We only get here for a successful query of the data. Process and return the results.
- Debug.Assert((uint)dataLength <= span.Length, $"Expected {dataLength} <= {span.Length}");
- switch (type)
- {
- case Interop.Advapi32.RegistryValues.REG_NONE:
- case Interop.Advapi32.RegistryValues.REG_BINARY:
- case Interop.Advapi32.RegistryValues.REG_DWORD_BIG_ENDIAN:
- return span.Slice(0, dataLength).ToArray();
-
- case Interop.Advapi32.RegistryValues.REG_DWORD:
- case Interop.Advapi32.RegistryValues.REG_QWORD:
- return dataLength switch
- {
- 4 => MemoryMarshal.Read(span),
- 8 => MemoryMarshal.Read(span),
- _ => span.Slice(0, dataLength).ToArray(), // This shouldn't happen, but the previous implementation included it defensively.
- };
-
- case Interop.Advapi32.RegistryValues.REG_SZ:
- case Interop.Advapi32.RegistryValues.REG_EXPAND_SZ:
- case Interop.Advapi32.RegistryValues.REG_MULTI_SZ:
- {
- // Handle the case where the registry contains an odd-byte length (corrupt data?)
- // by increasing the data by a single zero byte.
- if (dataLength % 2 == 1)
- {
- if (dataLength == int.MaxValue)
- {
- throw new IOException(SR.Arg_RegValueTooLarge);
- }
-
- if (dataLength >= span.Length)
- {
- byte[] newPooled = ArrayPool.Shared.Rent(dataLength + 1);
- span.CopyTo(newPooled);
- if (pooledArray is not null)
- {
- byte[] toReturn = pooledArray;
- pooledArray = null;
- ArrayPool.Shared.Return(toReturn);
- }
- span = pooledArray = newPooled;
- }
-
- span[dataLength++] = 0;
- }
-
- // From here on, we interpret the read bytes as chars; span and dataLength should no longer be used.
- ReadOnlySpan chars = MemoryMarshal.Cast(span.Slice(0, dataLength));
-
- if (type == Interop.Advapi32.RegistryValues.REG_MULTI_SZ)
- {
- string[] strings = Array.Empty();
- int count = 0;
-
- while (chars.Length > 1 || (chars.Length == 1 && chars[0] != '\0'))
- {
- int nullPos = chars.IndexOf('\0');
- string toAdd;
- if (nullPos < 0)
- {
- toAdd = chars.ToString();
- chars = default;
- }
- else
- {
- toAdd = chars.Slice(0, nullPos).ToString();
- chars = chars.Slice(nullPos + 1);
- }
-
- if (count == strings.Length)
- {
- Array.Resize(ref strings, count == 0 ? 4 : count * 2);
- }
- strings[count++] = toAdd;
- }
-
- if (count != 0)
- {
- Array.Resize(ref strings, count);
- }
-
- return strings;
- }
- else
- {
- if (chars.Length == 0)
- {
- return string.Empty;
- }
-
- // Remove null termination if it exists.
- if (chars[^1] == 0)
- {
- chars = chars[0..^1];
- }
-
- // Get the resulting string from the bytes.
- string str = chars.ToString();
- if (type == Interop.Advapi32.RegistryValues.REG_EXPAND_SZ && !doNotExpand)
- {
- str = Environment.ExpandEnvironmentVariables(str);
- }
-
- return str;
- }
- }
-
- default:
- return defaultValue;
- }
- }
- }
- finally
- {
- if (pooledArray is not null)
- {
- ArrayPool.Shared.Return(pooledArray);
- }
- }
- }
-
- private unsafe RegistryValueKind GetValueKindCore(string? name)
- {
- int type = 0;
- int datasize = 0;
- int ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, &type, (byte*)null, (uint*)&datasize);
- if (ret != 0)
- {
- Win32Error(ret, null);
- }
-
- return
- type == Interop.Advapi32.RegistryValues.REG_NONE ? RegistryValueKind.None :
- !Enum.IsDefined(typeof(RegistryValueKind), type) ? RegistryValueKind.Unknown :
- (RegistryValueKind)type;
- }
-
- private unsafe void SetValueCore(string? name, object value, RegistryValueKind valueKind)
- {
- int ret = 0;
- try
- {
- switch (valueKind)
- {
- case RegistryValueKind.ExpandString:
- case RegistryValueKind.String:
- {
- string data = value.ToString()!;
- ret = Interop.Advapi32.RegSetValueEx(_hkey,
- name,
- 0,
- (int)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());
-
- // First determine the size of the array
- //
- // Format is null terminator between strings and final null terminator at the end.
- // e.g. str1\0str2\0str3\0\0
- //
- int sizeInChars = 1; // no matter what, we have the final null terminator.
- for (int i = 0; i < dataStrings.Length; i++)
- {
- if (dataStrings[i] == null)
- {
- throw new ArgumentException(SR.Arg_RegSetStrArrNull);
- }
- sizeInChars = checked(sizeInChars + (dataStrings[i].Length + 1));
- }
- int sizeInBytes = checked(sizeInChars * sizeof(char));
-
- // Write out the strings...
- //
- char[] dataChars = new char[sizeInChars];
- int destinationIndex = 0;
- for (int i = 0; i < dataStrings.Length; i++)
- {
- int length = dataStrings[i].Length;
- dataStrings[i].CopyTo(0, dataChars, destinationIndex, length);
- destinationIndex += (length + 1); // +1 for null terminator, which is already zero-initialized in new array.
- }
-
- ret = Interop.Advapi32.RegSetValueEx(_hkey,
- name,
- 0,
- Interop.Advapi32.RegistryValues.REG_MULTI_SZ,
- dataChars,
- sizeInBytes);
-
- break;
- }
-
- case RegistryValueKind.None:
- case RegistryValueKind.Binary:
- byte[] dataBytes = (byte[])value;
- ret = Interop.Advapi32.RegSetValueEx(_hkey,
- name,
- 0,
- (valueKind == RegistryValueKind.None ? Interop.Advapi32.RegistryValues.REG_NONE : Interop.Advapi32.RegistryValues.REG_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 = Interop.Advapi32.RegSetValueEx(_hkey,
- name,
- 0,
- Interop.Advapi32.RegistryValues.REG_DWORD,
- ref data,
- 4);
- break;
- }
-
- case RegistryValueKind.QWord:
- {
- long data = Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture);
-
- ret = Interop.Advapi32.RegSetValueEx(_hkey,
- name,
- 0,
- Interop.Advapi32.RegistryValues.REG_QWORD,
- ref data,
- 8);
- break;
- }
- }
- }
- catch (Exception exc) when (exc is OverflowException || exc is InvalidOperationException || exc is FormatException || exc is InvalidCastException)
- {
- throw new ArgumentException(SR.Arg_RegSetMismatchedKind);
- }
-
- if (ret == 0)
- {
- SetDirty();
- }
- else
- {
- Win32Error(ret, null);
- }
- }
-
- ///
- /// 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.
- ///
- private void Win32Error(int errorCode, string? str)
- {
- switch (errorCode)
- {
- case Interop.Errors.ERROR_ACCESS_DENIED:
- throw str != null ?
- new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)) :
- new UnauthorizedAccessException();
-
- case Interop.Errors.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())
- {
- _hkey.SetHandleAsInvalid();
- _hkey = null!;
- }
- goto default;
-
- case Interop.Errors.ERROR_FILE_NOT_FOUND:
- throw new IOException(SR.Arg_RegKeyNotFound, errorCode);
-
- default:
- throw new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode);
- }
- }
-
- private static void Win32ErrorStatic(int errorCode, string? str) =>
- throw errorCode switch
- {
- Interop.Errors.ERROR_ACCESS_DENIED => str != null ?
- new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)) :
- new UnauthorizedAccessException(),
-
- _ => new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode),
- };
-
- private static int GetRegistryKeyAccess(bool isWritable)
- {
- int winAccess;
- if (!isWritable)
- {
- winAccess = Interop.Advapi32.RegistryOperations.KEY_READ;
- }
- else
- {
- winAccess = Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE;
- }
-
- return winAccess;
- }
-
- private static int GetRegistryKeyAccess(RegistryKeyPermissionCheck mode)
- {
- int winAccess = 0;
- switch (mode)
- {
- case RegistryKeyPermissionCheck.ReadSubTree:
- case RegistryKeyPermissionCheck.Default:
- winAccess = Interop.Advapi32.RegistryOperations.KEY_READ;
- break;
-
- case RegistryKeyPermissionCheck.ReadWriteSubTree:
- winAccess = Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE;
- break;
-
- default:
- Debug.Fail("unexpected code path");
- break;
- }
-
- return winAccess;
- }
- }
-}
diff --git a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs
index 2d6f85015a8c4..31168f59d2719 100644
--- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs
+++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs
@@ -3,13 +3,39 @@
using Microsoft.Win32.SafeHandles;
using System;
+using System.Buffers;
+using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Runtime.InteropServices;
using System.Security;
using System.Security.AccessControl;
using System.Text;
+/*
+ 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
{
/// Registry encapsulation. To get an instance of a RegistryKey use the Registry class's static members then call OpenSubKey.
@@ -88,7 +114,10 @@ private RegistryKey(SafeRegistryHandle hkey, bool writable, bool systemkey, bool
public void Flush()
{
- FlushCore();
+ if (_hkey != null && IsDirty())
+ {
+ Interop.Advapi32.RegFlushKey(_hkey);
+ }
}
public void Close()
@@ -117,7 +146,18 @@ public void Dispose()
}
else if (IsPerfDataKey())
{
- ClosePerfDataKey();
+ // 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 race 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 race 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.
+ Interop.Advapi32.RegCloseKey(HKEY_PERFORMANCE_DATA);
}
}
}
@@ -175,7 +215,44 @@ public RegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCheck permis
}
}
- return CreateSubKeyInternalCore(subkey, permissionCheck, registryOptions);
+ Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default;
+
+ // By default, the new key will be writable.
+ int ret = Interop.Advapi32.RegCreateKeyEx(_hkey,
+ subkey,
+ 0,
+ null,
+ (int)registryOptions /* specifies if the key is volatile */,
+ GetRegistryKeyAccess(permissionCheck != RegistryKeyPermissionCheck.ReadSubTree) | (int)_regView,
+ ref secAttrs,
+ out SafeRegistryHandle result,
+ out int _);
+
+ if (ret == 0 && !result.IsInvalid)
+ {
+ RegistryKey key = new RegistryKey(result, (permissionCheck != RegistryKeyPermissionCheck.ReadSubTree), false, _remoteKey, false, _regView);
+ key._checkMode = permissionCheck;
+
+ if (subkey.Length == 0)
+ {
+ key._keyName = _keyName;
+ }
+ else
+ {
+ key._keyName = _keyName + "\\" + subkey;
+ }
+ return key;
+ }
+
+ result.Dispose();
+
+ if (ret != 0) // syscall failed, ret is an error code.
+ {
+ Win32Error(ret, _keyName + "\\" + subkey); // Access denied?
+ }
+
+ Debug.Fail("Unexpected code path in RegistryKey::CreateSubKey");
+ return null;
}
///
@@ -209,7 +286,22 @@ public void DeleteSubKey(string subkey, bool throwOnMissingSubKey)
}
}
- DeleteSubKeyCore(subkey, throwOnMissingSubKey);
+ int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
+
+ if (ret != 0)
+ {
+ if (ret == Interop.Errors.ERROR_FILE_NOT_FOUND)
+ {
+ if (throwOnMissingSubKey)
+ {
+ throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
+ }
+ }
+ else
+ {
+ Win32Error(ret, null);
+ }
+ }
}
else // there is no key which also means there is no subkey
{
@@ -295,6 +387,15 @@ private void DeleteSubKeyTreeInternal(string subkey)
}
}
+ private void DeleteSubKeyTreeCore(string subkey)
+ {
+ int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
+ if (ret != 0)
+ {
+ Win32Error(ret, null);
+ }
+ }
+
/// Deletes the specified value from this key.
/// Name of value to delete.
public void DeleteValue(string name)
@@ -305,13 +406,56 @@ public void DeleteValue(string name)
public void DeleteValue(string name, bool throwOnMissingValue)
{
EnsureWriteable();
- DeleteValueCore(name, throwOnMissingValue);
+ int errorCode = Interop.Advapi32.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 == Interop.Errors.ERROR_FILE_NOT_FOUND ||
+ errorCode == Interop.Errors.ERROR_FILENAME_EXCED_RANGE)
+ {
+ if (throwOnMissingValue)
+ {
+ throw new ArgumentException(SR.Arg_RegSubKeyValueAbsent);
+ }
+ else
+ {
+ // Otherwise, reset and just return giving no indication to the user.
+ // (For compatibility)
+ errorCode = 0;
+ }
+ }
+ // We really should throw an exception here if errorCode was bad,
+ // but we can't for compatibility reasons.
+ Debug.Assert(errorCode == 0, $"RegDeleteValue failed. Here's your error code: {errorCode}");
}
+ ///
+ /// Retrieves a new that represents the requested .
+ ///
+ /// The hive to open.
+ /// Which view over the registry to employ.
+ /// The requested.
public static RegistryKey OpenBaseKey(RegistryHive hKey, RegistryView view)
{
ValidateKeyView(view);
- return OpenBaseKeyCore(hKey, view);
+
+ nint nKey = (int)hKey;
+
+ int index = ((int)nKey) & 0x0FFFFFFF;
+ Debug.Assert(index >= 0 && index < s_hkeyNames.Length, "index is out of range!");
+ Debug.Assert((((int)nKey) & 0xFFFFFFF0) == 0x80000000, "Invalid hkey value!");
+
+ bool isPerf = nKey == HKEY_PERFORMANCE_DATA;
+
+ // only mark the SafeHandle as ownsHandle if the key is HKEY_PERFORMANCE_DATA.
+ SafeRegistryHandle srh = new SafeRegistryHandle(nKey, isPerf);
+
+ RegistryKey key = new RegistryKey(srh, true, true, false, isPerf, view);
+ key._checkMode = RegistryKeyPermissionCheck.Default;
+ key._keyName = s_hkeyNames[index];
+ return key;
}
/// Retrieves a new RegistryKey that represents the requested key on a foreign machine.
@@ -329,7 +473,37 @@ public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, string machineNam
ValidateKeyView(view);
- return OpenRemoteBaseKeyCore(hKey, machineName, view);
+ int index = (int)hKey & 0x0FFFFFFF;
+ if (index < 0 || index >= s_hkeyNames.Length || ((int)hKey & 0xFFFFFFF0) != 0x80000000)
+ {
+ throw new ArgumentException(SR.Arg_RegKeyOutOfRange);
+ }
+
+ // connect to the specified remote registry
+ int ret = Interop.Advapi32.RegConnectRegistry(machineName, new IntPtr((int)hKey), out SafeRegistryHandle foreignHKey);
+ if (ret == 0 && !foreignHKey.IsInvalid)
+ {
+ RegistryKey key = new RegistryKey(foreignHKey, true, false, true, ((IntPtr)hKey) == HKEY_PERFORMANCE_DATA, view);
+ key._checkMode = RegistryKeyPermissionCheck.Default;
+ key._keyName = s_hkeyNames[index];
+ return key;
+ }
+
+ foreignHKey.Dispose();
+
+ if (ret != 0)
+ {
+ if (ret == Interop.Errors.ERROR_DLL_INIT_FAILED)
+ {
+ // return value indicates an error occurred
+ throw new ArgumentException(SR.Arg_DllInitFailure);
+ }
+
+ Win32ErrorStatic(ret, null);
+ }
+
+ // return value indicates an error occurred
+ throw new ArgumentException(SR.Format(SR.Arg_RegKeyNoRemoteConnect, machineName));
}
/// Returns a subkey with read only permissions.
@@ -353,7 +527,26 @@ public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, string machineNam
EnsureNotDisposed();
name = FixupName(name);
- return InternalOpenSubKeyCore(name, writable);
+ int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, (GetRegistryKeyAccess(writable) | (int)_regView), out SafeRegistryHandle result);
+ if (ret == 0 && !result.IsInvalid)
+ {
+ RegistryKey key = new RegistryKey(result, writable, false, _remoteKey, false, _regView);
+ key._checkMode = GetSubKeyPermissionCheck(writable);
+ key._keyName = _keyName + "\\" + name;
+ return key;
+ }
+
+ result.Dispose();
+
+ if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
+ {
+ // We need to throw SecurityException here for compatibility reasons,
+ // although UnauthorizedAccessException will make more sense.
+ throw new SecurityException(SR.Security_RegistryPermission);
+ }
+
+ // Return null if we didn't find the key.
+ return null;
}
public RegistryKey? OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck)
@@ -365,7 +558,7 @@ public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, string machineNam
public RegistryKey? OpenSubKey(string name, RegistryRights rights)
{
- return OpenSubKey(name, this._checkMode, rights);
+ return OpenSubKey(name, _checkMode, rights);
}
public RegistryKey? OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, RegistryRights rights)
@@ -378,7 +571,26 @@ public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, string machineNam
EnsureNotDisposed();
name = FixupName(name); // Fixup multiple slashes to a single slash
- return InternalOpenSubKeyCore(name, permissionCheck, (int)rights);
+ int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, (int)rights | (int)_regView, out SafeRegistryHandle 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;
+ }
+
+ result.Dispose();
+
+ if (ret == Interop.Errors.ERROR_ACCESS_DENIED || ret == Interop.Errors.ERROR_BAD_IMPERSONATION_LEVEL)
+ {
+ // We need to throw SecurityException here for compatibility reason,
+ // although UnauthorizedAccessException will make more sense.
+ throw new SecurityException(SR.Security_RegistryPermission);
+ }
+
+ // Return null if we didn't find the key.
+ return null;
}
internal RegistryKey? InternalOpenSubKeyWithoutSecurityChecks(string name, bool writable)
@@ -386,7 +598,17 @@ public static RegistryKey OpenRemoteBaseKey(RegistryHive hKey, string machineNam
ValidateKeyName(name);
EnsureNotDisposed();
- return InternalOpenSubKeyWithoutSecurityChecksCore(name, writable);
+ int ret = Interop.Advapi32.RegOpenKeyEx(_hkey, name, 0, GetRegistryKeyAccess(writable) | (int)_regView, out SafeRegistryHandle result);
+ if (ret == 0 && !result.IsInvalid)
+ {
+ RegistryKey key = new RegistryKey(result, writable, false, _remoteKey, false, _regView);
+ key._keyName = _keyName + "\\" + name;
+ return key;
+ }
+
+ result.Dispose();
+
+ return null;
}
public RegistrySecurity GetAccessControl()
@@ -415,7 +637,27 @@ public int SubKeyCount
get
{
EnsureNotDisposed();
- return InternalSubKeyCountCore();
+ int subkeys = 0;
+ int junk = 0;
+ int ret = Interop.Advapi32.RegQueryInfoKey(_hkey,
+ null,
+ null,
+ 0,
+ ref subkeys, // subkeys
+ null,
+ null,
+ ref junk, // values
+ null,
+ null,
+ null,
+ null);
+
+ if (ret != 0)
+ {
+ Win32Error(ret, null);
+ }
+
+ return subkeys;
}
}
@@ -437,6 +679,93 @@ public SafeRegistryHandle Handle
}
}
+ private SafeRegistryHandle SystemKeyHandle
+ {
+ get
+ {
+ Debug.Assert(IsSystemKey());
+
+ int ret = Interop.Errors.ERROR_INVALID_HANDLE;
+ IntPtr baseKey = 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;
+ default:
+ Win32Error(ret, null);
+ break;
+ }
+
+ // open the base key so that RegistryKey.Handle will return a valid handle
+ ret = Interop.Advapi32.RegOpenKeyEx(baseKey,
+ null,
+ 0,
+ GetRegistryKeyAccess(IsWritable()) | (int)_regView,
+ out SafeRegistryHandle result);
+
+ if (ret != 0 || result.IsInvalid)
+ {
+ result.Dispose();
+ Win32Error(ret, null);
+ }
+
+ return result;
+ }
+ }
+
+ private static int GetRegistryKeyAccess(RegistryKeyPermissionCheck mode)
+ {
+ int winAccess = 0;
+ switch (mode)
+ {
+ case RegistryKeyPermissionCheck.ReadSubTree:
+ case RegistryKeyPermissionCheck.Default:
+ winAccess = Interop.Advapi32.RegistryOperations.KEY_READ;
+ break;
+
+ case RegistryKeyPermissionCheck.ReadWriteSubTree:
+ winAccess = Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE;
+ break;
+
+ default:
+ Debug.Fail("unexpected code path");
+ break;
+ }
+
+ return winAccess;
+ }
+
+ private static int GetRegistryKeyAccess(bool isWritable)
+ {
+ int winAccess;
+ if (!isWritable)
+ {
+ winAccess = Interop.Advapi32.RegistryOperations.KEY_READ;
+ }
+ else
+ {
+ winAccess = Interop.Advapi32.RegistryOperations.KEY_READ | Interop.Advapi32.RegistryOperations.KEY_WRITE;
+ }
+
+ return winAccess;
+ }
+
public static RegistryKey FromHandle(SafeRegistryHandle handle)
{
return FromHandle(handle, RegistryView.Default);
@@ -457,9 +786,42 @@ public string[] GetSubKeyNames()
{
EnsureNotDisposed();
int subkeys = SubKeyCount;
- return subkeys > 0 ?
- InternalGetSubKeyNamesCore(subkeys) :
- Array.Empty();
+
+ if (subkeys <= 0)
+ {
+ return Array.Empty();
+ }
+
+ List names = new List(subkeys);
+ Span name = stackalloc char[MaxKeyLength + 1];
+
+ int result;
+ int nameLength = name.Length;
+
+ while ((result = Interop.Advapi32.RegEnumKeyEx(
+ _hkey,
+ names.Count,
+ ref MemoryMarshal.GetReference(name),
+ ref nameLength,
+ null,
+ null,
+ null,
+ null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
+ {
+ switch (result)
+ {
+ case Interop.Errors.ERROR_SUCCESS:
+ names.Add(new string(name.Slice(0, nameLength)));
+ nameLength = name.Length;
+ break;
+ default:
+ // Throw the error
+ Win32Error(result, null);
+ break;
+ }
+ }
+
+ return names.ToArray();
}
/// Retrieves the count of values.
@@ -469,20 +831,115 @@ public int ValueCount
get
{
EnsureNotDisposed();
- return InternalValueCountCore();
+ int values = 0;
+ int junk = 0;
+ int ret = Interop.Advapi32.RegQueryInfoKey(_hkey,
+ null,
+ null,
+ 0,
+ 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.
/// All value names.
- public string[] GetValueNames()
+ public unsafe string[] GetValueNames()
{
EnsureNotDisposed();
int values = ValueCount;
- return values > 0 ?
- GetValueNamesCore(values) :
- Array.Empty();
+
+ if (values <= 0)
+ {
+ return Array.Empty();
+ }
+
+ var names = new List(values);
+
+ // Names in the registry aren't usually very long, although they can go to as large
+ // as 16383 characters (MaxValueLength).
+ //
+ // Every call to RegEnumValue will allocate another buffer to get the data from
+ // NtEnumerateValueKey before copying it back out to our passed in buffer. This can
+ // add up quickly- we'll try to keep the memory pressure low and grow the buffer
+ // only if needed.
+
+ char[]? name = ArrayPool.Shared.Rent(100);
+
+ try
+ {
+ int result;
+ int nameLength = name.Length;
+
+ while ((result = Interop.Advapi32.RegEnumValue(
+ _hkey,
+ names.Count,
+ name,
+ ref nameLength,
+ 0,
+ null,
+ null,
+ null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
+ {
+ switch (result)
+ {
+ // The size is only ever reported back correctly in the case
+ // of ERROR_SUCCESS. It will almost always be changed, however.
+ case Interop.Errors.ERROR_SUCCESS:
+ names.Add(new string(name, 0, nameLength));
+ break;
+ case Interop.Errors.ERROR_MORE_DATA:
+ if (IsPerfDataKey())
+ {
+ // Enumerating the values for Perf keys always returns
+ // ERROR_MORE_DATA, but has a valid name. Buffer does need
+ // to be big enough however. 8 characters is the largest
+ // known name. The size isn't returned, but the string is
+ // null terminated.
+ fixed (char* c = &name[0])
+ {
+ names.Add(new string(c));
+ }
+ }
+ else
+ {
+ char[] oldName = name;
+ int oldLength = oldName.Length;
+ name = null;
+ ArrayPool.Shared.Return(oldName);
+ name = ArrayPool.Shared.Rent(checked(oldLength * 2));
+ }
+ break;
+ default:
+ // Throw the error
+ Win32Error(result, null);
+ break;
+ }
+
+ // Always set the name length back to the buffer size
+ nameLength = name.Length;
+ }
+ }
+ finally
+ {
+ if (name != null)
+ ArrayPool.Shared.Return(name);
+ }
+
+ return names.ToArray();
}
/// Retrieves the specified value. null is returned if the value doesn't exist
@@ -527,16 +984,232 @@ public string[] GetValueNames()
}
[return: NotNullIfNotNull(nameof(defaultValue))]
- private object? InternalGetValue(string? name, object? defaultValue, bool doNotExpand)
+ private unsafe object? InternalGetValue(string? name, object? defaultValue, bool doNotExpand)
{
EnsureNotDisposed();
- return InternalGetValueCore(name, defaultValue, doNotExpand);
+
+ // Create an initial stack buffer large enough to satisfy many reg keys. We need to call RegQueryValueEx
+ // in order to determine the type of the value, and we can avoid further retries if all of the data can be
+ // retrieved in that single call with this buffer. If we do need to grow, we grow into a pooled array. The
+ // caller is always handed back a copy of this data, either in an array, a string, or a boxed integer.
+ Span span = stackalloc byte[512];
+ byte[]? pooledArray = null;
+ if (IsPerfDataKey())
+ {
+ // If this is a performance key (rare usage), we're not actually retrieving data stored in the registry:
+ // calling RegQueryValueEx causes the system to collect the data from the appropriate provider. The value
+ // is expected to be much larger than for other keys, such that our stack-allocated space is less likely
+ // to be sufficient, so we immediately grow into the ArrayPool, using the same buffer size chosen
+ // in .NET Framework (until such time as a better estimate is selected). Additionally, the returned
+ // length when dealing with perf data keys can't be trusted, so the buffer needs to be zero'd.
+ span = pooledArray = ArrayPool.Shared.Rent(65_000);
+ span.Clear();
+ }
+
+ try
+ {
+ // Loop in case we need to try again with a larger buffer size.
+ while (true)
+ {
+ int type = 0;
+ int result;
+ int dataLength = span.Length;
+
+ fixed (byte* lpData = &MemoryMarshal.GetReference(span))
+ {
+ result = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, &type, lpData, (uint*)&dataLength);
+ if (dataLength < 0)
+ {
+ // Greater than 2GB values aren't supported.
+ throw new IOException(SR.Arg_RegValueTooLarge);
+ }
+ }
+
+ // If RegQueryValueEx told us we need a larger buffer, get one and then loop around to try again.
+ if (result == Interop.Errors.ERROR_MORE_DATA)
+ {
+ if (IsPerfDataKey())
+ {
+ // In the case of a performance key, dataLength is not reliable, but we know the existing
+ // size was insufficient, so double the buffer size.
+ dataLength = span.Length * 2;
+ }
+
+ if (pooledArray is not null)
+ {
+ // This should only happen if the registry key was changed concurrently, such that
+ // we called RegQueryValueEx with our initial buffer size that was too small, we then
+ // rented a buffer of the reportedly right size and called RegQueryValueEx again, but
+ // it still came back with ERROR_MORE_DATA again.
+ byte[] toReturn = pooledArray;
+ pooledArray = null;
+ ArrayPool.Shared.Return(toReturn);
+ }
+
+ // Greater than 2GB values aren't supported.
+ if (dataLength < 0)
+ {
+ throw new IOException(SR.Arg_RegValueTooLarge);
+ }
+
+ span = pooledArray = ArrayPool.Shared.Rent(dataLength);
+ if (IsPerfDataKey())
+ {
+ span.Clear();
+ }
+
+ continue;
+ }
+
+ // For any other error, return the default value. This might be ERROR_FILE_NOT_FOUND if the reg key
+ // wasn't found, or any other system error value for unspecified reasons. For compat, an exception
+ // is thrown for perf keys rather than returning the default value.
+ if (result != Interop.Errors.ERROR_SUCCESS)
+ {
+ if (IsPerfDataKey())
+ {
+ Win32Error(result, name);
+ }
+ return defaultValue;
+ }
+
+ // We only get here for a successful query of the data. Process and return the results.
+ Debug.Assert((uint)dataLength <= span.Length, $"Expected {dataLength} <= {span.Length}");
+ switch (type)
+ {
+ case Interop.Advapi32.RegistryValues.REG_NONE:
+ case Interop.Advapi32.RegistryValues.REG_BINARY:
+ case Interop.Advapi32.RegistryValues.REG_DWORD_BIG_ENDIAN:
+ return span.Slice(0, dataLength).ToArray();
+
+ case Interop.Advapi32.RegistryValues.REG_DWORD:
+ case Interop.Advapi32.RegistryValues.REG_QWORD:
+ return dataLength switch
+ {
+ 4 => MemoryMarshal.Read(span),
+ 8 => MemoryMarshal.Read(span),
+ _ => span.Slice(0, dataLength).ToArray(), // This shouldn't happen, but the previous implementation included it defensively.
+ };
+
+ case Interop.Advapi32.RegistryValues.REG_SZ:
+ case Interop.Advapi32.RegistryValues.REG_EXPAND_SZ:
+ case Interop.Advapi32.RegistryValues.REG_MULTI_SZ:
+ {
+ // Handle the case where the registry contains an odd-byte length (corrupt data?)
+ // by increasing the data by a single zero byte.
+ if (dataLength % 2 == 1)
+ {
+ if (dataLength == int.MaxValue)
+ {
+ throw new IOException(SR.Arg_RegValueTooLarge);
+ }
+
+ if (dataLength >= span.Length)
+ {
+ byte[] newPooled = ArrayPool.Shared.Rent(dataLength + 1);
+ span.CopyTo(newPooled);
+ if (pooledArray is not null)
+ {
+ byte[] toReturn = pooledArray;
+ pooledArray = null;
+ ArrayPool.Shared.Return(toReturn);
+ }
+ span = pooledArray = newPooled;
+ }
+
+ span[dataLength++] = 0;
+ }
+
+ // From here on, we interpret the read bytes as chars; span and dataLength should no longer be used.
+ ReadOnlySpan chars = MemoryMarshal.Cast(span.Slice(0, dataLength));
+
+ if (type == Interop.Advapi32.RegistryValues.REG_MULTI_SZ)
+ {
+ string[] strings = Array.Empty();
+ int count = 0;
+
+ while (chars.Length > 1 || (chars.Length == 1 && chars[0] != '\0'))
+ {
+ int nullPos = chars.IndexOf('\0');
+ string toAdd;
+ if (nullPos < 0)
+ {
+ toAdd = chars.ToString();
+ chars = default;
+ }
+ else
+ {
+ toAdd = chars.Slice(0, nullPos).ToString();
+ chars = chars.Slice(nullPos + 1);
+ }
+
+ if (count == strings.Length)
+ {
+ Array.Resize(ref strings, count == 0 ? 4 : count * 2);
+ }
+ strings[count++] = toAdd;
+ }
+
+ if (count != 0)
+ {
+ Array.Resize(ref strings, count);
+ }
+
+ return strings;
+ }
+ else
+ {
+ if (chars.Length == 0)
+ {
+ return string.Empty;
+ }
+
+ // Remove null termination if it exists.
+ if (chars[^1] == 0)
+ {
+ chars = chars[0..^1];
+ }
+
+ // Get the resulting string from the bytes.
+ string str = chars.ToString();
+ if (type == Interop.Advapi32.RegistryValues.REG_EXPAND_SZ && !doNotExpand)
+ {
+ str = Environment.ExpandEnvironmentVariables(str);
+ }
+
+ return str;
+ }
+ }
+
+ default:
+ return defaultValue;
+ }
+ }
+ }
+ finally
+ {
+ if (pooledArray is not null)
+ {
+ ArrayPool.Shared.Return(pooledArray);
+ }
+ }
}
- public RegistryValueKind GetValueKind(string? name)
+ public unsafe RegistryValueKind GetValueKind(string? name)
{
EnsureNotDisposed();
- return GetValueKindCore(name);
+ int type = 0;
+ int datasize = 0;
+ int ret = Interop.Advapi32.RegQueryValueEx(_hkey, name, null, &type, (byte*)null, (uint*)&datasize);
+ if (ret != 0)
+ {
+ Win32Error(ret, null);
+ }
+
+ return
+ type == Interop.Advapi32.RegistryValues.REG_NONE ? RegistryValueKind.None :
+ !Enum.IsDefined(typeof(RegistryValueKind), type) ? RegistryValueKind.Unknown :
+ (RegistryValueKind)type;
}
public string Name
@@ -556,7 +1229,7 @@ public void SetValue(string? name, object value)
SetValue(name, value, RegistryValueKind.Unknown);
}
- public void SetValue(string? name, object value, RegistryValueKind valueKind)
+ public unsafe void SetValue(string? name, object value, RegistryValueKind valueKind)
{
ArgumentNullException.ThrowIfNull(value);
@@ -579,7 +1252,120 @@ public void SetValue(string? name, object value, RegistryValueKind valueKind)
valueKind = CalculateValueKind(value);
}
- SetValueCore(name, value, valueKind);
+ int ret = 0;
+ try
+ {
+ switch (valueKind)
+ {
+ case RegistryValueKind.ExpandString:
+ case RegistryValueKind.String:
+ {
+ string data = value.ToString()!;
+ ret = Interop.Advapi32.RegSetValueEx(_hkey,
+ name,
+ 0,
+ (int)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());
+
+ // First determine the size of the array
+ //
+ // Format is null terminator between strings and final null terminator at the end.
+ // e.g. str1\0str2\0str3\0\0
+ //
+ int sizeInChars = 1; // no matter what, we have the final null terminator.
+ for (int i = 0; i < dataStrings.Length; i++)
+ {
+ if (dataStrings[i] == null)
+ {
+ throw new ArgumentException(SR.Arg_RegSetStrArrNull);
+ }
+ sizeInChars = checked(sizeInChars + (dataStrings[i].Length + 1));
+ }
+ int sizeInBytes = checked(sizeInChars * sizeof(char));
+
+ // Write out the strings...
+ //
+ char[] dataChars = new char[sizeInChars];
+ int destinationIndex = 0;
+ for (int i = 0; i < dataStrings.Length; i++)
+ {
+ int length = dataStrings[i].Length;
+ dataStrings[i].CopyTo(0, dataChars, destinationIndex, length);
+ destinationIndex += (length + 1); // +1 for null terminator, which is already zero-initialized in new array.
+ }
+
+ ret = Interop.Advapi32.RegSetValueEx(_hkey,
+ name,
+ 0,
+ Interop.Advapi32.RegistryValues.REG_MULTI_SZ,
+ dataChars,
+ sizeInBytes);
+
+ break;
+ }
+
+ case RegistryValueKind.None:
+ case RegistryValueKind.Binary:
+ byte[] dataBytes = (byte[])value;
+ ret = Interop.Advapi32.RegSetValueEx(_hkey,
+ name,
+ 0,
+ (valueKind == RegistryValueKind.None ? Interop.Advapi32.RegistryValues.REG_NONE : Interop.Advapi32.RegistryValues.REG_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 = Interop.Advapi32.RegSetValueEx(_hkey,
+ name,
+ 0,
+ Interop.Advapi32.RegistryValues.REG_DWORD,
+ ref data,
+ 4);
+ break;
+ }
+
+ case RegistryValueKind.QWord:
+ {
+ long data = Convert.ToInt64(value, System.Globalization.CultureInfo.InvariantCulture);
+
+ ret = Interop.Advapi32.RegSetValueEx(_hkey,
+ name,
+ 0,
+ Interop.Advapi32.RegistryValues.REG_QWORD,
+ ref data,
+ 8);
+ break;
+ }
+ }
+ }
+ catch (Exception exc) when (exc is OverflowException || exc is InvalidOperationException || exc is FormatException || exc is InvalidCastException)
+ {
+ throw new ArgumentException(SR.Arg_RegSetMismatchedKind);
+ }
+
+ if (ret == 0)
+ {
+ SetDirty();
+ }
+ else
+ {
+ Win32Error(ret, null);
+ }
}
private static RegistryValueKind CalculateValueKind(object value)
@@ -773,6 +1559,58 @@ private static void ValidateKeyRights(RegistryRights rights)
}
}
+ ///
+ /// 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.
+ ///
+ private void Win32Error(int errorCode, string? str)
+ {
+ switch (errorCode)
+ {
+ case Interop.Errors.ERROR_ACCESS_DENIED:
+ throw str != null ?
+ new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)) :
+ new UnauthorizedAccessException();
+
+ case Interop.Errors.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())
+ {
+ _hkey.SetHandleAsInvalid();
+ _hkey = null!;
+ }
+ goto default;
+
+ case Interop.Errors.ERROR_FILE_NOT_FOUND:
+ throw new IOException(SR.Arg_RegKeyNotFound, errorCode);
+
+ default:
+ throw new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode);
+ }
+ }
+
+ private static void Win32ErrorStatic(int errorCode, string? str) =>
+ throw errorCode switch
+ {
+ Interop.Errors.ERROR_ACCESS_DENIED => str != null ?
+ new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_RegistryKeyGeneric_Key, str)) :
+ new UnauthorizedAccessException(),
+
+ _ => new IOException(Interop.Kernel32.GetMessage(errorCode), errorCode),
+ };
+
/// Retrieves the current state of the dirty property.
/// A key is marked as dirty if any operation has occurred that modifies the contents of the key.
/// true if the key has been modified.
diff --git a/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.Windows.cs b/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.Windows.cs
deleted file mode 100644
index f2ccfe1f326d2..0000000000000
--- a/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.Windows.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.IO;
-using System.Runtime.InteropServices;
-
-namespace System.Security.AccessControl
-{
- public sealed partial class RegistrySecurity : NativeObjectSecurity
- {
- private static Exception? _HandleErrorCodeCore(int errorCode)
- {
- Exception? exception = null;
-
- switch (errorCode)
- {
- case Interop.Errors.ERROR_FILE_NOT_FOUND:
- exception = new IOException(SR.Format(SR.Arg_RegKeyNotFound, errorCode));
- break;
-
- case Interop.Errors.ERROR_INVALID_NAME:
- exception = new ArgumentException(SR.Arg_RegInvalidKeyName, "name");
- break;
-
- case Interop.Errors.ERROR_INVALID_HANDLE:
- exception = new ArgumentException(SR.AccessControl_InvalidHandle);
- break;
-
- default:
- break;
- }
-
- return exception;
- }
- }
-}
diff --git a/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.cs b/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.cs
index 001c296103173..749410d8a3b1b 100644
--- a/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.cs
+++ b/src/libraries/Microsoft.Win32.Registry/src/System/Security/AccessControl/RegistrySecurity.cs
@@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32.SafeHandles;
+using System.IO;
using System.Runtime.InteropServices;
using System.Security.Principal;
@@ -96,7 +97,27 @@ internal RegistrySecurity(SafeRegistryHandle hKey, AccessControlSections include
private static Exception? _HandleErrorCode(int errorCode, string? name, SafeHandle? handle, object? context)
{
- return _HandleErrorCodeCore(errorCode);
+ Exception? exception = null;
+
+ switch (errorCode)
+ {
+ case Interop.Errors.ERROR_FILE_NOT_FOUND:
+ exception = new IOException(SR.Format(SR.Arg_RegKeyNotFound, errorCode));
+ break;
+
+ case Interop.Errors.ERROR_INVALID_NAME:
+ exception = new ArgumentException(SR.Arg_RegInvalidKeyName, nameof(name));
+ break;
+
+ case Interop.Errors.ERROR_INVALID_HANDLE:
+ exception = new ArgumentException(SR.AccessControl_InvalidHandle);
+ break;
+
+ default:
+ break;
+ }
+
+ return exception;
}
public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type)
diff --git a/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj b/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj
index a0dbb8e208bd4..696335c91f40e 100644
--- a/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj
+++ b/src/libraries/System.Diagnostics.PerformanceCounter/src/System.Diagnostics.PerformanceCounter.csproj
@@ -58,8 +58,6 @@ System.Diagnostics.PerformanceCounter
Link="Common\Interop\Windows\Advapi32\Interop.RegQueryValueEx.cs" />
-
();
- char[] name = ArrayPool.Shared.Rent(MaxKeyLength + 1);
-
- try
+ Span name = stackalloc char[MaxKeyLength + 1];
+
+ int result;
+ int nameLength = name.Length;
+
+ while ((result = Interop.Advapi32.RegEnumKeyEx(
+ _hkey,
+ names.Count,
+ ref MemoryMarshal.GetReference(name),
+ ref nameLength,
+ null,
+ null,
+ null,
+ null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
{
- int result;
- int nameLength = name.Length;
-
- while ((result = Interop.Advapi32.RegEnumKeyEx(
- _hkey,
- names.Count,
- name,
- ref nameLength,
- null,
- null,
- null,
- null)) != Interop.Errors.ERROR_NO_MORE_ITEMS)
+ switch (result)
{
- switch (result)
- {
- case Interop.Errors.ERROR_SUCCESS:
- names.Add(new string(name, 0, nameLength));
- nameLength = name.Length;
- break;
- default:
- // Throw the error
- Win32Error(result, null);
- break;
- }
+ case Interop.Errors.ERROR_SUCCESS:
+ names.Add(new string(name.Slice(0, nameLength)));
+ nameLength = name.Length;
+ break;
+ default:
+ // Throw the error
+ Win32Error(result, null);
+ break;
}
}
- finally
- {
- ArrayPool.Shared.Return(name);
- }
return names.ToArray();
}
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.Windows.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.Windows.cs
deleted file mode 100644
index d37ed78049079..0000000000000
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.Windows.cs
+++ /dev/null
@@ -1,22 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using Microsoft.Win32.SafeHandles;
-
-#if REGISTRY_ASSEMBLY
-namespace Microsoft.Win32.SafeHandles
-#else
-namespace Internal.Win32.SafeHandles
-#endif
-{
-#if REGISTRY_ASSEMBLY
- public
-#else
- internal
-#endif
- sealed partial class SafeRegistryHandle : SafeHandleZeroOrMinusOneIsInvalid
- {
- protected override bool ReleaseHandle() =>
- Interop.Advapi32.RegCloseKey(handle) == Interop.Errors.ERROR_SUCCESS;
- }
-}
diff --git a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs
index afe1a93e85840..73419ae1e51b7 100644
--- a/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs
+++ b/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeRegistryHandle.cs
@@ -31,5 +31,9 @@ public SafeRegistryHandle(IntPtr preexistingHandle, bool ownsHandle) : base(owns
{
SetHandle(preexistingHandle);
}
+
+ ///
+ protected override bool ReleaseHandle() =>
+ Interop.Advapi32.RegCloseKey(handle) == Interop.Errors.ERROR_SUCCESS;
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 0e4f53259d998..7dbc1cb3f3deb 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1947,7 +1947,6 @@
-
@@ -2529,4 +2528,4 @@
-
\ No newline at end of file
+