Skip to content

Commit 03acd23

Browse files
authored
Use RegDeleteTree in RegistryKey.DeleteSubKeyTree (#82598)
* Use RegDeleteTree in RegistryKey.DeleteSubKeyTree * Restore self delete behavior * Call RegDeleteTree on subkey to simulate permission behavior. * Adjust comment and add tests.
1 parent f695b49 commit 03acd23

File tree

4 files changed

+100
-45
lines changed

4 files changed

+100
-45
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#if REGISTRY_ASSEMBLY
5+
using Microsoft.Win32.SafeHandles;
6+
#else
7+
using Internal.Win32.SafeHandles;
8+
#endif
9+
using System.Runtime.InteropServices;
10+
11+
internal static partial class Interop
12+
{
13+
internal static partial class Advapi32
14+
{
15+
[LibraryImport(Libraries.Advapi32, EntryPoint = "RegDeleteTreeW", StringMarshalling = StringMarshalling.Utf16)]
16+
internal static partial int RegDeleteTree(
17+
SafeRegistryHandle hKey,
18+
string lpSubKey);
19+
}
20+
}

src/libraries/Microsoft.Win32.Registry/src/Microsoft.Win32.Registry.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
Link="Common\Interop\Windows\Interop.RegCreateKeyEx.cs" />
2929
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteKeyEx.cs"
3030
Link="Common\Interop\Windows\Interop.RegDeleteKeyEx.cs" />
31+
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteTree.cs"
32+
Link="Common\Interop\Windows\Interop.RegDeleteTree.cs" />
3133
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegDeleteValue.cs"
3234
Link="Common\Interop\Windows\Interop.RegDeleteValue.cs" />
3335
<Compile Include="$(CommonPath)Interop\Windows\Advapi32\Interop.RegEnumKeyEx.cs"

src/libraries/Microsoft.Win32.Registry/src/Microsoft/Win32/RegistryKey.cs

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -334,65 +334,36 @@ public void DeleteSubKeyTree(string subkey, bool throwOnMissingSubKey)
334334

335335
subkey = FixupName(subkey); // Fixup multiple slashes to a single slash
336336

337+
// If the key has values, it must be opened with KEY_SET_VALUE,
338+
// or RegDeleteTree will fail with ERROR_ACCESS_DENIED.
339+
337340
RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
338341
if (key != null)
339342
{
340343
using (key)
341344
{
342-
if (key.SubKeyCount > 0)
345+
int ret = Interop.Advapi32.RegDeleteTree(key._hkey, string.Empty);
346+
if (ret != 0)
343347
{
344-
string[] keys = key.GetSubKeyNames();
345-
346-
for (int i = 0; i < keys.Length; i++)
347-
{
348-
key.DeleteSubKeyTreeInternal(keys[i]);
349-
}
348+
Win32Error(ret, null);
350349
}
351-
}
352350

353-
DeleteSubKeyTreeCore(subkey);
354-
}
355-
else if (throwOnMissingSubKey)
356-
{
357-
throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
358-
}
359-
}
351+
// RegDeleteTree doesn't self-delete when lpSubKey is empty.
352+
// Manually delete the key to restore old behavior.
360353

361-
/// <summary>
362-
/// An internal version which does no security checks or argument checking. Skipping the
363-
/// security checks should give us a slight perf gain on large trees.
364-
/// </summary>
365-
private void DeleteSubKeyTreeInternal(string subkey)
366-
{
367-
RegistryKey? key = InternalOpenSubKeyWithoutSecurityChecks(subkey, true);
368-
if (key != null)
369-
{
370-
using (key)
371-
{
372-
if (key.SubKeyCount > 0)
354+
ret = Interop.Advapi32.RegDeleteKeyEx(key._hkey, string.Empty, (int)_regView, 0);
355+
if (ret != 0)
373356
{
374-
string[] keys = key.GetSubKeyNames();
375-
for (int i = 0; i < keys.Length; i++)
376-
{
377-
key.DeleteSubKeyTreeInternal(keys[i]);
378-
}
357+
Win32Error(ret, null);
379358
}
380359
}
381-
382-
DeleteSubKeyTreeCore(subkey);
383360
}
384361
else
385362
{
386-
throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
387-
}
388-
}
389-
390-
private void DeleteSubKeyTreeCore(string subkey)
391-
{
392-
int ret = Interop.Advapi32.RegDeleteKeyEx(_hkey, subkey, (int)_regView, 0);
393-
if (ret != 0)
394-
{
395-
Win32Error(ret, null);
363+
if (throwOnMissingSubKey)
364+
{
365+
throw new ArgumentException(SR.Arg_RegSubKeyAbsent);
366+
}
396367
}
397368
}
398369

