Google

2013-01-11

Getting Members of Large Groups via PowerShell


PowerShell AD module make checking group membership a trivial task. If I wanted to get the list of members for a group, I can use Get-ADGroupMember cmdlet. Problem with module is that, it's an 'extra' component on top of standard installation and if you will use it, you need to make sure that your script will have access to it. You may be using PowerShell as your logon/start-up script and your machines may not have the AD module. Yes, there are techniques remote-session techniques that can be employed to work around this but most would prefer to enumerate group members some other way.

One common method is using .Net classes, specifically "DirectoryServices.DirectorySearcher". I had a function that was using this class to enumerate group members, something like this:

function Get-GroupMembers {

  param ([string]$group)

  $searcher=new-object directoryservices.directorysearcher   
  $filter="(&(objectClass=group)(cn=${group}))"
  $searcher.PageSize=1000
  $searcher.Filter=$filter
  $result=$searcher.FindOne()
  $members = $result.properties.item("member")
  if ($members) {
    return $members
  }

  return $false

}


I noticed that this stopped working for a specific group, and when I manually looked into the group properties,  I found that "member" property of the group was empty. There was; however, another noticeable property that got populated instead: "member;range=0-1499".

Long story short, when members of a group exceed 1500, AD was populating that property instead of plain 'member' property and I needed to use a different technique called 'ranged retrieval'.

The main change is that we use 'PropertiesToLoad' property to feed $searcher object the range of members we would like to get and loop through them. When we give $searcher object an invalid range, it throws an error at us and we catch that to exit the loop. Here is the modified function.

## Return members of given AD group

function  Get-GroupMembers {

    param ([string]$group)

    if (-not ($group)) { return $false }
  

    $searcher=new-object directoryservices.directorysearcher   
    $filter="(&(objectClass=group)(cn=${group}))"
    $searcher.PageSize=1000
    $searcher.Filter=$filter
    $result=$searcher.FindOne()

    if ($result) {
        $members = $result.properties.item("member")

        ## Either group is empty or has 1500+ members
        if($members.count -eq 0) {                       

            $retrievedAllMembers=$false           
            $rangeBottom =0
            $rangeTop= 0

            while (! $retrievedAllMembers) {

                $rangeTop=$rangeBottom + 1499               

               ##this is how it would show up in AD
                $memberRange="member;range=$rangeBottom-$rangeTop"  

                $searcher.PropertiesToLoad.Clear()
                [void]$searcher.PropertiesToLoad.Add("$memberRange")

                $rangeBottom+=1500

                try {
                    ## should cause and exception if the $memberRange is not valid
                    $result = $searcher.FindOne() 
                    $rangedProperty = $result.Properties.PropertyNames -like "member;range=*"
                    $members +=$result.Properties.item($rangedProperty)          
                   
                     # UPDATE - 2013-03-24 check for empty group
                      if ($members.count -eq 0) { $retrievedAllMembers=$true }
                }

                catch {

                    $retrievedAllMembers=$true   ## we received all members
                }

            }

        }

        $searcher.Dispose()
        return $members

    }
    return $false   
}
Post a Comment