Get AD group members using powershell - powershell

I am trying to export groups and their members in following format using powershell.
can somebody help me out here.
Format in CSV:
Group Name | Members
g1 M1
g1 M2
g2 M1
g2 M2
g3 M1
I have got output in CSV format but I am not able to split it out properly.
1st row == DN,member,sAMAccountName
2nd row ==
CN=DSO_ALL_**_AMER_Member_P_17700,OU=PersonalGroups,DC=**,DC=**,DC=**,DC=**,"CN=M123456,OU=Personal,OU=SG,OU=CS,DC=gbl,DC=**,DC=**,DC=**;CN=M123458,OU=Personal,
OU=SG,OU=CS,DC=gbl,DC=**,DC=**,DC=**,DSO_ALL-_**_AMER_Member_P_17700
From the 2nd row I need both member id and group name i.e "M123456" and "M123458"
and group name "DSO_ALL-_**_AMER_Member_P_17700"

Get-ADGroup | Get-ADGroupMember is a good start, however, Get-ADGroupMember does not return group identity, which you pretty much need here. So, you need a little pre-processing and post-processing.
$groups=get-adgroup -filter *
$data=#()
foreach ($group in $groups) {
$gid=$group.name
foreach ($member in (get-adgroupmember -id $group)) {
# now we have both group and member, make an object
$obj=new-object psobject
$obj | add-member -type noteproperty -name "Group Name" -value $gid
$obj | add-member -type noteproperty -name "Member" -value ($member.name)
$data+=$obj
}
}
$data | export-csv file.csv -notypeinformation -encoding utf8
Then you parse your properly prepared CSV. In case you need other attributes, add a respective line of $obj | add-member -type noteproperty.

Related

Powershell to get active user OU, username, and group memberships

