Skip to content

Commit 8644ff8

Browse files
authored
Merge pull request #53 from santisq/52-add-cancellation-sort-by-ascending-registrykeys
Add Ctrl+C Cancellation Support and Ascending Sort Order for `Get-PSTreeRegistry`
2 parents 2aa310c + 172c899 commit 8644ff8

File tree

9 files changed

+109
-49
lines changed

9 files changed

+109
-49
lines changed

module/PSTree.psd1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
}
1717

1818
# Version number of this module.
19-
ModuleVersion = '2.2.6'
19+
ModuleVersion = '2.2.7'
2020

2121
# Supported PSEditions
2222
# CompatiblePSEditions = @()

src/PSTree/Commands/GetPSTreeCommand.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public sealed class GetPSTreeCommand : TreeCommandBase
1414
{
1515
private readonly Stack<TreeDirectory> _stack = new();
1616

17-
private readonly Cache<TreeFileSystemInfo, TreeFile> _cache = new();
17+
private readonly TreeBuilder<TreeFileSystemInfo, TreeFile> _builder = new();
1818

1919
private readonly TreeComparer _comparer = new();
2020

@@ -65,11 +65,11 @@ protected override void ProcessRecord()
6565

6666
private TreeFileSystemInfo[] Traverse(TreeDirectory directory)
6767
{
68-
_cache.Clear();
68+
_builder.Clear();
6969
directory.PushToStack(_stack);
7070
string source = directory.FullName;
7171

72-
while (_stack.Count > 0)
72+
while (_stack.Count > 0 && !Canceled)
7373
{
7474
TreeDirectory next = _stack.Pop();
7575
int level = next.Depth + 1;
@@ -111,7 +111,7 @@ private TreeFileSystemInfo[] Traverse(TreeDirectory directory)
111111
.Create(fileInfo, source, level)
112112
.AddParent<TreeFile>(next)
113113
.SetIncludeFlagIf(WithInclude)
114-
.AddToCache(_cache);
114+
.AddToCache(_builder);
115115
}
116116

117117
continue;
@@ -138,15 +138,15 @@ private TreeFileSystemInfo[] Traverse(TreeDirectory directory)
138138

139139
if (next.Depth <= Depth)
140140
{
141-
_cache.Add(next);
142-
_cache.Flush();
141+
_builder.Add(next);
142+
_builder.Flush();
143143
}
144144
}
145145
catch (Exception exception)
146146
{
147147
if (next.Depth <= Depth)
148148
{
149-
_cache.Add(next);
149+
_builder.Add(next);
150150
}
151151

152152
WriteError(exception.ToEnumerationError(next));
@@ -160,7 +160,7 @@ private TreeFileSystemInfo[] Traverse(TreeDirectory directory)
160160

161161
private TreeFileSystemInfo[] GetTree(bool includeCondition)
162162
{
163-
TreeFileSystemInfo[] result = _cache.GetResult(includeCondition);
163+
TreeFileSystemInfo[] result = _builder.GetTree(includeCondition);
164164
return result.Format(GetItemCount(result));
165165
}
166166

src/PSTree/Commands/GetPSTreeRegistryCommand.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Linq;
45
using System.Management.Automation;
56
using System.Security;
67
using Microsoft.Win32;
@@ -18,7 +19,7 @@ namespace PSTree.Commands;
1819
public sealed class GetPSTreeRegistryCommand : TreeCommandBase
1920
{
2021
#if WINDOWS
21-
private readonly Cache<TreeRegistryBase, TreeRegistryValue> _cache = new();
22+
private readonly TreeBuilder<TreeRegistryBase, TreeRegistryValue> _builder = new();
2223

2324
private readonly Stack<(TreeRegistryKey, RegistryKey)> _stack = [];
2425
#endif
@@ -55,15 +56,15 @@ protected override void ProcessRecord()
5556

5657
private TreeRegistryBase[] Traverse(RegistryKey registryKey)
5758
{
58-
_cache.Clear();
59+
_builder.Clear();
5960

6061
registryKey.
6162
CreateTreeKey(System.IO.Path.GetFileName(registryKey.Name)).
6263
PushToStack(_stack);
6364

6465
string source = registryKey.Name;
6566

66-
while (_stack.Count > 0)
67+
while (_stack.Count > 0 && !Canceled)
6768
{
6869
(TreeRegistryKey tree, registryKey) = _stack.Pop();
6970
int depth = tree.Depth + 1;
@@ -87,11 +88,11 @@ private TreeRegistryBase[] Traverse(RegistryKey registryKey)
8788
new TreeRegistryValue(registryKey, value, source, depth)
8889
.AddParent<TreeRegistryValue>(tree)
8990
.SetIncludeFlagIf(WithInclude)
90-
.AddToCache(_cache);
91+
.AddToCache(_builder);
9192
}
9293

9394
PushKeys:
94-
foreach (string keyname in registryKey.GetSubKeyNames())
95+
foreach (string keyname in registryKey.EnumerateKeys())
9596
{
9697
if (ShouldExclude(keyname))
9798
{
@@ -121,11 +122,11 @@ private TreeRegistryBase[] Traverse(RegistryKey registryKey)
121122
}
122123
}
123124

124-
_cache.Add(tree);
125-
_cache.Flush();
125+
_builder.Add(tree);
126+
_builder.Flush();
126127
}
127128

128-
return _cache.GetResult(WithInclude && !KeysOnly).Format();
129+
return _builder.GetTree(WithInclude && !KeysOnly).Format();
129130
}
130131

131132
private bool ShouldSkipValue(string value) => ShouldExclude(value) || !ShouldInclude(value);

src/PSTree/Extensions/TreeExtensions.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Text;
43
using Microsoft.Win32;
4+
using System.Linq;
55
#if NETCOREAPP
66
using System.Runtime.CompilerServices;
7+
#else
8+
using System.Text;
79
#endif
810

911
namespace PSTree.Extensions;
@@ -86,7 +88,7 @@ internal static TreeFileSystemInfo[] Format(
8688
return tree;
8789
}
8890

89-
internal static void AddToCache<TBase, TLeaf>(this TLeaf leaf, Cache<TBase, TLeaf> cache)
91+
internal static void AddToCache<TBase, TLeaf>(this TLeaf leaf, TreeBuilder<TBase, TLeaf> cache)
9092
where TLeaf : TBase
9193
where TBase : ITree
9294
{
@@ -186,6 +188,13 @@ internal static TreeRegistryBase[] Format(
186188
return tree;
187189
}
188190

191+
internal static IEnumerable<string> EnumerateKeys(this RegistryKey registryKey) =>
192+
#if NET6_0_OR_GREATER
193+
registryKey.GetSubKeyNames().OrderDescending();
194+
#else
195+
registryKey.GetSubKeyNames().OrderByDescending(e => e);
196+
#endif
197+
189198
internal static (TreeRegistryKey, RegistryKey) CreateTreeKey(
190199
this RegistryKey key, string name) =>
191200
(new TreeRegistryKey(key, name, key.Name), key);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
namespace PSTree;
55

6-
internal sealed class Cache<TBase, TLeaf>
6+
internal sealed class TreeBuilder<TBase, TLeaf>
77
where TLeaf : TBase
88
where TBase : ITree
99
{
@@ -15,7 +15,7 @@ internal sealed class Cache<TBase, TLeaf>
1515

1616
internal void Add(TBase container) => _items.Add(container);
1717

18-
internal TBase[] GetResult(bool includeCondition) =>
18+
internal TBase[] GetTree(bool includeCondition) =>
1919
includeCondition
2020
? [.. _items.Where(static e => e.Include)]
2121
: [.. _items];

src/PSTree/TreeCommandBase.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ public abstract class TreeCommandBase : PSCmdlet
1515

1616
private WildcardPattern[]? _includePatterns;
1717

18+
private string[]? _paths;
19+
1820
protected const string PathSet = "Path";
1921

2022
protected const string LiteralPathSet = "LiteralPath";
2123

22-
protected string[]? _paths;
23-
2424
protected bool WithExclude { get; private set; }
2525

2626
protected bool WithInclude { get; private set; }
@@ -30,6 +30,8 @@ protected bool IsLiteral
3030
get => MyInvocation.BoundParameters.ContainsKey(nameof(LiteralPath));
3131
}
3232

33+
protected bool Canceled { get; set; }
34+
3335
[Parameter(
3436
ParameterSetName = PathSet,
3537
Position = 0,
@@ -98,6 +100,8 @@ protected override void BeginProcessing()
98100
}
99101
}
100102

103+
protected override void StopProcessing() => Canceled = true;
104+
101105
protected IEnumerable<(ProviderInfo, string)> EnumerateResolvedPaths()
102106
{
103107
Collection<string> resolvedPaths;

tests/GetPSTreeCommand.tests.ps1

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
$ErrorActionPreference = 'Stop'
1+
using namespace System.IO
2+
using namespace System.Linq
23

3-
$moduleName = (Get-Item ([IO.Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
4-
$manifestPath = [IO.Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
4+
$ErrorActionPreference = 'Stop'
5+
6+
$moduleName = (Get-Item ([Path]::Combine($PSScriptRoot, '..', 'module', '*.psd1'))).BaseName
7+
$manifestPath = [Path]::Combine($PSScriptRoot, '..', 'output', $moduleName)
58

69
Import-Module $manifestPath
7-
Import-Module ([System.IO.Path]::Combine($PSScriptRoot, 'shared.psm1'))
10+
Import-Module ([Path]::Combine($PSScriptRoot, 'shared.psm1'))
811

912
Describe 'Get-PSTree' {
1013
BeforeAll {
@@ -33,7 +36,7 @@ Describe 'Get-PSTree' {
3336
$newFolder
3437
New-Item @fileSplat
3538
} | ForEach-Object {
36-
$_.Attributes = $_.Attributes -bor [System.IO.FileAttributes]::Hidden
39+
$_.Attributes = $_.Attributes -bor [FileAttributes]::Hidden
3740
}
3841
}
3942

@@ -109,9 +112,9 @@ Describe 'Get-PSTree' {
109112
$exclude = '*tools*', '*build*', '*.ps1'
110113
Get-PSTree $testPath -Exclude * | Should -HaveCount 1
111114
Get-PSTree $testPath -Exclude $exclude -Recurse | ForEach-Object {
112-
[System.Linq.Enumerable]::Any(
115+
[Enumerable]::Any(
113116
[string[]] $exclude,
114-
[System.Func[string, bool]] { $_.Name -like $args[0] })
117+
[Func[string, bool]] { $_.Name -like $args[0] })
115118
} | Should -Not -BeTrue
116119

117120
Get-ChildItem $testPath -Filter *.ps1 -Recurse |
@@ -122,9 +125,9 @@ Describe 'Get-PSTree' {
122125
It 'Includes child items with -Include parameter' {
123126
$include = '*.ps1', '*.cs'
124127
Get-PSTree $testPath -Include $include -Recurse | ForEach-Object {
125-
[System.Linq.Enumerable]::Any(
128+
[Enumerable]::Any(
126129
[string[]] $include,
127-
[System.Func[string, bool]] {
130+
[Func[string, bool]] {
128131
$_.Name -like $args[0] -or $_ -is [PSTree.TreeDirectory]
129132
}
130133
)
@@ -176,4 +179,23 @@ Describe 'Get-PSTree' {
176179
$testHiddenFolder | Get-PSTree -Recurse -Force |
177180
Should -HaveCount 21
178181
}
182+
183+
It 'Should be able to Cancel the cmdlet' {
184+
Measure-Command {
185+
$ps = [powershell]::Create().AddScript({
186+
Import-Module $args[0]
187+
188+
$roots = Get-PSDrive |
189+
Where-Object { $_.Provider.Name -eq 'FileSystem' } |
190+
ForEach-Object Root
191+
192+
Get-PSTree $roots -Recurse -ErrorAction SilentlyContinue
193+
}).AddArgument($manifestPath)
194+
195+
$task = $ps.BeginInvoke()
196+
Start-Sleep 0.5
197+
$ps.Stop()
198+
try { $ps.EndInvoke($task) } catch { }
199+
} | Should -BeLessThan ([timespan] '00:00:01')
200+
}
179201
}

0 commit comments

Comments
 (0)