Solved

Export the security groups a user belongs to

Posted on 2013-11-27
3
1,109 Views
Last Modified: 2014-01-27
Hi,

We need to be able to create a report whether that be in excel format or html that will list all which groups (specifically security) as user belongs to. Nested groups also need to be captured. Can anyone help?

I have been using the below so far which does return results but i can't figure out how to filter it to security groups only. Also if i could just pass a text file with names to it instead that would be a bonus too.

#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"

Open in new window

0
Comment
Question by:stealth82
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
3 Comments
 
LVL 71

Accepted Solution

by:
Chris Dent earned 500 total points
ID: 39683287
Hey,

Happily it can be simplified a little. LDAP filters are more flexible than you might think.

The filters used here are derived from the documentation here:

http://msdn.microsoft.com/en-us/library/windows/desktop/aa746475(v=vs.85).aspx

The first we use test for the Security flag on the group (the value for that is documented here http://msdn.microsoft.com/en-us/library/windows/desktop/ms675935(v=vs.85).aspx). To do that, we must perform a Binary AND comparison against the groupType attribute. The OID 1.2.840.113556.1.4.803 lets us do that.

The second allows us to have AD tell us about nested group membership, it uses LDAP_MATCHING_RULE_IN_CHAIN which is fantastic for chasing nested things (from this, to manager / directReports and a few others inbetween). That's the 1.2.840.113556.1.4.1941 value.

$Username = "cdent"

# With Quests AD CmdLets:

Get-QADMemberOf $Username -Indirect -LdapFilter "(groupType:1.2.840.113556.1.4.803:=2147483648)"

# Primary groups are excluded from this search. They can be added if required.

# Just good old ADSI
$UserDN = ([ADSISearcher]"(sAMAccountName=$Username)").FindOne().Properties['distinguishedname']

# Query must be executed against a system running no less than 2003 SP1 for this to work.
$Searcher = [ADSISearcher]"(&(member:1.2.840.113556.1.4.1941:=$UserDN)(groupType:1.2.840.113556.1.4.803:=2147483648))"
$Searcher.PageSize = 1000

# Applicable only if you have a Forest to search
$ForestRootDN = ([ADSI]"LDAP://RootDSE").rootDomainNamingContext
$Searcher.SearchRoot = [ADSI]"GC://$ForestRootDN"

# User is a member of these
$Searcher.FindAll() | Select-Object `
  @{n='Name';e={ $_.Properties['name'][0] }},
  @{n='DN';e={ $_.Properties['distinguishedname'] }}

Open in new window

Please let me know if you have any questions about this.

Cheers,

Chris
0
 
LVL 71

Expert Comment

by:Chris Dent
ID: 39683299
Tweaked slightly to allow reading of usernames from a file, sorting of the return value, and exporting to another file.
# Reading usernames from a file
Get-Content YourListOfUsers.txt | ForEach-Object {
  $Username = $_

  # Primary groups are excluded from this search. They can be added if required.

  # Just good old ADSI
  $UserDN = ([ADSISearcher]"(sAMAccountName=$Username)").FindOne().Properties['distinguishedname']

  # Query must be executed against a system running no less than 2003 SP1 for this to work.
  $Searcher = [ADSISearcher]"(&(member:1.2.840.113556.1.4.1941:=$UserDN)(groupType:1.2.840.113556.1.4.803:=2147483648))"
  $Searcher.PageSize = 1000

  # Applicable only if you have a Forest to search
  # $ForestRootDN = ([ADSI]"LDAP://RootDSE").rootDomainNamingContext
  # $Searcher.SearchRoot = [ADSI]"GC://$ForestRootDN"

  # User is a member of these
  # $Searcher.FindAll() | Select-Object `
  #  @{n='Name';e={ $_.Properties['name'][0] }},
  #  @{n='DN';e={ $_.Properties['distinguishedname'] }}

  # Implementing sorting. Modify the command above to chain Sort-Object
  $Searcher.FindAll() | Select-Object `
      @{n='Username';e={ $Username }},
      @{n='GroupName';e={ $_.Properties['name'][0] }},
      @{n='GroupDN';e={ $_.Properties['distinguishedname'] }} |
    Sort-Object GroupName

# Optional export of everything to another file
} | Export-Csv TheBigGroupReport.csv -NoTypeInformation

Open in new window

Chris
0
 
LVL 2

Expert Comment

by:MilesLogan
ID: 39686162
Subsun helped me with the Quest tools script below .

Add the SamAccountNames to user.txt

GC C:\user.txt | Get-QADUser | %{
      New-Object PSObject -Property @{
      Firstname = $_.Firstname
      Lastname = $_.Lastname
      DisplayName = $_.DisplayName
      Email = $_.Email
      Title = $_.Title
      group = ""
      }
$_.memberof | %{
      New-Object PSObject -Property @{
      Firstname = ""
      Lastname = ""
      DisplayName = ""
      Email = ""
      Title = ""
      group = ($_ -Split ",")[0] -replace "CN="
      }
 }
} | Select Firstname,Lastname,DisplayName,Email,Title,group | Export-Csv C:\report.csv -NoTypeInformation
0

Featured Post

Is Your AD Toolbox Looking More Like a Toybox?

Managing Active Directory can get complicated.  Often, the native tools for managing AD are just not up to the task.  The largest Active Directory installations in the world have relied on one tool to manage their day-to-day administration tasks: Hyena. Start your trial today.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Had a business requirement to store the mobile number in an environmental variable. This is just a quick article on how this was done.
After seeing many questions for JRNL_WRAP_ERROR for replication failure, I thought it would be useful to write this article.
The viewer will learn how to count occurrences of each item in an array.
Exchange organizations may use the Journaling Agent of the Transport Service to archive messages going through Exchange. However, if the Transport Service is integrated with some email content management application (such as an antispam), the admini…

724 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question