I am trying to get a powershell script to export all users in an OU and sub OUs which I can do fine, but when I try to get the user's OU, I get nothing for the OU. I have looked all over online and found a few scripts that pull just the user's OU, but they are a little slow and I can't seem to get them to pull groups or is for pulling from one group instead of listing all users and their groups.
I am trying to export this list and sort by OU so that I can ensure each student is in the proper groups. We have had a few students that were in extra groups and I want a quick and easy look to find those students.
#Student
$Report = #()
#Collect all users
$Users = Get-ADUser -Filter * -SearchBase 'OU=Student,DC=domain,DC=com' -Properties distinguishedname, Name, GivenName, SurName, SamAccountName, UserPrincipalName, MemberOf, Enabled -ResultSetSize $Null
# Use ForEach loop, as we need group membership for every account that is collected.
# MemberOf property of User object has the list of groups and is available in DN format.
Foreach($User in $users){
$UserGroupCollection = $User.MemberOf
#This Array will hold Group Names to which the user belongs.
$UserGroupMembership = #()
#To get the Group Names from DN format we will again use Foreach loop to query every DN and retrieve the Name property of Group.
Foreach($UserGroup in $UserGroupCollection){
$GroupDetails = Get-ADGroup -Identity $UserGroup
#Here we will add each group Name to UserGroupMembership array
$UserGroupMembership += $GroupDetails.Name
}
#As the UserGroupMembership is array we need to join element with ',' as the seperator
$Groups = $UserGroupMembership -join ','
#Creating custom objects
$Out = New-Object PSObject
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
$Out | Add-Member -MemberType noteproperty -Name Name -Value $User.Name
$Out | Add-Member -MemberType noteproperty -Name UserName -Value $User.SamAccountName
$Out | Add-Member -MemberType noteproperty -Name Status -Value $User.Enabled
$Out | Add-Member -MemberType noteproperty -Name Groups -Value $Groups
$Report += $Out
}
#Output to screen as well as csv file.
$Report | Sort-Object DistinguishedName | FT -AutoSize
$Report | Sort-Object DistinguishedName | Export-Csv -Path $env:temp\students.csv -NoTypeInformation
There you go, I added some comments to help you understand the thought process.
This should be a lot faster than what you were doing.
The problem while adding your OUs was here:
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
Which should've been:
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value ($user.distinguishedname -replace '^.+?(?<!\\),','')
#Student
$Report = [system.collections.generic.list[pscustomobject]]::new()
# Using Collection.Generic.List instead of System.Array for efficiency
#Collect all users
$Users = Get-ADUser -Filter * -SearchBase 'OU=Student,DC=domain,DC=com' -Properties MemberOf
# -> Use -SearchScope Subtree if you want to go all the way down in OU recursion starting from the 'OU=Student'
# -> distinguishedname, Name, GivenName, SurName, SamAccountName, UserPrincipalName and Enabled are Default Properties
# of Get-ADUser, no need to call them.
# -> -ResultSetSize $Null is default for Get-ADUSer, no need to call it
# Use ForEach loop, as we need group membership for every account that is collected.
# MemberOf property of User object has the list of groups and is available in DN format.
Foreach($User in $users)
{
#This Array will hold Group Names to which the user belongs.
$UserGroupMembership = [system.collections.generic.list[string]]::new()
#To get the Group Names from DN format we will again use Foreach loop to query every DN and retrieve the Name property of Group.
Foreach($UserGroup in $User.MemberOf)
{
# $GroupDetails = Get-ADGroup -Identity $UserGroup
# -> Instead of this, we can do some string manipulation
# which will be a lot faster and give you the same results.
$UserGroupMembership.Add($UserGroup.Split(',OU=')[0].replace('CN=',''))
}
#As the UserGroupMembership is array we need to join element with ',' as the seperator
$Groups = $UserGroupMembership -join ','
#Creating custom objects
<#
$Out = New-Object PSObject
$Out | Add-Member -MemberType noteproperty -Name DistinguishedName -Value #{Name="DistinguishedName";Expression={$_.distinguishedname | ForEach-Object {$_ -replace '^.+?(?<!\\),',''}}}
$Out | Add-Member -MemberType noteproperty -Name Name -Value $User.Name
$Out | Add-Member -MemberType noteproperty -Name UserName -Value $User.SamAccountName
$Out | Add-Member -MemberType noteproperty -Name Status -Value $User.Enabled
$Out | Add-Member -MemberType noteproperty -Name Groups -Value $Groups
$Report += $Out
-> Again, Add-Member is highly inefficient compared to casting PSCustomObject
-> += is evil ( •̀ᴗ•́ )و ̑̑
#>
$Report.Add(
[pscustomobject]#{
OrganizationalUnit = ($user.DistinguishedName -replace '^.+?(?<!\\),','')
Name = $user.Name
UserName = $user.sAMAccountName
Status = $user.Enabled
Membership = $Groups
})
}
#Output to screen as well as csv file.
$Report | Sort-Object OrganizationalUnit | FT -AutoSize
$Report | Sort-Object OrganizationalUnit | Export-Csv -Path $env:temp\students.csv -NoTypeInformation
I don't know how many users you have but every time you += on an array the entire array plus the new element is copied to a completely new array. This is a bad practice and gets exponentially worse with every item added the array. You can avoid this by building the arrays as a loop result or by using dotnet list object with an efficient add() method.
You also look up the same group names repeatedly. I don't know the numbers but it's probably a lot better to put all your groups in a hashtable once and then look them up.
Your question is unclear, but if you want a list of users and their groups, you are going the long way around. You mention the ou but AFAICS there is no org unit used in the code. Do you want the AD ou property or a part of the DN? You don't seem to be using either.
Note that the DN is a string and sorting by DN will just give an alpha string sort which is not helpful. Are your students in separate org units under OU=students ? This is not clear. If so, use the AD canonicalName to sort the list.
No need to include default properties in -property. Splatting is nice.
You should improve your question by indicating what your AD structure looks like and what you think your output should look like.
Also, format your code for readability.
You want something along these lines:
# group hashtable, for efficient name lookup
$groupName = #{}
$ignoredGroups = #( 'AllStudents','AllUsers', 'etc' ) # don't clutter list with these groups
Get-AdGroup -filter '*' | # any restrictions? searchbase, etc
ForEach-Object {
if ( $ignoredGroups -notcontains $_.Name ) {
$groupName[ $_.distinguishedName ] = $_.Name
}
}
# ADsplat, for readability
$AD_Splat = #{
Filter = '*'
SearchBase = 'OU=Student,DC=domain,DC=com'
Properties = 'MemberOf,CanonicalName,sn,givenName'.split(',') # split to array
ResultSetSize = $Null # !? also, there are system limits to size
}
$results = Get-ADUser #ad_splat |
ForEach-Object {
$DN = $_.distinguishedName # do you need this at all?
$CName = $_.canonicalName # for sorting by AD org unit
$XName = $_.sn + ', ' + $_.givenName
if ( $_.Enabled ) { $Enabled = 'Y'} else { $Enabled = '.' }
$groups = (
$_.memberOf |
ForEach-Object { $GroupName[ $_ ] } | # lookup name
where-Object { $_ } | # ignore nulls (when group not in hashtable)
sort-object # consistent ordering between users
) -join ';' # don't use comma, csv conflict
# leave custom object in pipe! This builds the array efficiently.
New-Object PSObject -Property #{
DistinguishedName = $dn
Name = $_.name
XName = $XName
Login = $_.SamAccountName
CName = $CName
Groups = $Groups
}
} | Sort-Object CName # sort the objects by canonical name
$results | format-table
$results | Export-Csv -Path 'c:\temp\usersgroups.csv' -NoTypeInformation

