diff --git a/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs
new file mode 100644
index 00000000000000..52289627bc243d
--- /dev/null
+++ b/src/libraries/Common/src/Interop/Windows/Advapi32/Interop.RegDeleteTree.cs
@@ -0,0 +1,20 @@
+// 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);
+ }
+}
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 987e8547b27f06..7cb3100170e1b4 100644
--- a/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
+++ b/src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj
@@ -28,6 +28,8 @@
Link="Common\Interop\Windows\Interop.RegCreateKeyEx.cs" />
+
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.
- ///
- /// 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.
- ///
- 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);
+ }
}
}
diff --git a/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs b/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs
index c8606e1a2de022..727087d466ccfc 100644
--- a/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs
+++ b/src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs
@@ -3,7 +3,6 @@
using System;
using System.Linq;
-using System.Reflection;
using Xunit;
namespace Microsoft.Win32.RegistryTests
@@ -49,6 +48,38 @@ public void SelfDeleteTest()
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()
@@ -85,6 +116,37 @@ public void DeleteSubKeyTreeTest2()
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))]