src/libraries/Microsoft.Win32.Registry/tests/RegistryKey/RegistryKey_DeleteSubKeyTree_str.cs

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System;
55
using System.Linq;
6-
using System.Reflection;
76
using Xunit;
87

98
namespace Microsoft.Win32.RegistryTests
@@ -49,6 +48,38 @@ public void SelfDeleteTest()
4948

5049
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
5150
}
51+
52+
[Fact]
53+
public void SelfDeleteWithValuesTest()
54+
{
55+
using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
56+
{
57+
rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
58+
rk.SetDefaultValue("Default");
59+
using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
60+
created.SetValue("Value", 42, RegistryValueKind.DWord);
61+
rk.DeleteSubKeyTree("");
62+
}
63+
64+
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
65+
}
66+
67+
[Fact]
68+
public void SelfDeleteWithValuesTest_AnotherHandlePresent()
69+
{
70+
using (var rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
71+
{
72+
rk.SetValue("VAL", "Dummy", RegistryValueKind.String);
73+
rk.SetDefaultValue("Default");
74+
using RegistryKey created = rk.CreateSubKey(TestRegistryKeyName);
75+
created.SetValue("Value", 42, RegistryValueKind.DWord);
76+
77+
using var rk2 = TestRegistryKey.OpenSubKey(TestRegistryKeyName);
78+
rk.DeleteSubKeyTree("");
79+
}
80+
81+
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
82+
}
5283

5384
[Fact]
5485
public void DeleteSubKeyTreeTest()
@@ -85,6 +116,37 @@ public void DeleteSubKeyTreeTest2()
85116
TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
86117
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
87118
}
119+
120+
[Fact]
121+
public void DeleteSubKeyTreeTest3()
122+
{
123+
// [] Add in multiple subkeys and then delete the root key
124+
string[] subKeyNames = Enumerable.Range(1, 9).Select(x => "BLAH_" + x.ToString()).ToArray();
125+
126+
using (RegistryKey rk = TestRegistryKey.CreateSubKey(TestRegistryKeyName))
127+
{
128+
foreach (var subKeyName in subKeyNames)
129+
{
130+
using RegistryKey rk2 = rk.CreateSubKey(subKeyName);
131+
Assert.NotNull(rk2);
132+
133+
using RegistryKey rk3 = rk2.CreateSubKey("Test");
134+
Assert.NotNull(rk3);
135+
}
136+
137+
Assert.Equal(subKeyNames, rk.GetSubKeyNames());
138+
139+
// Add multiple values to the key being deleted
140+
foreach (int i in Enumerable.Range(1, 9))
141+
{
142+
rk.SetValue("STRVAL_" + i, i.ToString(), RegistryValueKind.String);
143+
rk.SetValue("INTVAL_" + i, i, RegistryValueKind.DWord);
144+
}
145+
}
146+
147+
TestRegistryKey.DeleteSubKeyTree(TestRegistryKeyName);
148+
Assert.Null(TestRegistryKey.OpenSubKey(TestRegistryKeyName));
149+
}
88150

89151
[Theory]
90152
[MemberData(nameof(TestRegistrySubKeyNames))]

0 commit comments

Comments
 (0)