Counting security groups nested into other security groups

I'm trying to learn the scope of my companies security group nesting. We have a lot of groups nested into others which is causing issues with security token size (IE adding User X to Group Y increases their group membership count by 350+).
I want generate a report that lists all security groups (while excluding distribution groups) and counts the total number of recursive group memberships.
Quest Tools can run on-demand reports without issue "(get-qadgroup "GroupHere").AllMemberOf.count". I'm having problems extracting everything into a report. Here's what I'm trying.
Get-ADGroup -filter {groupCategory -eq 'Security'} |
Select SamAccountName |
ForEach-Object { (get-qadgroup $_.SamAccountName).AllMemberOf.count |
Export-csv -path "C:\_Reports\Security Group Nesting Numbers.csv" -NoTypeInformation -append
}
My logic behind the "-append" is to avoid a loop re-writing the top value. I'm not 100% sure it's necessary.
The Get-ADGroup works fine. The Select works fine. The ForEach does return a list of numbers. I think they're accurate. I'm mainly having trouble exporting the SamAccountName with the associated AllMemberOf.Count.
You could use something like this:
#Create array to hold all groups
$groups = #()
#Run through groups
Get-ADGroup -Filter {groupCategory -eq 'Security'} | Select-Object -ExpandProperty SamAccountName | ForEach-Object {
#Get current group
$currentGroup = Get-ADGroup $_
#Create new custom object
$group = New-Object System.Object
#Add group info to custom object
$group | Add-Member -MemberType NoteProperty -Name "SamAccountName" -Value $currentGroup.SamAccountName
$group | Add-Member -MemberType NoteProperty -Name "Count" -Value $currentGroup.AllMemberOf.Count
#Add custom object to array
$groups += $group
}
#Output array to CSV
$groups | Export-Csv -NoTypeInformation -Path C:\test.csv
The output in the CSV file looks like this:
SamAccountName Count
Group1 2
Group2 3

Powershell Get all groups of ADusers into unique columns in a CSV

