List all the groups a user is a member of … Thank-You PowerShell

Let me start by saying that if you still cling to vbscript (as I did for so long) I have to say come to the dark side we have cookies. Seriously things are so much better once you get it figured out.

For whatever reason I started working with arrays in this script and learned a few cool things.

Remember how hard it was to actually remove an item from an array?
$array.remove(object)
and this is done using the value of the array member so you don’t even need to know it’s address!

recall having to write whole loops to bubble sort your array? another one liner now.

[Array]::Sort([array]$ArrayName)

And perhaps my favorite in this script, how to loop through an array and see if it contains a value, again a single line.

$array -contains "value"

The other thing I do a lot in this script is work with AD objects. Search for a user find all the groups they are a member of then bind each group and check its members etc etc. Lots of good examples of how to get a handle on an AD object. took me a long time to get it right but once you do it’s easy.

$Object= [ADSI]("LDAP://"+ $x)

where $x is the distinguished name of the object…. don’t know the DN? use the searcher

$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$searcher.Filter = "(&(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone()

The only trick now that you have the name is actually getting the AD object to work with.

$user=$result.GetDirectoryEntry()

Starting to enjoy PowerShell yet?

# for more info s@blackops.ca
#The script will prompt you for a user logon name (no domain)
#If you click cancel or don’t enter a name the script will exit
#The script will continue to prompt you until it gets a valid username.
#The script will then load all the groups the user is a member of and go through all the nested groups reporting the hierarchy and noting any groups the use is a member of more than once.
#At the end it will output a single list sorted by group name to make it easier to find a specific group.

# This is the core function it takes an array of AD groups via distinguished name and a depth field to help with spacing in the output. For each group that is a member of a previous group it will call itself with the new array and depth +1
function GroupEnnumerate ([System.Collections.ArrayList]$InputArray, [int]$Depth)
{
$spacer=""
for ($i=1;$i -le $Depth; $i++) {$spacer=$spacer + "`t"}
#the two above lines add a tab char foe each level deep to keep the output formatted

While ($InputArray.count -gt 0) #repent until the input array is empty
{
#The next line is important to make sure we have not already processed a group.
#This prevents an endless loop if a group is a member of itself either directly or indirectly
if (!($expandedgrouparray -contains $InputArray[0]))
{
$GroupName = $InputArray[0]
$expandedgrouparray.add($InputArray[0])|Out-Null #add the new group to the array of processed groups
$Group = [ADSI]("LDAP://"+ $groupname) #lookup the group in AD
$expandedgrouparraynames.add($group.name.tostring())|Out-Null #add the group friendly name to an array for the output at the end
Write-Host $spacer($Group.Name) #display the group in the output
$TempGroupArray = New-Object System.Collections.ArrayList $null #blank and init the temp array of sub groups
foreach ($x in $group.memberof) # for each sub-group do the following
{
$member = [ADSI]("LDAP://"+ $x) #lookup the memberof  in AD

If ($member.SchemaClassName -eq "group") #we don't care if the member is not a group, just in case...
{
$TempGroupArray.add($x)|Out-Null #add the sub-group to a temp array to be used when calling the function for the next loop
}
}
if ($TempGroupArray.Count -gt 0) #if we found any sub-groups start checking them
{
[Array]::Sort([array]$TempGroupArray) # sort the array so he output is easier to read
GroupEnnumerate $TempGroupArray ($Depth+1) # call the function again with the temparray if sub-groups and add another tab to the output
$tempGroupArray=$null #blank out the temp array
}
$InputArray.remove($InputArray[0]) #as we process each group in the input array we need to remove it
}
else
{
#If we have already processed the group once then just report and remove it from the input array
write-Host -ForegroundColor Red $spacer([ADSI]("LDAP://"+ ($InputArray[0]))).name "- Already a member"
$InputArray.remove($InputArray[0])
}
}
}

cls
$StartGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArray = New-Object System.Collections.ArrayList
$ExpandedGroupArrayNames = New-Object System.Collections.ArrayList
#I create the arrarys as above so I can use the .add .remove etc

$searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]'')
$user=$null
$PromptMessage="Please enter the user logon name"

#the following do loop continues until you don't enter anything or you enter a good username
do
{
$SAMAccountName=$null
$SAMAccountName = Read-Host $PromptMessage

if ($SAMAccountName) #if there was ANY input in the box
{
$searcher.Filter = "(&(objectClass=User)(samAccountName=$SAMAccountName))"
$result = $searcher.Findone() # search AD for the requested user
if ($result)
{
$user=$result.GetDirectoryEntry()
# if we find a user in ID then get the AD object to work with later
}
else
{
# if we don't find a user update the message prompt so the user knows something happened
$PromptMessage= "User " + $samaccountname + " not found - Please enter the user logon name"
}
}
else
{
exit #if there was not ANY input in the box quit the whole script
}
}
while (!$user) # until we have a valid user object in AD keep pestering the operator

