diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index 01dc0ec..f4c7be0 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -22,6 +22,7 @@ Get-ADTreeGroupMember [-Server ] [-Depth ] [-ShowAll] + [-Exclude ] [] ``` @@ -34,6 +35,7 @@ Get-ADTreeGroupMember [-Server ] [-Recursive] [-ShowAll] + [-Exclude ] [] ``` @@ -88,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] @@ -99,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 @@ -199,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 @@ -220,6 +222,29 @@ 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. +> - When the matched principal is of type `group`, all child principals are also excluded from the output. + +```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..261b33e 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 ] [] ``` @@ -84,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] @@ -95,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 @@ -179,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 @@ -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). 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..539812c 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -70,8 +70,13 @@ protected override void ProcessRecord() try { using PrincipalSearchResult search = principal.GetGroups(); - foreach (Principal parent in search) + 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); @@ -162,8 +167,13 @@ private void EnumerateMembership( string source, int depth) { - foreach (Principal group in searchResult) + foreach (Principal group in searchResult.GetSortedEnumerable(_comparer)) { + 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..ebbebf0 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,10 +52,21 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable [Parameter] public SwitchParameter ShowAll { get; set; } + [Parameter] + [SupportsWildcards] + public string[]? Exclude { get; set; } + 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); @@ -70,6 +89,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)