Good Afternoon,
I searched through this forum and a few others combining ideas and trying different angles but haven't figured this out. If this has been answered, and I suck at searching, I am sorry and please let me know.
End Goal of script: have an excel file with columns for the AD properties Name, Office, Org, and most importantly a seperate column for each group a user is a member of.
The problem I am running into is creating a new column for each/every group that a user has. Not all users have the same amount of groups. Some have 10 some have 30 (yes 30, our AD is a mess).
Here is what I have done so far, and the spot that I am having difficulty with is towards the end:
$scoop = get-content C:\temp\SCOOP.txt ##This is a text file with a list of user id's, to search AD with
$outfile = 'C:\temp\SCOOP_ID.csv'
$ou = "OU=Humans,OU=Coupeville,DC=ISLANDS" #This is the searchbase, helps AD isolate the objects
Clear-Content $outfile #I clear content each time when I am testing
Foreach($ID in $scoop){
##AD Search filters##
$filtertype = 'SamAccountName'
$filter1 = $ID
##End AD Search filters##
##AD Search --MY MAIN ISSUE is getting the MemberOF property properly
$properties = get-aduser -SearchBase $ou -Filter {$filtertype -eq $filter1} -Properties Name,Office,Organization,MemberOf | select Name,Office,Organization,MemberOf
##AD Search ## Turns the MemberOf property to a string, I tried this during my testing not sure if objects or strings are easier to work with
#$properties = get-aduser -SearchBase $ou -Filter {$filtertype -eq $filter1} -Properties Name,Office,Organization,MemberOf | select Name,Office,Organization, #{n='MemberOf'; e= { $_.memberof | Out-String}}
#Attempt to specify each property for the output to csv
$name = $properties.Name
$office = $properties.Office
$org = $properties.Organization
$MemberOf = $properties.MemberOf
$membersArray = #()
foreach($mem in $MemberOf){ $membersArray += $mem }
###This is what I typically use to export to a CSV - I am sure there are other maybe better ways but this one I know.
$Focus = [PSCustomObject]#{}
$Focus | Add-Member -MemberType NoteProperty -Name 'Name' -Value $name -Force
$Focus | Add-Member -MemberType NoteProperty -Name 'Office' -Value $office -Force
$Focus | Add-Member -MemberType NoteProperty -Name 'Org' -Value $org -Force
$Focus | Add-Member -MemberType NoteProperty -Name 'Groups' -Value $MemberOf -Force
<########################
#
# Main ISSUE is getting the $memberOf variable, which contains all of the users groups, into seperate columns in the csv. To make it more plain, each column would be labeled 'Groups', then 'Groups1', and so on.
I have tried a few things but not sure if any were done properly or what I am messing up. I tried using $memberof.item($i) with a FOR loop but couldnt figure out how to send each
item out into its own Add-Member property.
#
#######################>
##Final Output of the $Focus Object
$Focus | Export-Csv $outfile -Append -NoTypeInformation
}
I came up with this solution, simply loop $MemberOf and add a unique name.
$MemberOf = #('a','b','c','d','e','f') #example
$Focus = [PSCustomObject]#{}
$Focus | Add-Member -MemberType NoteProperty -Name 'Name' -Value 'test' -Force
$i=0; $MemberOf | % {
$i++
$Focus | Add-Member -MemberType NoteProperty -Name "Group $i" -Value $_ -Force
}
$Focus | Export-Csv xmgtest1.csv -Append -Force -NoTypeInformation
However this doesn't work in your case, since you run this code once per user and there's no "awareness" of the entire set. So you would (in your existing architecture) need to ensure that the largest number of groups is added first.
You can fix this by creating the CSV all at once (only other option would be to constantly load/unload the csv file).
First, add $csv = #() to the top of your file.
Then, after you finish creating $Focus, add it to $csv.
Finally, you may remove -Append.
The slimmed down version looks like this:
$csv = #()
#Foreach($ID in $scoop){ etc..
$MemberOf = #('a','b','c','d','e','f') #example
$Focus = [PSCustomObject]#{}
$Focus | Add-Member -MemberType NoteProperty -Name 'Name' -Value 'test' -Force
#$Focus | Add-Member etc ...
$i=0; $MemberOf | % {
$i++
$Focus | Add-Member -MemberType NoteProperty -Name "Group $i" -Value $_ -Force
}
$csv += $Focus
# } end of Foreach($ID in $scoop)
$csv | Export-Csv xmgtest1.csv -NoTypeInformation
Hope this helps.

