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))]