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"
Thanks this works perfectly, but how can I get it to ouput to a text file instead of only in powershell window? Thanks
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 -Append
Out-File $outputFile.FullName
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
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…
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
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
Do you have multiple forests you are trying to check?
How many domains/forests do you want to check?
@Scott : We have one forest and 3 domains..
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
Hi is there a way to just return security groups rather than all groups?
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
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.
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)
Going to post the results in a new thread
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.
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.