PowerShell: Get *All* Domain User’s Group Memberships

Hi All – Need to get all of a user’s group memberships – both domain and local? Do you need indirect memberships also? Then read on!

I’m busy installing Lync Server 2013 and came to a point where I needed to add my “Lync Admin” user to a local user group on the Lync dedicated front-end.

But – I was pretty sure that my “Lync Admin” user already had this membership, via some indirect group. However, I was too lazy to want to track this down; I want the system to tell me *all* of my user’s group memberships. This includes both domain and local memberships. Sounds simple, but it was more complex than I thought.

I turned to PowerShell for the answer. Here’s what I do:

      Get the domain group memberships for my user. This is easy enough; $adGroups = (Get-AdUser $userName -Properties MemberOf).MemberOf
      Found and modified a recursive function that would go check indirect group memberships, passing in the groups I got from above as a starter set.
      Then things got tricky; there’s no “recursive” local group membership that I could find. So I resorted to an ugly set of nested loops to check for all group memberships.

It’s a simple script although not particularly fast. Hope it’s useful to all!

# list-user-groups.ps1, ABr, 20141231
# list all group memberships - domain and local - for the passed account.
# assumes current domain (just use the account name, no domain).
# example use: list-user-groups.ps myAccount
param(
  [string]$userName
)

# we require AD module
import-module activedirectory

# this recursive function takes in a list of groups and recursively
# gets all indirect group memberships from the current domain.
Function Get-ADGroupsRecursive{
  Param([String[]]$Groups)
  Begin{
    $Results = @()
  }
  Process{
    ForEach($Group in $Groups){
      $Results+=$Group
      ForEach($Object in (Get-ADGroupMember $Group|?{$_.objectClass -eq "Group"})){
        $Results += Get-ADGroupsRecursive $Object
      }
    }
  }
  End{
   $Results | Select -Unique
  }
}

# we'll assume we're running on the current system
$Computer = [ADSI]"WinNT://$env:COMPUTERNAME"

# all groups is the result (starts empty)
$allGroups = @()

# get the Active Directory user object, including group memberships
$user = Get-AdUser $userName -Properties MemberOf

# now expand the current list
$userAdGroups = $user.MemberOf
$userAdGroups += $userAdGroups | %{Get-ADGroupsRecursive $_}

# remove any duplicates
$userAdGroups = $userAdGroups | Select -Unique

# the AD groups come back with FQDN. this is nice, but the PsBase calls
# below just return the group name. to make it easy for me, I'm extracting
# the group name here from the FQDN.
ForEach ($userAdGroup in $userAdGroups) {
  $found = $userAdGroup -Match 'CN=([^,]+).*'
  If ($found) { $allGroups += $matches[1] }
}

# now get all local groups
$localGroups = $Computer.psbase.Children | Where {$_.psbase.schemaClassName -eq "group"}

# this gets ugly. we need to iterate over all local groups repeatedly.
$foundGroup = $TRUE
$iterCount = 0
while ($foundGroup) {
  # keep track of how many times we've done this foolishness...
  $iterCount += 1
  Write-Debug "Iteration $iterCount..."

  # reset our flag; we'll assume we don't find any more indirect memberships
  $foundGroup = $FALSE
  ForEach ($localGroup in $localGroups) {
    # get the name of this local group
    $localGroupName = $localGroup.GetType().InvokeMember("Name", 'GetProperty', $Null, $localGroup, $Null)

    # quick check to see if the local group is already in our result list
    $foundLocalGroup = $allGroups -Contains $localGroupName
    if ($foundLocalGroup) { Continue }

    # it's not...we need to check this local group by looking at its members
    Write-Debug "  Checking group '$localGroupName'..."
    $localGroupMembers = @($localGroup.psbase.Invoke("Members"))
    ForEach ($localGroupMember In $localGroupMembers) {
      # extract local group member info
      $localGroupMemberClass = $localGroupMember.GetType().InvokeMember("Class", 'GetProperty', $Null, $localGroupMember, $Null)
      $localGroupMemberName = $localGroupMember.GetType().InvokeMember("Name", 'GetProperty', $Null, $localGroupMember, $Null)

      # should this local group be added? this will be the case if the local
      # group member is one of our AD groups *or* if the user account is in
      # the local group
      $addLocalGroup = $False
      $localGroupMemberIsGroup = ([string]::Compare($localGroupMemberClass, "Group", $True) -eq 0)
      if (-Not $localGroupMemberIsGroup) {
        # this local group member is a user...is it us??
        if ([string]::Compare($localGroupMemberName, $userName, $True) -eq 0) {
          # ah, our AD account is in this local group. let's add the local group
          # to our list of groups.
          $addLocalGroup = $True
        }
      } else {
        Write-Debug "    Checking subgroup '$localGroupMemberName'..."

        # the logic here is: is this sub-group in our list of discovered groups?
        $foundLocalGroupMember = $allGroups -Contains $localGroupMemberName
        if ($foundLocalGroupMember) {
          # woohoo. we found an indirect membership.
          $addLocalGroup = $True
        }
      }
      if ($addLocalGroup) {
        # add the local group to our list of all groups
        Write-Debug "      Added local group '$localGroupMemberName'..."
        $allGroups += $localGroupName

        # set our flag to continue the controlling iteration...
        $foundGroup = $True

        # ...but we don't need to check this local group anymore.
        Break
      }
    }
  }

  # so why the outer loop? consider this group membership chain:
  #   domainUser = my account
  #   domainGroup = a domain group with my account
  # on the local computer, we have two groups:
  #   localGroupA - contains localGroupB as a member
  #   localGroupB - contains domainGroup as a member
  # here's the logic:
  #  1. My list of discovered groups starts only with "domainGroup".
  #  2. I check localGroupA - it has only localGroupB as a member, which fails my test.
  #     So, I ignore localGroupA.
  #  3. I check localGroupB - aha! it has domainGroup as a member, and I'm a member
  #     a member of domainGroup, so I add localGroupB to my list.
  # the problem? because I check localGroupA *before* I get to localGroupB, I never
  # discover that I have an indirect membership (via my domain group membership in
  # localGroupB). the outer WHILE loop, in conjunction with my "$foundGroup" flag,
  # solves this problem.
}

# print the list of all groups...sorry, no indication of whether they are
# domain or local. maybe next script ;)
$allGroups

Team-oriented systems mentor with deep knowledge of numerous software methodologies, technologies, languages, and operating systems. Excited about turning emerging technology into working production-ready systems. Focused on moving software teams to a higher level of world-class application development. Specialties:Software analysis and development...Product management through the entire lifecycle...Discrete product integration specialist!

Leave a Reply

Your email address will not be published. Required fields are marked *

*