Solved

Export the security groups a user belongs to

Posted on 2013-11-27
3
1,058 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
  • 2
3 Comments
 
LVL 70

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 70

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

Free Trending Threat Insights Every Day

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

Active Directory replication delay is the cause to many problems.  Here is a super easy script to force Active Directory replication to all sites with by using an elevated PowerShell command prompt, and a tool to verify your changes.
In this previous article (https://oddytee.wordpress.com/2016/05/05/provision-new-office-365-user-and-mailbox-from-exchange-hybrid-via-powershell/), we made basic license assignments to users in O365. When I say basic, the method is the simplest way …
This tutorial will walk an individual through the process of transferring the five major, necessary Active Directory Roles, commonly referred to as the FSMO roles from a Windows Server 2008 domain controller to a Windows Server 2012 domain controlle…
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …

706 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

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now