diff --git a/docs/en-US/Get-ADTreeGroupMember.md b/docs/en-US/Get-ADTreeGroupMember.md index f4c7be0..74d45a1 100644 --- a/docs/en-US/Get-ADTreeGroupMember.md +++ b/docs/en-US/Get-ADTreeGroupMember.md @@ -1,4 +1,4 @@ ---- +--- external help file: PSADTree.dll-Help.xml Module Name: PSADTree online version: @@ -20,6 +20,7 @@ Get-ADTreeGroupMember [-Group] [-Identity] [-Server ] + [-Credential ] [-Depth ] [-ShowAll] [-Exclude ] @@ -33,6 +34,7 @@ Get-ADTreeGroupMember [-Group] [-Identity] [-Server ] + [-Credential ] [-Recursive] [-ShowAll] [-Exclude ] @@ -99,10 +101,28 @@ The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of ## PARAMETERS +### -Credential + +Specifies a user account that has permission to perform this action. The default is the current user. + +Type a user name, such as **User01** or **Domain01\User01**, or enter a **PSCredential** object generated by the `Get-Credential` cmdlet. If you type a user name, you're prompted to enter the password. + +```yaml +Type: PSCredential +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Depth Determines the number of nested groups and their members included in the recursion. -By default, only 3 levels of recursion are included. +By default, only 3 levels of recursion are included. `Get-ADTreeGroupMember` emits a warning if the levels exceed this number. ```yaml Type: Int32 @@ -116,6 +136,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 +``` + ### -Group The `-Group` switch indicates that the cmdlet should display nested group members only. Essentially, a built-in filter where [`ObjectClass`](https://learn.microsoft.com/en-us/windows/win32/adschema/a-objectclass) is `group`. @@ -222,29 +265,6 @@ 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). @@ -266,3 +286,5 @@ You can pipe strings containing an identity to this cmdlet. [__`ADGroup`__](http ## NOTES `treegroupmember` is the alias for this cmdlet. + +## RELATED LINKS diff --git a/docs/en-US/Get-ADTreePrincipalGroupMembership.md b/docs/en-US/Get-ADTreePrincipalGroupMembership.md index 261b33e..c7b2d64 100644 --- a/docs/en-US/Get-ADTreePrincipalGroupMembership.md +++ b/docs/en-US/Get-ADTreePrincipalGroupMembership.md @@ -1,4 +1,4 @@ ---- +--- external help file: PSADTree.dll-Help.xml Module Name: PSADTree online version: @@ -19,6 +19,7 @@ schema: 2.0.0 Get-ADTreePrincipalGroupMembership [-Identity] [-Server ] + [-Credential ] [-Depth ] [-ShowAll] [-Exclude ] @@ -31,6 +32,7 @@ Get-ADTreePrincipalGroupMembership Get-ADTreePrincipalGroupMembership [-Identity] [-Server ] + [-Credential ] [-Recursive] [-ShowAll] [-Exclude ] @@ -95,10 +97,28 @@ The `-ShowAll` switch indicates that the cmdlet should display the hierarchy of ## PARAMETERS +### -Credential + +Specifies a user account that has permission to perform this action. The default is the current user. + +Type a user name, such as **User01** or **Domain01\User01**, or enter a **PSCredential** object generated by the `Get-Credential` cmdlet. If you type a user name, you're prompted to enter the password. + +```yaml +Type: PSCredential +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Depth Determines the number of nested group memberships included in the recursion. -By default, only 3 levels of recursion are included. +By default, only 3 levels of recursion are included. `Get-ADTreePrincipalGroupMembership` emits a warning if the levels exceed this number. ```yaml Type: Int32 @@ -112,6 +132,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 +``` + ### -Identity Specifies an Active Directory principal by providing one of the following property values: @@ -202,28 +244,6 @@ 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). @@ -245,3 +265,5 @@ You can pipe strings containing an identity to this cmdlet. [`ADObject`](https:/ ## NOTES `treeprincipalmembership` is the alias for this cmdlet. + +## RELATED LINKS diff --git a/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs b/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs index 1386e09..1c32970 100644 --- a/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs +++ b/src/PSADTree/Commands/GetADTreeGroupMemberCommand.cs @@ -21,33 +21,35 @@ protected override void ProcessRecord() { Dbg.Assert(Identity is not null); Dbg.Assert(_context is not null); + _truncatedOutput = false; try { using GroupPrincipal? group = GroupPrincipal.FindByIdentity(_context, Identity); if (group is null) { - WriteError(ErrorHelper.IdentityNotFound(Identity)); + WriteError(Exceptions.IdentityNotFound(Identity)); return; } - WriteObject( - sendToPipeline: Traverse( - groupPrincipal: group, - source: group.DistinguishedName), - enumerateCollection: true); + TreeObjectBase[] result = Traverse( + groupPrincipal: group, + source: group.DistinguishedName); + + DisplayWarningIfTruncatedOutput(); + WriteObject(sendToPipeline: result, enumerateCollection: true); } - catch (Exception e) when (e is PipelineStoppedException or FlowControlException) + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { throw; } - catch (MultipleMatchesException e) + catch (MultipleMatchesException exception) { - WriteError(e.AmbiguousIdentity(Identity)); + WriteError(exception.AmbiguousIdentity(Identity)); } - catch (Exception e) + catch (Exception exception) { - WriteError(e.Unspecified(Identity)); + WriteError(exception.Unspecified(Identity)); } } @@ -105,13 +107,13 @@ private TreeObjectBase[] Traverse( _index.TryAddPrincipals(); current?.Dispose(); } - catch (Exception e) when (e is PipelineStoppedException or FlowControlException) + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { throw; } - catch (Exception e) + catch (Exception exception) { - WriteError(e.EnumerationFailure(current)); + WriteError(exception.EnumerationFailure(current)); } } @@ -183,7 +185,7 @@ private TreeObjectBase ProcessPrincipal( TreeObjectBase AddTreeObject(TreeObjectBase obj) { - if (Recursive.IsPresent || depth <= Depth) + if (depth <= Depth) { _index.AddPrincipal(obj); } @@ -211,7 +213,6 @@ TreeObjectBase HandleGroup( private void EnumerateMembers(TreeGroup parent, int depth) { - bool shouldProcess = Recursive.IsPresent || depth <= Depth; foreach (TreeObjectBase member in parent.Childs) { if (member is TreeGroup treeGroup) @@ -220,7 +221,7 @@ private void EnumerateMembers(TreeGroup parent, int depth) continue; } - if (shouldProcess) + if (depth <= Depth) { _index.Add(member.Clone(parent, depth)); } diff --git a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs index 539812c..bf00155 100644 --- a/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs +++ b/src/PSADTree/Commands/GetADTreePrincipalGroupMembershipCommand.cs @@ -18,6 +18,7 @@ protected override void ProcessRecord() { Dbg.Assert(Identity is not null); Dbg.Assert(_context is not null); + _truncatedOutput = false; Principal? principal; Clear(); @@ -25,24 +26,24 @@ protected override void ProcessRecord() { principal = Principal.FindByIdentity(_context, Identity); } - catch (Exception e) when (e is PipelineStoppedException or FlowControlException) + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { throw; } - catch (MultipleMatchesException e) + catch (MultipleMatchesException exception) { - WriteError(e.AmbiguousIdentity(Identity)); + WriteError(exception.AmbiguousIdentity(Identity)); return; } - catch (Exception e) + catch (Exception exception) { - WriteError(e.Unspecified(Identity)); + WriteError(exception.Unspecified(Identity)); return; } if (principal is null) { - WriteError(ErrorHelper.IdentityNotFound(Identity)); + WriteError(Exceptions.IdentityNotFound(Identity)); return; } @@ -69,7 +70,7 @@ protected override void ProcessRecord() try { - using PrincipalSearchResult search = principal.GetGroups(); + using PrincipalSearchResult search = principal.GetGroups(_context); foreach (Principal parent in search.GetSortedEnumerable(_comparer)) { if (ShouldExclude(parent, _exclusionPatterns)) @@ -82,22 +83,22 @@ protected override void ProcessRecord() Push(groupPrincipal, treeGroup); } } - catch (Exception e) when (e is PipelineStoppedException or FlowControlException) + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { throw; } - catch (Exception e) + catch (Exception exception) { - WriteError(e.EnumerationFailure(null)); + WriteError(exception.EnumerationFailure(null)); } finally { principal?.Dispose(); } - WriteObject( - sendToPipeline: Traverse(source), - enumerateCollection: true); + TreeObjectBase[] result = Traverse(source); + DisplayWarningIfTruncatedOutput(); + WriteObject(sendToPipeline: result, enumerateCollection: true); } private TreeObjectBase[] Traverse(string source) @@ -138,7 +139,7 @@ private TreeObjectBase[] Traverse(string source) continue; } - using PrincipalSearchResult? search = current?.GetGroups(); + using PrincipalSearchResult? search = current?.GetGroups(_context); if (search is not null) { @@ -148,13 +149,13 @@ private TreeObjectBase[] Traverse(string source) _index.Add(treeGroup); current?.Dispose(); } - catch (Exception e) when (e is PipelineStoppedException or FlowControlException) + catch (Exception _) when (_ is PipelineStoppedException or FlowControlException) { throw; } - catch (Exception e) + catch (Exception exception) { - WriteError(e.EnumerationFailure(current)); + WriteError(exception.EnumerationFailure(current)); } } @@ -197,7 +198,7 @@ TreeGroup ProcessGroup(GroupPrincipal group) private void EnumerateMembership(TreeGroup parent, int depth) { - if (!Recursive.IsPresent && depth > Depth) + if (depth > Depth) { return; } diff --git a/src/PSADTree/ErrorHelper.cs b/src/PSADTree/Exceptions.cs similarity index 97% rename from src/PSADTree/ErrorHelper.cs rename to src/PSADTree/Exceptions.cs index 10e0cb3..8537db3 100644 --- a/src/PSADTree/ErrorHelper.cs +++ b/src/PSADTree/Exceptions.cs @@ -4,7 +4,7 @@ namespace PSADTree; -internal static class ErrorHelper +internal static class Exceptions { internal static ErrorRecord IdentityNotFound(string? identity) => new( diff --git a/src/PSADTree/PSADTreeCmdletBase.cs b/src/PSADTree/PSADTreeCmdletBase.cs index ebbebf0..d7c9bf5 100644 --- a/src/PSADTree/PSADTreeCmdletBase.cs +++ b/src/PSADTree/PSADTreeCmdletBase.cs @@ -16,6 +16,8 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable private bool _disposed; + protected bool _truncatedOutput; + protected readonly Stack<(GroupPrincipal? group, TreeGroup treeGroup)> _stack = new(); internal readonly TreeCache _cache = new(); @@ -30,7 +32,6 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable | WildcardOptions.CultureInvariant | WildcardOptions.IgnoreCase; - [Parameter( Position = 0, Mandatory = true, @@ -40,7 +41,11 @@ public abstract class PSADTreeCmdletBase : PSCmdlet, IDisposable public string? Identity { get; set; } [Parameter] - public string? Server { get; set; } + public string Server { get; set; } = Environment.UserDomainName; + + [Parameter] + [Credential] + public PSCredential? Credential { get; set; } [Parameter(ParameterSetName = DepthParameterSet)] [ValidateRange(0, int.MaxValue)] @@ -60,6 +65,11 @@ protected override void BeginProcessing() { try { + if (Recursive.IsPresent) + { + Depth = int.MaxValue; + } + if (Exclude is not null) { _exclusionPatterns = Exclude @@ -67,13 +77,17 @@ protected override void BeginProcessing() .ToArray(); } - if (Server is null) + if (Credential is null) { - _context = new PrincipalContext(ContextType.Domain); + _context = new PrincipalContext(ContextType.Domain, Server); return; } - _context = new PrincipalContext(ContextType.Domain, Server); + _context = new PrincipalContext( + ContextType.Domain, + Server, + Credential.UserName, + Credential.GetNetworkCredential().Password); } catch (Exception exception) { @@ -83,9 +97,24 @@ protected override void BeginProcessing() protected void Push(GroupPrincipal? groupPrincipal, TreeGroup treeGroup) { - if (Recursive.IsPresent || treeGroup.Depth <= Depth) + if (treeGroup.Depth > Depth) + { + return; + } + + if (treeGroup.Depth == Depth) + { + _truncatedOutput = true; + } + + _stack.Push((groupPrincipal, treeGroup)); + } + + protected void DisplayWarningIfTruncatedOutput() + { + if (_truncatedOutput) { - _stack.Push((groupPrincipal, treeGroup)); + WriteWarning($"Result is truncated as enumeration has exceeded the set depth of {Depth}."); } }