Adding data to a PowerShell array / Add-Member

I am trying to write a PowerShell script that will compile together a list of groups in Active Directory along with the members of each group. My ultimate goal is to export this out to a CSV files, so I want the final PowerShell multi-dimensional array to have the following format:
GroupName GroupMember
Domain Admins Henry Doe
Domain Admins Melody Doe
Domain Names Doe Ray Me
Domain Users John Doe
Domain Users Jane Doe
(etc…)
I am using the following code to try and do this:
[array]$arrGroupMemberList = New-Object PSObject
Add-Member -InputObject $arrGroupMemberList -membertype NoteProperty -Name 'GroupName' -Value ""
Add-Member -InputObject $arrGroupMemberList -membertype NoteProperty -Name 'GroupMember' -Value ""
[array]$arrGroupMemberList = #()
[array]$arrGroupNameObjects = Get-ADGroup -Filter * | Where-Object {$_.Name -Like "Domain*"}
If ($arrGroupNameObjects.Count -ge 1)
{
## Cycle thru each group name and get the members
$arrGroupNameObjects | ForEach-Object {
[string]$strTempGroupName = $_.Name
$arrGroupMemberObjects = Get-ADGroupMember $strTempGroupName -Recursive
If ($arrGroupMemberObjects.Count -ge 1)
{
## Cycle thru the group members and compile into the final array
$arrGroupMemberObjects | ForEach-Object {
$arrGroupMemberList += $strTempGroupName, $_.Name
}
}
}
}
My problem is, I keep ending up with the following as my array:
Domain Admins
Henry Doe
Domain Admins
Melody Doe
Domain Names
Doe Ray Me
Domain Users
John Doe
Domain Users
Jane Doe
I've tried a few different ways and I've searched but haven’t found the answer anywhere. I’m sure that it is something simple, but what am I doing wrong? Can I create a multi-dimensional array with the necessary data like I am trying to do? If I use the following instead:
## Cycle thru the group members and compile into the final array
$arrGroupMemberObjects | ForEach-Object {
$arrGroupMemberList[$intIndex].GroupName = $strTempGroupName
$arrGroupMemberList[$intIndex].GroupMember = $_.Name
$intIndex++
I end up with errors like:
Property 'GroupMember' cannot be found on this object; make sure it exists and is settable.
Property 'GroupName' cannot be found on this object; make sure it exists and is settable.
Thanks
**UPDATE**
I may have found out where my problem is, it may be when I am adding the array members. At the end of my PowerShell script, I am adding the following line of code:
$arrGroupMemberList | Get-Member
There are no properties, my elements are not there, even though I added them with Add-Member cmdlet earlier in the script. Am I using the Add-Member cmdlet properly?
Looks like you need to use the following line to add rows to your table (two dimensional array).
$arrGroupMemberList += ,($strTempGroupName, $_.Name)
http://blogs.msdn.com/b/powershell/archive/2007/01/23/array-literals-in-powershell.aspx
I figured out what I was doing wrong - I was using Add-Member incorrectly. I was trying to use Add-Member to add members to a collection, and it doesn't seem to work that way. Something that simple but I really didn't see it discussed anywhere. So I found some examples and did the trial-and-error thing and got it to work. So I wanted to post an update back here in case anyone else has the same issue. The following code works just like I want it to (and will create an array with a list of groups from Active Directory along with the group members in each group):
[array]$arrGroupNameObjects = Get-ADGroup -Filter * | Where-Object {$_.Name -Like "Domain*"}
If ($arrGroupNameObjects.Count -ge 1)
{
## Cycle thru each group name and get the members
$arrGroupNameObjects | ForEach-Object {
[string]$strTempGroupName = $_.Name
$arrGroupMemberObjects = Get-ADGroupMember $strTempGroupName -Recursive
If ($arrGroupMemberObjects.Count -ge 1)
{
## Cycle thru the group members and compile into the final array
$arrGroupMemberObjects | ForEach-Object {
$objGroupMember = New-Object PSObject
Add-Member -InputObject $objGroupMember -membertype NoteProperty -Name 'GroupName' -Value $strTempGroupName
Add-Member -InputObject $objGroupMember -membertype NoteProperty -Name 'GroupMemberName' -Value $_.Name
[array]$arrGroupMemberList += $objGroupMember
}
}
}
}

Method to export objects with varying properties?

I hit a common problem with my scripting lately and decided to throw it into the wild to see how other people deal with this problem.
tl;dr; I want to export objects which have a varying number of properties. eg; object 1 may have 3 IP address but object 2 has 7 IP addresses.
I've evolved to creating a new object with custom properties and then injecting these objects into an array as my method of catching results - happy to hear if there is a better way but this is how I roll. This method works 100% when outputting to the screen as the objects are shown in list format - I've tried a number of export/out-file methods to no avail when I want to capture and store the output for reading in something like Excell.
The following is an example of me building an object and storing it (the function of the code is not important here - just the results it generates):
add-pssnapin Quest.ActiveRoles.ADManagement
$Groups = get-qadgroup AmbigousGroupNameHere-
$UserInfo = #()
ForEach ( $Group in $Groups ) {
$CurrentGroupMembers = get-qadgroupmember $Group
write-host "Processing group:" $Group
ForEach ( $GroupMember in $CurrentGroupMembers ) {
If ( $GroupMember.type -eq "User" ) {
$counter = 1
write-host "Processing member:" $GroupMember
$UserObject = get-qaduser $GroupMember | select SamAccountName,FirstName,LastName
$objUserInfo = New-Object System.Object
$objUserInfo | Add-Member -MemberType NoteProperty -Name SamAccountName -Value $UserObject.SamAccountName
$objUserInfo | Add-Member -MemberType NoteProperty -Name FirstName -Value $UserObject.FirstName
$objUserInfo | Add-Member -MemberType NoteProperty -Name LastName -Value $UserObject.LastName
$GroupMembership = get-qadgroup -ContainsMember $GroupMember | where name -like "AmbigousGroupNameHere-*"
ForEach ( $GroupName in $GroupMembership ) {
$objUserInfo | Add-Member -MemberType NoteProperty -Name CtxGroup$counter -Value $GroupName.SamAccountName
$counter++
}
$UserInfo += $objUserInfo
} else {
write-host "This is a group - we are ignoring it."
}
}
}
$UserInfo | Export-Csv UsersOutput.csv -NoType
From the above - you can see I scale the object property name by 1 for each group. CtxGroup$counter allows me to scale an object for the correct number of groups each user has. Confirmed this works great when outputting to the screen by default. The object is listed and I can see a new property for each group that matches for that user.
Now for the problem. When I export-csv or out-file the file is generated with enough headers based off the first object - so it creates the headings based on the amount of properties the first object has. So lets say the first user has 3 matching groups, it will create heading CtxGroup1, CtxGroup2, CtxGroup3. Great! No.
If the next user has 5 matching groups - only the first three are included in the output and the additional 2 are discarded as we don't have headings for CtxGroup4, CtxGroup5.
How on earth do other people deal with this?
side note; I considered creating my first object as a dummy with a massive amount of object (and hence headings) but well - that is not cool and really makes me feel inefficient.
You can obtain what you want ordering $UserInfo array by the number of properties, it can be done, but it's not so simple, in your case I would add another propertie with the count of groups added:
...
ForEach ( $GroupName in $GroupMembership ) {
$objUserInfo | Add-Member -MemberType NoteProperty -Name CtxGroup$counter -Value $GroupName.SamAccountName
$counter++
}
$objUserInfo | Add-Member -MemberType NoteProperty -Name NbCtxGroup -Value ($counter - 1)
$UserInfo += $objUserInfo
...
And then order descending the array on this propertie :
$UserInfo | Sort-Object -Property NbCtxGroup -Descending | Export-Csv .\F.csv
It's not so nice but it will do the trick.
Have a look at :
$objUserInfo.psobject.Properties | Measure-Object