--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#if REGISTRY_ASSEMBLY
+using Microsoft.Win32.SafeHandles;
+#else
+using Internal.Win32.SafeHandles;
+#endif
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Advapi32
+ {
+ [LibraryImport(Libraries.Advapi32, EntryPoint = "RegDeleteTreeW", StringMarshalling = StringMarshalling.Utf16)]
+ internal static partial int RegDeleteTree(
+ SafeRegistryHandle hKey,
+ string lpSubKey);
+ }
+}
Link="Common\Interop\Windows\Interop.RegCreateKeyEx.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteKeyEx.cs"
Link="Common\Interop\Windows\Interop.RegDeleteKeyEx.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteTree.cs"
+ Link="Common\Interop\Windows\Interop.RegDeleteTree.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteValue.cs"
Link="Common\Interop\Windows\Interop.RegDeleteValue.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegEnumKeyEx.cs"
subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
+ // If the key has values, it must be opened with KEY_SET_VALUE,
+ // or RegDeleteTree will fail with ERROR_ACCESS_DENIED.
+
RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
if (key != null)
{
using (key)
{
- if (key.SubKeyCount > 0)
+ int ret = Interop.Advapi32.RegDeleteTree(key._hkey, string.Empty);
+ if (ret != 0)
{
- string[] keys = key.GetSubKeyNames();
-
- for (int i = 0; i < keys.Length; i++)
- {
- key.DeleteSubKeyTreeInternal(keys[i]);
- }
+ Win32Error(ret, null);
}
- }
- DeleteSubKeyTreeCore(subkey);
- }
- else if (throwOnMissingSubKey)
- {
- throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
- }
- }
+ // RegDeleteTree doesn't self-delete when lpSubKey is empty.
+ // Manually delete the key to restore old behavior.
- /// <summary>
- /// 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.
- /// </summary>
- private void DeleteSubKeyTreeInternal(string subkey)
- {
- RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
- if (key != null)
- {
- using (key)
- {
- if (key.SubKeyCount > 0)
+ ret = Interop.Advapi32.RegDeleteKeyEx(key._hkey, string.Empty, (int)_regView, 0);
+ if (ret != 0)
{
- string[] keys = key.GetSubKeyNames();
- for (int i = 0; i < keys.Length; i++)
- {
- key.DeleteSubKeyTreeInternal(keys[i]);
- }
+ Win32Error(ret, null);
}
}
-
- DeleteSubKeyTreeCore(subkey);
}
else
{
- throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
- }
- }
-
- private void DeleteSubKeyTreeCore(string subkey)
- {
- int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
- if (ret != 0)
- {
- Win32Error(ret, null);
+ if (throwOnMissingSubKey)
+ {
+ throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
+ }
}
}
using System;
using System.Linq;
-using System.Reflection;
using Xunit;
namespace Microsoft.Win32.RegistryTests
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
}
+
+ [Fact]
+ public void SelfDeleteWithValuesTest()
+ {
+ using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+ {
+ rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
+ rk.SetDefaultValue("Default");
+ using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
+ created.SetValue("Value", 42, RegistryValueKind.DWord);
+ rk.DeleteSubKeyTree("");
+ }
+
+ Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+ }
+
+ [Fact]
+ public void SelfDeleteWithValuesTest_AnotherHandlePresent()
+ {
+ using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+ {
+ rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
+ rk.SetDefaultValue("Default");
+ using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
+ created.SetValue("Value", 42, RegistryValueKind.DWord);
+
+ using var rk2 = TestRegistryKey.OpenSubKey(TestRegistryKeyName);
+ rk.DeleteSubKeyTree("");
+ }
+
+ Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+ }
[Fact]
public void DeleteSubKeyTreeTest()
TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
}
+
+ [Fact]
+ public void DeleteSubKeyTreeTest3()
+ {
+ // [] Add in multiple subkeys and then delete the root key
+ string[] subKeyNames = Enumerable.Range(1, 9).Select(x => "BLAH_" + x.ToString()).ToArray();
+
+ using (RegistryKey rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
+ {
+ foreach (var subKeyName in subKeyNames)
+ {
+ using RegistryKey rk2 = rk.CreateSubKey(subKeyName);
+ Assert.NotNull(rk2);
+
+ using RegistryKey rk3 = rk2.CreateSubKey("Test");
+ Assert.NotNull(rk3);
+ }
+
+ Assert.Equal(subKeyNames, rk.GetSubKeyNames());
+
+ // Add multiple values to the key being deleted
+ foreach (int i in Enumerable.Range(1, 9))
+ {
+ rk.SetValue("STRVAL_" + i, i.ToString(), RegistryValueKind.String);
+ rk.SetValue("INTVAL_" + i, i, RegistryValueKind.DWord);
+ }
+ }
+
+ TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
+ Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
+ }
[Theory]
[MemberData(nameof(TestRegistrySubKeyNames))]