Get-ADGroupMembers.ps1
<#PSScriptInfo .VERSION 1.0 .GUID ffc2dd5e-5612-4605-ad74-3df6b249de78 .AUTHOR Mark Holderness .PROJECTURI https://github.com/mholderness/Get-ADGroupMembers #> <# .DESCRIPTION Get-ADGroupMembers supporting large groups. Returns ADObjects Avoids: msds-memberTransitive (limited in query results to 4500) and a subsequent call to AD to return AD object properties for each distinguishedName in the results. Get-ADGroupMember -Recursive (default Active Directory Web Services MaxGroupOrMemberEntries limit of 5000) ... and a subsequent call to Get-ADObject if the ADPrincipal object properties are not sufficient. LDAP_MATCHING_RULE_IN_CHAIN (which has none of the shortcomings of the above approaches but) can be very slow. .SYNOPSIS Get-ADGroupMembers supporting large groups (< 5000) .OUTPUTS ADObject https://docs.microsoft.com/en-us/dotnet/api/microsoft.activedirectory.management.adobject?view=activedirectory-management-10.0 .EXAMPLE $DirectMembersOfBigGroup = Get-ADGroupMembers 'BigGroup' -Verbose .EXAMPLE $IndirectMembersOfBigGroup = Get-ADGroupMembers 'BigGroup' -Indirect -Verbose .EXAMPLE $RecursiveMembersOfBigGroup = Get-ADGroup 'BigGroup' | Get-ADGroupMembers -Recursive -Verbose #> [cmdletbinding(DefaultParameterSetName="GetDirectMember")] Param( <# Specifies an Active Directory group object. See the requirements of the Identity parameter of Get-ADGroup for more information: 'Get-Help -Name Get-ADGroup -Parameter Identity' #> [Parameter(Mandatory,Position=0,ValueFromPipeline)]$Identity, <# Specifies whether to include ADObjects with objectClass -eq group. Groups are not returned by default. #> [switch]$IncludeGroups, <# Specifies to get all members in the hierarchy of a group that do not contain child objects. #> [Parameter(ParameterSetName='GetRecursiveMember')][switch]$Recursive, <# Specifies to get all indirect members in the hierarchy of a group that do not contain child objects. In other words, return all members of directly nested groups of a group. #> [Parameter(ParameterSetName='GetIndirectMember')][switch]$Indirect, <# Specifies the properties to return for each group member. #> [string[]]$MemberProperties = @("distinguishedName","name","objectClass") ) Begin { #Hash Table used by the Get-ADObjectMemberOfGroup inner function to avoid infinite recursion. $ADGroupProcessed = @{} #Hash Table used by the Get-ADObjectMemberOfGroup inner function to avoid returning duplicate objects. $ADGroupMemberSeen = @{} #Hash Table used to splat parameters on calls to the Get-ADObjectMemberOfGroup inner function. $GetADObjectMemberOfGroup = @{} $GetADObjectMemberOfGroup.MemberProperties = $MemberProperties If($IncludeGroups) { $GetADObjectMemberOfGroup.IncludeGroups = $IncludeGroups } Function Get-ADObjectMemberOfGroup { [cmdletbinding()] Param( [Parameter(Mandatory,Position=0,ValueFromPipeline)]$Identity, [string[]]$MemberProperties = @("distinguishedName","name","objectClass"), [switch]$Recursive, [switch]$IncludeGroups ) Begin { If($Recursive) { If(!$ADGroupProcessed){ Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : Create ADGroupsProcessed hash table to track process group to avoid infinite recursion." $ADGroupProcessed = @{} } If(!$ADGroupMemberSeen){ Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : Create ADGroupMembersSeen hash table to avoid returning duplicate members." $ADGroupMemberSeen = @{} } If(-Not $MemberProperties -Contains "objectClass"){ #objectClass required to trigger recursive function calls for member groups. $MemberProperties += "objectClass" } $GetADObjectMemberOfGroupSplat = @{} $GetADObjectMemberOfGroupSplat.MemberProperties = $MemberProperties If($IncludeGroups) { $GetADObjectMemberOfGroup.IncludeGroups = $IncludeGroups } } $GetADObjectSplat = @{} $GetADObjectSplat.Properties = $MemberProperties } Process { ForEach($Group in $Identity){ Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : $Group" [array]$ADGroup = Get-ADGroup $Group If($ADGroup.Count -eq 1) { $ADGroupIdentity = $ADGroup.distinguishedName If($ADGroupProcessed.ContainsKey($ADGroupIdentity)) { Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : Skipping group to avoid infinite recursion: $($ADGroup.distinguishedName)" } Else { $ADGroupProcessed.Add($ADGroupIdentity,"") If($Recursive) { $Member = Get-ADObject -LDAPFilter "(&(memberOf=$($ADGroup.distinguishedName)))" @GetADObjectSplat #Return members that are not groups and haven't previously been returned (where an object is a member of more than one group in the hierarchy) $Member | ForEach-Object { If($ADGroupMemberSeen.ContainsKey($PSItem.distinguishedName)) { Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : Skipping ADObject to avoid returning duplicates: $($PSItem.distinguishedName)" } Else { $ADGroupMemberSeen.Add($PSItem.distinguishedName,"") If($PSItem.objectClass -eq 'group' -And -Not $IncludeGroups) { Write-Verbose "$(Get-Date) | Get-ADObjectMemberOfGroup : Skipping ADObject. objectClass -eq group and IncludeGroups -eq $IncludeGroups : $($PSItem.distinguishedName)" } Else { $PSItem } } } [array]$MemberGroup = $Member | Where-Object { $PSItem.objectClass -eq 'group' } If($MemberGroup.Count -ge 1){ Get-ADObjectMemberOfGroup $MemberGroup @GetADObjectMemberOfGroupSplat -Recursive } } Else { If($IncludeGroups) { Get-ADObject -LDAPFilter "(&(memberOf=$($ADGroup.distinguishedName)))" @GetADObjectSplat } Else { Get-ADObject -LDAPFilter "(&(memberOf=$($ADGroup.distinguishedName))(!(objectClass=group)))" @GetADObjectSplat } } } } } } } } Process { ForEach($Group in $Identity){ Write-Verbose "$(Get-Date) | Get-ADGroupMembers : $Group" [array]$ADGroup = Get-ADGroup $Group If($ADGroup.Count -eq 1) { If($Indirect) { Write-Verbose "$(Get-Date) | Get-ADGroupMembers : $($ADGroup.Name) : Indirect : Searching for direct members with objectClass -eq group and returning transitive members of each." [array]$MemberGroup = Get-ADObject -LDAPFilter "(&(memberOf=$($ADGroup.distinguishedName))(objectClass=group))" If($MemberGroup.Count -ge 1) { Get-ADObjectMemberOfGroup $MemberGroup @GetADObjectMemberOfGroup -Recursive } } ElseIf($Recursive) { Write-Verbose "$(Get-Date) | Get-ADGroupMembers : $($ADGroup.Name) : Recursive : Searching for transitive members." Get-ADObjectMemberOfGroup $ADGroup.distinguishedName @GetADObjectMemberOfGroup -Recursive } Else { Write-Verbose "$(Get-Date) | Get-ADGroupMembers : $($ADGroup.Name) : Searching for direct members." Get-ADObjectMemberOfGroup $ADGroup.distinguishedName @GetADObjectMemberOfGroup } } Else { Write-Warning "$(Get-Date) | Get-ADGroup $Group returned none or more than one." } } } |