$groups = $user.memberof # get a list of all the groups that this user is a member of
foreach($group in $groups)
{
#for each group that they are a member of add the group DN to the array for processing
$StartGroupArray = $startGroupArray + $group
}
Write-Host "User: " $user.displayName " " $user.sAMAccountName " is a member of:" #this is the header of the output
[Array]::Sort([array]$startGroupArray)#fort the array so the output is easier to read
GroupEnnumerate $startGroupArray 1 #initiate the top-level group check
Write-Host # space in the output
Write-Host "Complete list sorted by group name" #second output header
$ExpandedGroupArraynames | Sort-Object #sorted list of all the groups processed that the user is a member of.
Write-Host # space in the output
Write-Host "Script Complete"

16 thoughts on “List all the groups a user is a member of … Thank-You PowerShell

  1. Eric

    Thanks this works perfectly, but how can I get it to ouput to a text file instead of only in powershell window? Thanks

  2. Scott Garrett

    No problem

    Add the following at the top of the script

    [System.IO.FileInfo]$outputFile = “C:\output.txt”
    $ReportOnly = $false
    if ($outputFile.Exists)
    {
    $outputFile.Delete()
    }

    Then for every Write-Host that you want to go to a file replace it with
    Out-File $outputFile.FullName -Append

  3. Scott Garrett

    Jimmie pointed out that this was not working quite right for him and was kind enough to document the results in great detail.

    This resulted in one tiny change that very much changes the results.

    Line 27 changed from
    foreach ($x in $group.member)
    to
    foreach ($x in $group.memberof)

    Thanks again Jimmie

  4. Nikhil Vaish

    By Default its picking the doamin from where i am running the script.

    How i can use this script to check for multiple domain..

    tried to replace LDAP with GC but didnt worked…

    Need help…

  5. Scott Garrett

    If you only want to check one other domain you can:
    Look for the following
    $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]”)

    Change it to
    $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]”LDAP://**Domain Distinguished Name here**”)

    If you want to search multiple domains (within one forest)
    you could create a loop with something like this

    $forest= [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    $domains= $forest.Domains
    foreach ($domain in $Domains)
    {
    $domainDN=$domain.GetDirectoryEntry().distinguishedName
    something…..
    }

    let me know if this is what you were looking for

  6. Nikhil Vaish

    Thank you Scott…now can check for other domain user also..

    Not sure where can add the for loop to check in multiple domain in forest

  7. Scott Garrett

    Do you have multiple forests you are trying to check?
    How many domains/forests do you want to check?

  8. Scott Garrett

    Try this as a replacement for lines 53-89
    (sorry not enough time for testing the whole script)

    cls
    $StartGroupArray = New-Object System.Collections.ArrayList
    $ExpandedGroupArray = New-Object System.Collections.ArrayList
    $ExpandedGroupArrayNames = New-Object System.Collections.ArrayList
    #I create the arrarys as above so I can use the .add .remove etc

    $forest= [System.DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest()
    $domains= $forest.Domains
    $DomainDNs = @()
    foreach ($domain in $Domains)
    {
    $DomainDNs = $DomainDNs + $domain.GetDirectoryEntry().distinguishedName
    }

    $user=$null
    $PromptMessage=”Please enter the user logon name”

    do
    {
    $SAMAccountName=$null
    $SAMAccountName = Read-Host $PromptMessage

    if ($SAMAccountName) #if there was ANY input in the box
    {
    foreach ($serachDomain in $DomainDNs)
    {
    $searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]”LDAP://$serachDomain”)
    $searcher.Filter = “(&(objectClass=User)(samAccountName=$SAMAccountName))”
    $result = $searcher.Findone() # search AD for the requested user
    if ($result)
    {
    $user=$result.GetDirectoryEntry()
    # if we find a user in ID then get the AD object to work with later
    }
    }
    If (!$user)
    {
    ## if we don’t find a user update the message prompt so the user knows something happened
    $PromptMessage= “User ” + $samaccountname + ” not found – Please enter the user logon name”
    }

    }
    else
    {
    exit #if there was not ANY input in the box quit the whole script
    }
    }
    while (!$user) # until we have a valid user object in AD keep pestering the operator

  9. Johan Greefkes

    great article, really helped me get this group membership info without relying on external components. I did make some changes that helped for my situation that I thought I’d share:

    The function I changed to accept a string only, plus added a limited recurse depth:
    function get-RecursiveMembers {
    param([string]$DN,[int]$MaxRecurse)
    if ($maxRecurse -gt 0) {
    foreach ($group in ([ADSI]”LDAP://$DN”).memberof) {
    ([ADSI]”LDAP://$group”).name # print group name
    get-RecursiveMembers $group ($MaxRecurse -1)
    }
    }
    }

    Then to call it, feed in the Distinguished Name of the user, as well as the max depth to prevent a looping group membership from hanging the script:

    get-RecursiveMembers $user.distinguishedName 10

    To use this function to retrieve an array of unique groups, use this:

    $AllGroups = get-RecursiveMembers $user.distinguishedName 10 | Sort-Object | Select-Object -Unique

  10. Scott Garrett

    I am sure there is, I am sorry it’s been so long but I am in the middle of a launch.
    I hope to be able to get back to this early in the new year.

  11. Vicky

    Hi All,

    I need almost similar script in PowerShell. I’ve the current script which is written in VBScript. I am using it to fetch the AD groups for any user, but its fetching groups along with the group type and the owner of that groups.

    Please see if you can help me and give me the replacement of this in PS.

    Thanks in advance.

    1. “ShowGroups” helps us to see what groups that a user belongs to.
    The output is a CSV file stored under R:\Scripts\GetGroups\Output\ and it is opened automatically in Excel.

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ User’s groups dump script
    ‘ INPUT: UserName
    ‘ If the User name is different from the pre-Windows 2000 logon name,
    ‘ you must specify the pre-Windows 2000 name

    ‘ DEPENDANCIES: WSH5.6, Windows 2000 or greater
    ‘ Read access to user accounts in the specified domain
    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ Variables

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘*** Some base variables we’ll use later
    Dim strUserName ‘String value for user logon name
    Dim strADsPath ‘String value to hold the full LDAP path to the user object
    Dim objRootDSE ‘LDAP object to keep connection open to the AD LDAP directory
    Dim strDomainDN ‘Default Naming Contect for the domain you are logged into
    Dim strUserDN ‘Distinguished Name for the user object
    Dim strGroupSupport ‘Support group for the group object extensionAttribute11

    ‘**** We need to know the date for our output file ***

    trWeekDay = Weekday(Date)
    strDate = Replace(Date,”/”,”-“)
    strTime = time
    strTimeout1 = Replace(time,”:”,”.”)
    strTimeout = Replace(strtimeout1,” “,”.”)

    ‘*** Bind to rootDSE so we can retrieve the domain DN ***
    Set objRootDSE = GetObject(“LDAP://RootDSE”)
    strDomainDN = objRootDSE.Get(“DefaultNamingContext”)

    ‘*** We need to connect to the local computer to execute this file later ***

    Set objShell = CreateObject(“Wscript.Shell”)
    strComputer = “.”

    Set objWMIService = GetObject(“winmgmts:\\” & strComputer & “\root\cimv2”)

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ prompt for user name

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘Check for required parameters, should be one and only one

    If WScript.Arguments.Count 1 Then
    ‘Wrong number of parameters, get a name
    strUserName = inputbox( “Please Enter User name (pre-2000 version):”, “User Name Input” )
    Else
    ‘Parameter found
    strUserName = WScript.Arguments(0)
    End If

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘Get the distinguishedName for the supplied user

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    On Error Resume Next

    Const ADS_SCOPE_SUBTREE = 2

    Set objConnection = CreateObject(“ADODB.Connection”)
    Set objCommand = CreateObject(“ADODB.Command”)
    objConnection.Provider = “ADsDSOObject”
    objConnection.Open “Active Directory Provider”
    Set objCommand.ActiveConnection = objConnection

    objCommand.Properties(“Page Size”) = 1000
    objCommand.Properties(“Searchscope”) = ADS_SCOPE_SUBTREE

    ‘objCommand.CommandText = _
    ‘ “SELECT distinguishedName FROM ‘LDAP://dc=iam,dc=intranet’ WHERE objectCategory=’user’ ” & _
    ‘ “AND sAMAccountName='” & strUserName & “‘”
    ‘Set objRecordSet = objCommand.Execute

    objCommand.CommandText = _
    “SELECT distinguishedName FROM ‘LDAP://dc=iam,dc=intranet’ WHERE sAMAccountName='” & strUserName & “‘”
    Set objRecordSet = objCommand.Execute

    objRecordSet.MoveFirst
    Do Until objRecordSet.EOF
    strUser = objRecordSet.Fields(“distinguishedName”).Value
    objRecordSet.MoveNext
    Loop

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ Connect to the file we want to use

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    strPath = “R:\scripts\Output\GetGroups\” & strUserName & “-” & strDate & “-” & strTimeout &”.csv”

    Set objFSo = CreateObject(“Scripting.FileSystemObject”)
    Set objFile = objFSO.CreateTextFile(strPath)

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ Connect to our user

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    On Error Resume Next

    ‘Set objADSysInfo = CreateObject(“ADSystemInfo”)
    ‘strUser = objADSysInfo.UserName

    Set objUser = GetObject(“LDAP://” & strUser)

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    ‘ Start writing data to the file

    ‘+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    objFile.WriteLine “User Login Name: ” + strUserName
    objFile.WriteLine “Account Location: ” + strUser
    objFile.WriteLine
    objFile.WriteLine “Group,Group Type,Support Group,Owner E-mail,Owner Lastname,First Name”

    ‘***************************************************LOOP***********************************

    ‘If the user is a member of only one group, then the object returned from
    ‘the memberOf attribute will not be in an array format and we will create
    ‘our own array with the Array function

    If IsArray(objUser.Get(“memberOf”)) Then
    colGroups = objUser.Get(“memberOf”)
    Else
    colGroups = Array(objUser.Get(“memberOf”))
    End If

    For Each strGroup in colGroups

    ‘For Each strGroup in objUser.memberOf
    Set objGroup = GetObject(“LDAP://” & strGroup)

    ‘==========================================================================
    ‘Change group name to something readable
    ‘==========================================================================

    objGname = objGroup.Get(“Name”)

    ‘==========================================================================
    ‘Get managed by and add it to a variable then change it to CN
    ‘==========================================================================

    objXname = objGroup.Get(“managedBy”)

    If objXname “” Then
    Set objOwnerName = GetObject(“LDAP://”+objXname)
    objOwner = objOwnerName.CN
    objOwnermail = objOwnerName.mail
    Else
    objOwner = “”
    objOwnermail = “”
    End If

    ‘==========================================================================
    ‘Get group type (Security vrs Distro) and add it to a Variable objGroupType
    ‘==========================================================================

    If objGroup.groupType < 0 Then
    objGroupType = "security"
    Else
    objGroupType = "distribution"
    End If

    '==========================================================================
    'Get Support group
    '==========================================================================

    objSupportGroup = objGroup.Get("extensionAttribute11")

    '==========================================================================
    'Output what we know
    '==========================================================================

    objFile.WriteLine objGname + " , " + objGroupType + " , " + objSupportGroup + " , " + objOwnermail + " , " + objOwner

    '==========================================================================
    'Erase what we know
    '==========================================================================

    objXname = ""
    objSupportGroup = ""
    objGname = ""
    objGroupType = ""
    objOwnermail = ""
    objOwner = ""

    Next

    '***********************************************END LOOP***********************************

    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    '
    ' Close the CSV file and open it in Excel
    '
    '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

    objFile.Close
    objShell.Run("excel.exe " & strPath)

  12. Jakob Hage

    Hello!
    Many thanks for your great work!!
    I noticed the primary group of users AD Object is missing. Mostly it will be the Domain Users group, which may be member of other groups also.

    I have added these code to get the DN of the Primary Group, and added it to the $groups array

    $searcher.Filter = “(&(objectClass=User)(samAccountName=$SAMAccountName))”
    $result = $searcher.Findone() # search AD for the requested user
    $user=$result.GetDirectoryEntry()

    # Read primary group ID
    $PrimaryGroupID = $user.primaryGroupID

    $objUser = New-Object System.Security.Principal.NTAccount(“$SAMAccountName”)
    $strUserSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])

    # Get domain SID
    $strDomainSID = $strUserSID.Value.Substring(0,$strUserSID.Value.lastindexofany(“-“)+1)

    # Build SID of primary Group
    $strPrimaryGroupSID = $strDomainSID + $PrimaryGroupID

    # echo $strPrimaryGroupSID
    $objPrimaryGroup = [ADSI]”LDAP://”
    $objPrimaryGroup.distinguishedName

    You may probably also update your script.

  13. Scott Post author

    Good catch, some of these things feel strange and difficult to get considering how simple it appears.
    I have not updated the main script because I don’t have time to test right now, but the code looks good so if that’s what you need here it is.
    thanks.

Leave a Reply

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