From 142c531a277cc42e3de7ad683b86e511a36da45c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 2 Sep 2024 13:34:33 -0300 Subject: [PATCH 01/10] added `-Exclude` parameter. needs testing and docs. --- .../Commands/GetADTreeGroupMemberCommand.cs | 5 ++ ...etADTreePrincipalGroupMembershipCommand.cs | 5 ++ src/PSADTree/PSADTreeCmdletBase.cs | 55 +++++++++++++++++-- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs b/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs index 181a617..1386e09 100644 --- a/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs +++ b/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs @@ -144,6 +144,11 @@ private void EnumerateMembers( } } + if (ShouldExclude(member, _exclusionPatterns)) + { + continue; + } + TreeObjectBase treeObject = ProcessPrincipal( principal: member, parent: parent, diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index 25d83dd..ce51a42 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -164,6 +164,11 @@ private void EnumerateMembership( { foreach (Principal group in searchResult) { + if (ShouldExclude(group, _exclusionPatterns)) + { + continue; + } + TreeGroup treeGroup = ProcessGroup((GroupPrincipal)group); if (ShowAll.IsPresent) { diff --git a/src/PSADTree/PSADTreeCmdletBase.cs b/src/PSADTree/PSADTreeCmdletBase.cs index 7ae8766..6e3725e 100644 --- a/src/PSADTree/PSADTreeCmdletBase.cs +++ b/src/PSADTree/PSADTreeCmdletBase.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.DirectoryServices.AccountManagement; +using System.Linq; using System.Management.Automation; namespace PSADTree; @@ -23,11 +24,18 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable internal PSADTreeComparer _comparer = new(); + protected WildcardPattern[]? _exclusionPatterns; + + private const WildcardOptions _wpoptions = WildcardOptions.Compiled + | WildcardOptions.CultureInvariant + | WildcardOptions.IgnoreCase; + + [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] [Alias("DistinguishedName")] public string? Identity { get; set; } @@ -44,6 +52,10 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable [Parameter] public SwitchParameter ShowAll { get; set; } + [Parameter] + [SupportsWildcards] + public string[]? Exclude { get; set; } + protected override void BeginProcessing() { try @@ -55,6 +67,14 @@ protected override void BeginProcessing() } _context = new PrincipalContext(ContextType.Domain, Server); + + + if (Exclude is not null) + { + _exclusionPatterns = Exclude + .Select(e => new WildcardPattern(e, _wpoptions)) + .ToArray(); + } } catch (Exception exception) { @@ -70,6 +90,33 @@ protected void Push(GroupPrincipal? groupPrincipal, TreeGroup treeGroup) } } + private static bool MatchAny( + Principal principal, + WildcardPattern[] patterns) + { + foreach (WildcardPattern pattern in patterns) + { + if (pattern.IsMatch(principal.SamAccountName)) + { + return true; + } + } + + return false; + } + + protected static bool ShouldExclude( + Principal principal, + WildcardPattern[]? patterns) + { + if (patterns is null) + { + return false; + } + + return MatchAny(principal, patterns); + } + protected virtual void Dispose(bool disposing) { if (disposing && !_disposed) From b8ed61cf79efd51988f0d42c6587d4a6776716d7 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 2 Sep 2024 13:48:16 -0300 Subject: [PATCH 02/10] documented parameter --- docs/en-US/Get-ADTreeGroupMember.md | 26 ++++++++++++++++++- .../Get-ADTreePrincipalGroupMembership.md | 24 +++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index 01dc0ec..62f1172 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -21,7 +21,8 @@ Get-ADTreeGroupMember [-Identity] [-Server ] [-Depth ] - [-ShowAll] + [-ShowAll + [-Exclude ]] [] ``` @@ -34,6 +35,7 @@ Get-ADTreeGroupMember [-Server ] [-Recursive] [-ShowAll] + [-Exclude ] [] ``` @@ -220,6 +222,28 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Exclude + +Specifies an array of one or more string patterns to be matched as the cmdlet enumerates child principals. +Any matching principal is excluded from the output. +Wildcard characters are accepted. + +> [!NOTE] +> +> - Patterns are tested against the principal's `.SamAccountName` property. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: True +``` + ### CommonParameters This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). diff --git a/docs/en-US/Get-ADTreePrincipalGroupMembership.md b/docs/en-US/Get-ADTreePrincipalGroupMembership.md index 01f2333..c2a7cb6 100644 --- a/docs/en-US/Get-ADTreePrincipalGroupMembership.md +++ b/docs/en-US/Get-ADTreePrincipalGroupMembership.md @@ -21,6 +21,7 @@ Get-ADTreePrincipalGroupMembership [-Server ] [-Depth ] [-ShowAll] + [-Exclude ] [] ``` @@ -32,6 +33,7 @@ Get-ADTreePrincipalGroupMembership [-Server ] [-Recursive] [-ShowAll] + [-Exclude ] [] ``` @@ -200,6 +202,28 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -Exclude + +Specifies an array of one or more string patterns to be matched as the cmdlet enumerates child principals. +Any matching principal is excluded from the output. +Wildcard characters are accepted. + +> [!NOTE] +> +> - Patterns are tested against the principal's `.SamAccountName` property. + +```yaml +Type: String[] +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: True +``` + ### CommonParameters This cmdlet supports the common parameters. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). From 799ce1ee8c990345e36b1b2fffdeccf4554811b9 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 2 Sep 2024 13:48:56 -0300 Subject: [PATCH 03/10] documented parameter --- docs/en-US/Get-ADTreeGroupMember.md | 2 +- docs/en-US/Get-ADTreePrincipalGroupMembership.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index 62f1172..18085e0 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -230,7 +230,7 @@ Wildcard characters are accepted. > [!NOTE] > -> - Patterns are tested against the principal's `.SamAccountName` property. +> Patterns are tested against the principal's `.SamAccountName` property. ```yaml Type: String[] diff --git a/docs/en-US/Get-ADTreePrincipalGroupMembership.md b/docs/en-US/Get-ADTreePrincipalGroupMembership.md index c2a7cb6..1665ce3 100644 --- a/docs/en-US/Get-ADTreePrincipalGroupMembership.md +++ b/docs/en-US/Get-ADTreePrincipalGroupMembership.md @@ -210,7 +210,7 @@ Wildcard characters are accepted. > [!NOTE] > -> - Patterns are tested against the principal's `.SamAccountName` property. +> Patterns are tested against the principal's `.SamAccountName` property. ```yaml Type: String[] From cdb673750a9a4cfd9d810c3ae06085b51157065f Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 2 Sep 2024 13:59:44 -0300 Subject: [PATCH 04/10] documented parameter --- docs/en-US/Get-ADTreeGroupMember.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index 18085e0..fa51530 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -230,7 +230,8 @@ Wildcard characters are accepted. > [!NOTE] > -> Patterns are tested against the principal's `.SamAccountName` property. +> - Patterns are tested against the principal's `.SamAccountName` property. +> - When the matched principal is of type `group`, all child principals are also excluded from the output. ```yaml Type: String[] From 93a5c356ad898abfc40475d7c3642314d258181c Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Mon, 2 Sep 2024 22:23:42 -0300 Subject: [PATCH 05/10] fixed beginprocessing method in base cmdlet --- src/PSADTree/PSADTreeCmdletBase.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/PSADTree/PSADTreeCmdletBase.cs b/src/PSADTree/PSADTreeCmdletBase.cs index 6e3725e..ebbebf0 100644 --- a/src/PSADTree/PSADTreeCmdletBase.cs +++ b/src/PSADTree/PSADTreeCmdletBase.cs @@ -60,6 +60,13 @@ protected override void BeginProcessing() { try { + if (Exclude is not null) + { + _exclusionPatterns = Exclude + .Select(e => new WildcardPattern(e, _wpoptions)) + .ToArray(); + } + if (Server is null) { _context = new PrincipalContext(ContextType.Domain); @@ -67,14 +74,6 @@ protected override void BeginProcessing() } _context = new PrincipalContext(ContextType.Domain, Server); - - - if (Exclude is not null) - { - _exclusionPatterns = Exclude - .Select(e => new WildcardPattern(e, _wpoptions)) - .ToArray(); - } } catch (Exception exception) { From 1809f423360ff35a00d223a3f6954b05cef38265 Mon Sep 17 00:00:00 2001 From: PoshAJ <19650958-PoshAJ@users.noreply.gitlab.com> Date: Mon, 2 Sep 2024 21:54:16 -0400 Subject: [PATCH 06/10] fix typo and whitespace in docs --- docs/en-US/Get-ADTreeGroupMember.md | 14 +++++++------- docs/en-US/Get-ADTreePrincipalGroupMembership.md | 10 +++++----- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index fa51530..f4c7be0 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -21,8 +21,8 @@ Get-ADTreeGroupMember [-Identity] [-Server ] [-Depth ] - [-ShowAll - [-Exclude ]] + [-ShowAll] + [-Exclude ] [] ``` @@ -90,7 +90,7 @@ PS ..\PSADTree\> Get-ADTreeGroupMember TestGroup001 -Server otherDomain PS ..\PSADTree\> Get-ADTreeGroupMember TestGroup001 -ShowAll ``` -By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. +By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of all previously processed groups. > [!NOTE] @@ -101,7 +101,7 @@ The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of ### -Depth -Determines the number of nested groups and their members included in the recursion. +Determines the number of nested groups and their members included in the recursion. By default, only 3 levels of recursion are included. ```yaml @@ -201,13 +201,13 @@ Accept wildcard characters: False ### -ShowAll -By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. +By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. This switch forces the cmdlet to display the full hierarchy including previously processed groups. > [!NOTE] > -> This cmdlet uses a caching mechanism to ensure that Active Directory Groups are only queried once per Identity. -> This caching mechanism is also used to reconstruct the pre-processed group's hierarchy when the `-ShowAll` switch is used, thus not incurring a performance cost. +> This cmdlet uses a caching mechanism to ensure that Active Directory Groups are only queried once per Identity. +> This caching mechanism is also used to reconstruct the pre-processed group's hierarchy when the `-ShowAll` switch is used, thus not incurring a performance cost. > The intent behind this switch is to not clutter the cmdlet's output by default. ```yaml diff --git a/docs/en-US/Get-ADTreePrincipalGroupMembership.md b/docs/en-US/Get-ADTreePrincipalGroupMembership.md index 1665ce3..261b33e 100644 --- a/docs/en-US/Get-ADTreePrincipalGroupMembership.md +++ b/docs/en-US/Get-ADTreePrincipalGroupMembership.md @@ -86,7 +86,7 @@ PS ..\PSADTree\> Get-ADTreePrincipalGroupMembership john.doe -Server otherDomain PS ..\PSADTree\> Get-ADTreePrincipalGroupMembership john.doe -ShowAll ``` -By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. +By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of all previously processed groups. > [!NOTE] @@ -97,7 +97,7 @@ The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of ### -Depth -Determines the number of nested group memberships included in the recursion. +Determines the number of nested group memberships included in the recursion. By default, only 3 levels of recursion are included. ```yaml @@ -181,13 +181,13 @@ Accept wildcard characters: False ### -ShowAll -By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. +By default, previously processed groups will be marked as _"Processed Group"_ and their hierarchy will not be displayed. This switch forces the cmdlet to display the full hierarchy including previously processed groups. > [!NOTE] > -> This cmdlet uses a caching mechanism to ensure that Active Directory Groups are only queried once per Identity. -> This caching mechanism is also used to reconstruct the pre-processed group's hierarchy when the `-ShowAll` switch is used, thus not incurring a performance cost. +> This cmdlet uses a caching mechanism to ensure that Active Directory Groups are only queried once per Identity. +> This caching mechanism is also used to reconstruct the pre-processed group's hierarchy when the `-ShowAll` switch is used, thus not incurring a performance cost. > The intent behind this switch is to not clutter the cmdlet's output by default. ```yaml From 0da772e6e2617febec572f9b3874062917b30729 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 3 Sep 2024 10:01:47 -0300 Subject: [PATCH 07/10] adding sorting for principal group membership command --- .../Commands/GetADTreePrincipalGroupMembershipCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index ce51a42..f84029c 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -162,7 +162,7 @@ private void EnumerateMembership( string source, int depth) { - foreach (Principal group in searchResult) + foreach (Principal group in searchResult.GetSortedEnumerable(_comparer)) { if (ShouldExclude(group, _exclusionPatterns)) { From d4d8e9eec7d43bba10238b58bcfbee7fabfca31e Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 3 Sep 2024 10:12:54 -0300 Subject: [PATCH 08/10] adding sorting to principal group membership command --- .../Commands/GetADTreePrincipalGroupMembershipCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index f84029c..f0b9fa8 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -70,7 +70,7 @@ protected override void ProcessRecord() try { using PrincipalSearchResult search = principal.GetGroups(); - foreach (Principal parent in search) + foreach (Principal parent in search.GetSortedEnumerable(_comparer)) { GroupPrincipal groupPrincipal = (GroupPrincipal)parent; TreeGroup treeGroup = new(source, null, groupPrincipal, 1); @@ -162,7 +162,7 @@ private void EnumerateMembership( string source, int depth) { - foreach (Principal group in searchResult.GetSortedEnumerable(_comparer)) + foreach (Principal group in searchResult) { if (ShouldExclude(group, _exclusionPatterns)) { From 3aa3503a387830ba4c8f60881d0be5a62d499c9a Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 3 Sep 2024 10:27:27 -0300 Subject: [PATCH 09/10] adding sorting to principal group membership command --- .../Commands/GetADTreePrincipalGroupMembershipCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index f0b9fa8..314c606 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -162,7 +162,7 @@ private void EnumerateMembership( string source, int depth) { - foreach (Principal group in searchResult) + foreach (Principal group in searchResult.GetSortedEnumerable(_comparer)) { if (ShouldExclude(group, _exclusionPatterns)) { From 5cdffc88baa8d204fb4391a606ee18e267e86f85 Mon Sep 17 00:00:00 2001 From: Santiago Squarzon Date: Tue, 3 Sep 2024 10:43:16 -0300 Subject: [PATCH 10/10] adding sorting for principal group membership command --- .../Commands/GetADTreePrincipalGroupMembershipCommand.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index 314c606..539812c 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -72,6 +72,11 @@ protected override void ProcessRecord() using PrincipalSearchResult search = principal.GetGroups(); foreach (Principal parent in search.GetSortedEnumerable(_comparer)) { + if (ShouldExclude(parent, _exclusionPatterns)) + { + continue; + } + GroupPrincipal groupPrincipal = (GroupPrincipal)parent; TreeGroup treeGroup = new(source, null, groupPrincipal, 1); Push(groupPrincipal, treeGroup);