PowerShell Compare AD Groups but show only these what I need - powershell

I have a PowerShell script that is generating from a file.txt a list of users and groups where they belong to.
The next step that I need to achieve is to confirm if the group from file Groups.txt is assigned to users.
To do this I used logical operator -contains so the code is looking like that:
$UserList = Get-Content ("C:\users.txt")
$GroupList = Get-Content ("C:\Groups.txt")
$result = #()
foreach ($UserList in $UserList){
$data = New-Object PSObject
$Group = (Get-ADPrincipalGroupMembership -Identity $UserList | foreach {$_.SamAccountName}) -contains $GroupList
$data = get-aduser $Userlist -properties samaccountname,givenname,surname | select samaccountname,givenname,surname, #{name="Groups";expression={$Group}}
$result += $data
}
$result
The code is working when I have only one group in file Groups.txt. If I have two or more it is applying only the last one with value True
The resolution what I expecting is
When the user has one or multiple groups from the file Group.txt script should mention that group name and nothing else.
To be more precise I need something like that as results:
samaccountname givenname surname Groups
-------------- --------- ------- ------
User FirstName Surname False (or anything)
User1 Firstname Surname Group1, Group2, Group3
Many thanks for any help in this matter.

You can do the following:
$UserList = Get-Content "C:\users.txt"
$GroupList = Get-Content "C:\Groups.txt"
$result = foreach ($User in $UserList) {
$Groups = Get-ADPrincipalGroupMembership -Identity $User |
Where SamAccountName -in $GroupList
Get-ADUser -Identity $User -Properties GivenName,Surname |
Select-Object SamAccountName,GivenName,Surname,#{name="Groups";expression={$Groups.SamAccountName}}
}
$result
When using a foreach loop, the proper syntax is foreach ($<item> in $<collection>) { statements }. $<item> is just a variable that you can reference within the statements, and it should be a variable that has not been assigned up until that point. See About_Foreach.
If your foreach statements produce output as in this case, they can simply be collected by assigning a variable to the foreach loop. This will result in a more efficient array assignment. Using += to effectively expand an array, just results in creating a new array on each loop iteration that is bigger than the previous. It is not efficient and is unnecessary in cases like these.
Regarding collection comparison, -contains is used when comparing a left-hand side (LHS) single item with a right-hand side (RHS) collection. A sample syntax would be $<collection> -contains $<single_item>. -in is used when comparing a LHS single item with a RHS collection. A sample syntax would be $<single_item> -in $<collection>. See About Comparison Operators.
Since you ultimately wanted to gather groups from a command output based on a certain condition, that is a prime candidate for Where or Where-Object. It's pseudo code usage is out of these 20 items, show me the ones that meet a certain condition. See Where-Object.

Related

Powershell - How to remove trailing spaces from a list of groups a user is in

I've copied/created a script to get all the members of a group that a user is part of, including nested groups. However, my output isn't quite the way I want it.
It goes in one of two ways. Either it outputs as one big string, which looks nice, but has trailing spaces on each line so I cannot simply copy and paste it into AD. Or if I change the Out-String to use -stream, it comes out as a garbled mess, but may allow me to trim the spaces.
I currently have the output going into a TextBox in a simple GUI.
Function Get-ADUserNestedGroups {
Param
(
[string]$DistinguishedName,
[array]$Groups = #()
)
#Get the AD object, and get group membership.
$ADObject = Get-ADObject -Filter "DistinguishedName -eq '$DistinguishedName'" -Properties memberOf, DistinguishedName;
#If object exists.
If($ADObject)
{
#Enummurate through each of the groups.
Foreach($GroupDistinguishedName in $ADObject.memberOf)
{
#Get member of groups from the enummerated group.
$CurrentGroup = Get-ADObject -Filter "DistinguishedName -eq '$GroupDistinguishedName'" -Properties memberOf, DistinguishedName;
#Check if the group is already in the array.
If(($Groups | Where-Object {$_.DistinguishedName -eq $GroupDistinguishedName}).Count -eq 0)
{
#Add group to array.
$Groups += $CurrentGroup;
#Get recursive groups.
$Groups = Get-ADUserNestedGroups -DistinguishedName $GroupDistinguishedName -Groups $Groups;
}
}
}
#Return groups.
Return $Groups;
}
Function Display-UserGroups {
#Get all groups.
$Groups = Get-ADUserNestedGroups -DistinguishedName (Get-ADUser -Identity $userSAM).DistinguishedName;
$ResultsTextBox.Text = $Groups | Select-Object Name| Sort-Object name | Out-String
The output with the first way looks like:
Group Name1(Eight Spaces Here)
Group Name2(Eight Spaces Here)
The output with the second way looks like:
Group Name1GroupName2GroupName3
Thanks for your help!
You need to trim your output, which can easily be done with String.Trim method. However, it can only be applied against strings. $Groups will be an array of ADObject types. You will need to return the Name values of those objects and apply the Trim() method to the values.
($Groups | Select -Expand Name | Sort).Trim() -join "`r`n"
You can use $Groups += $CurrentGroup.trimEnd() to add the value with the trailing spaces trimmed.
A few other methods you might want to research
.trim() # Trim leading and trailing
.trimEnd() # Trim trailing
.trimStart() # Trim leading
The padding is being caused by PowerShell's formatting system. Select-Object is returning single property (Name) objects. PowerShell outputs that as a table, which may have some padding. Out-String is keeping that padding which is why it was reflecting in your final output...
The name property of the group is already a string. There's no need to use Out-String if you unroll the Name property from your $Groups variable/collection.
With some other adjustments for readability & testing. Below will emit a single string with each group on a new line. That should be paste-able into AD, by which I think you meant Active Directory User & Computers.
Function Get-ADUserNestedGroups
{
Param
(
[string]$DistinguishedName,
[array]$Groups = #()
)
# Get the AD object, and get group membership.
$ADObject = Get-ADObject $DistinguishedName -Properties memberOf, DistinguishedName
# If object exists.
If( $ADObject )
{
# Enummurate through each of the groups.
Foreach( $GroupDistinguishedName in $ADObject.memberOf )
{
# Get member of groups from the enummerated group.
$CurrentGroup = Get-ADObject $GroupDistinguishedName -Properties memberOf, DistinguishedName
# Check if the group is already in the array.
If( $Groups.DistinguishedName -notcontains $GroupDistinguishedName )
{
# Add group to array.
$Groups += $CurrentGroup
# Get recursive groups.
$Groups = Get-ADUserNestedGroups -DistinguishedName $GroupDistinguishedName -Groups $Groups
}
}
}
# Return groups.
Return $Groups
}
$userSAM = 'UserName'
# Get all groups.
$Groups = Get-ADUserNestedGroups -DistinguishedName (Get-ADUser -Identity $userSAM).DistinguishedName
($Groups.Name | Sort-Object) -join [System.Environment]::NewLine
Among the secondary changes you'll see I removed the -Filter parameter in a few places. The DistinguishedName can be used as the -Identity parameter so no need to filter.
Also, where you were checking if the $Groups collection already had the current group I used the -notContains operator instead. That should be faster then repeatedly iterating the collection with |Where{...} The code is also shorter and more readable.
Note: if we reverse the operands we could use -notin which may look
nicer still
An aside; You should avoid appending arrays with +=. Doing so can causes performance problems because it creates a new array any copies the contents over. The best way to deal with this is to allow PowerShell to accumulate the results for you. If you must do the append directly, look into using Array Lists. There's a lot information on this, just google it.

Listing group membership combinations

Hoping i can get some help in my thinking here because im struggling to accomplish what im after and im doubting its even the best way to achieve this now!
Essentially i have a powershell script (see below) which successfully lists members of a certain filter i put in. This all works smoothly and outputs a list into a CSV.
The next part however is telling it to return only members who are members of specific group combo's. For example, a user might be a member of Group 1, Group 2 and Group 7 (lets say out of 9 groups with that naming scheme)
so im trying to return results of members who only match the statement that they are in TWO groups (Group 1 and Group 2) and exclude those who may only be in Group 1 but not Group 2....hope that makes sense.
# first im narowing down group search to all groups starting with Group and then a number (this is an example). This will return 9 groups. Group 1 through to Group 9
$Groups = (Get-AdGroup -filter * | Where-object { $_.name -like "Group *" } | select-object name -expandproperty name)
# Just standard Array
$Array = #()
$Data = [ordered]#{}
# so now im wanting to search for members in each of those groups we narrowed down to above
Foreach ($Group in $Groups) {
# This bit defines my search criteria. It works perfectly if i just return all users. But if i only want to display members that are in Group 1 AND Group 2....it does not return any results.
$Members = Get-ADGroupMember -identity $Group | Where-Object { ($Group.name -like "Group 1") -and ($Group.name -like "Group 2") } | Get-ADUser -Properties * | select-object givenName, sn, sAMAccountName, mail
# Eventually that will be displayed in the object below...this bit works fine
foreach ($Member in $Members) {
$Data."update" = "modify"
$Data."region" = $Group
$Data."login" = $Member.mail
$Data."first_name" = $Member.givenName
$Data."last_name" = $Member.sn
$Data."approver_level" = "BlankForNow"
#
$DataPSObject = New-Object PSObject -property $Data
#
$Array += $DataPSObject
}
}
#
$Array | Sort-Object -Property login | Export-Csv "D:\Temp\Groups.csv" -NoTypeInformation
Any ideas where i could be going wrong? Maybe its better to edit the outputted CSV and match statements that way. So remove lines from CSV where users are not a member of both? Not even sure if thats entirely possible with Import-CSV tbh
Thanks in advance!
If you want the common members of the groups "Operations" and "ServiceDeskLevel2"
# Get groups members
$membersGroup1 = Get-ADGroupMember "Operations"
$membersGroup2 = Get-ADGroupMember "ServiceDeskLevel2"
# Compares both groups and put common members in the $res list
$res = Compare-Object $membersGroup1 $membersGroup2 -PassThru -IncludeEqual -ExcludeDifferent
# Output the name of the common members from $res
$res | Format-List -Property name

Foreach in foreach (nested)

I'm trying to loop all disabled users through an array of groups to check if the users have membership in any of the listed groups. My thought is that for every user in the list loop them through and check if they are present in one of the listed groups. That would require nesting foreach loops, right? The output I get is like this:
...
user1
user2
user3
is not a member of group1
Here is the source code:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
$groups = 'Group1', 'Group2'
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive |
Select -ExpandProperty SamAccountName
if ($members -contains $dUsers) {
Write-Host "[+] $dUser is a member of $group"
} else {
Write-Host "[-] $dUser is not a member of $group"
}
}
}
I'm pulling my hair because I feel like there is a simple solution, but I'm lost.
Update:
I wanted to put all disabled users in variable $dUsers.
It actually works if I manually put users in the variable like this:
$dUsers = 'user1','user2','user3'
Which gives me the following output:
user1 is not a member of group1
user1 is not a member of group2
user2 is not a member of group1
user2 is not a member of group2
...
This makes me question how it gets "foreached" when the variable is:
$dUsers = Get-ADUser -Filter {enabled -eq $false} |
FT samAccountName |
Out-String
Anyone got a clarification on that?
Update:
This is the final code. It takes a long time to run, even with only two groups.
$dUsers = Get-ADUser -Filter {enabled -eq $false} | Select-Object -Expand SamAccountName
$groups = 'Group1', 'Group2'
Write-host '[+] Checking if any disabled user is member of any SSL groups'
Write-host '[+] This might take a while. Get a coffee!'
write-host '[+] Running...'`n
foreach ($dUser in $dUsers) {
foreach ($group in $groups) {
$members = Get-ADGroupMember -Identity $group -Recursive | Select -ExpandProperty SamAccountName
if($members -contains $dUser) {
Write-Host "$dUser is a member of $group"
} Else {
# Remove or comment out the line below to get a clutterfree list.
# Write-Host "$dUser is not a member of $group"
}
}
}
You have two issues in your code:
You're creating a single string from the Get-ADUser output. Piping the output of that cmdlet through Format-Table (alias ft) and then Out-String creates one string with a tabular display of all matching account names including the table header.
If you output $dUsers in a way that makes beginning and end of a string visible you'd see something like this (the leading and trailing == marking the beginning and end):
PS> $dUsers | ForEach-Object { "==$_==" }
==samAccountName
--------------
user1
user2
user3==
Since there is no account with a username matching this string no match can be found in any group and you're getting the output you observed.
This misuse of Format-* cmdlets is a common beginner's mistake. People get a nicely formatted string output and then try to work with that. ONLY use Format-* cmdlets when you're presenting data directly to a user, NEVER when further processing of the data is required or intended.
What you actually want is not a string with a tabular display of usernames, but an array of username strings. You get that by expanding the SamAccountName property of the user objects you get from Get-ADUser.
$dUsers = Get-ADUser ... | Select-Object -Expand SamAccountName
The second issue is probably just a typo. Your condition $members -contains $dUsers won't work, since both $members and $dUsers are arrays (after fixing the first issue, that is). The -contains operator expects an array as the first operand and a single value as the second operand.
Change
$members -contains $dUsers
to
$members -contains $dUser
Depending on what PowerShell version you are on, there is a cmdlet for this use case and others.
As for
I'm Trying to loop all disabled users
Just do...
Search-ADAccount -AccountDisabled |
Select-Object -Property Name, Enabled,
#{Name = 'GroupName';Expression = {$_.DistinguishedName.Split(',')[1] -replace 'CN='}}
# Results
Name Enabled GroupName
---- ------- ---------
...
testuser2 NewTest False Users
Guest False Users
Or different cmdlet…
# Get disabled users and their group membership, display user and group name
ForEach ($TargetUser in (Get-ADUser -Filter {Enabled -eq $false}))
{
"`n" + "-"*12 + " Showing group membership for " + $TargetUser.SamAccountName
Get-ADPrincipalGroupMembership -Identity $TargetUser.SamAccountName | Select Name
}
# Results
...
------------ Showing group membership for testuser1
Domain Users
Users
------------ Showing group membership for testuser2
Domain Users
As for ...
an array of Groups
Just select or filter the DN for the group name you want using the normal comparison operators.
As for...
Unfortunately I'm not well versed in powershell.
… be sure to spend the necessary time to get ramped up on it, to limit the amount of misconceptions, confusions, errors, etc. that you are going to encounter. There are plenty of no cost / free video and text-based training / presentations all over the web.
Example:
Videos
Use tools that will write the code for you that you can later tweak as needed.
Step-By-Step: Utilizing PowerShell History Viewer in Windows Server 2012 R2
Learning PowerShell with Active Directory Administrative Center (PowerShell History Viewer)
As well as plenty of sample scripts and modules via the MS PowerShell Script / Module Gallery.
There are two commands for the AD Groups.
First I see that you want the membership of the disabled users that is easy.
#Get the dissabled users from your AD with all their attributes (properties and select)
$dUsers = Get-ADUser -Filter {Enabled -eq $false} -Properties * | Select *
#Run a loop for each user to get the group membership
Foreach ($User in $dUsers) {
$User = $User.SamAccountName
Get-ADUser $User -Properties * | Select Name, SamAccountName, MemberOf | Format-Table -Wrap # > "D:\test\$user.txt" -HideTableHeaders
}
This one can work but I don't like the output that we get.
I prefer to run the groupmembership command and check the users.
$GroupMembers = Get-ADGroupMember "groupname"| Select Name, SamAccountName
ForEach ($User in $GroupMembers)
{
$UserProperties = Get-ADUser $User.SamAccountName -Properties * | select *
If ($UserProperties.Enabled -eq $False) {
Write-Host $UserProperties.SamAccountName
}
}
Edit:
Let me know if those fits you.
Kind regards.
The first thing you should try to check is whenever you are only interested in direct memberships or indirect ones as well. Depending on the answer the options you got availabel change a bit. You probably will encounter Distinguished Names while working on this so check out what they are if you don't know (mostly a path for an object).
If it's only direct memberships using memberOf with Get-ADUser should be sufficient. The memberOf attribute contains every direct group membership of the user with the full Distinguished Name of the group.
Get-ADUser test -Properties MemberOf | Select-Object -ExpandProperty memberOf
You can match the groups you're looking for in various ways. You could get the whole Distinguished Name of those groups or you could do a partial match. It's up to you to decide how to proceed.
If you need the indirect memberships as well you might want to split up your code to make it easier for yourself. For instance you could first find the users and save them. Afterwards find all group members of those groups (You already got that with Get-ADGroupMember) and finally compare the two.
Currently for every user you build the whole list of group members again. This approach would save a few resources as you wouldn't be doing the same queries over and over again.
Finally you could also use the MemberOf approach but get the list of every direct and indirect membership of a user using an LDAP query.
$dn = (Get-ADUser example).DistinguishedName
$userGroups = Get-ADGroup -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn)
This approach uses a LDAP search query. It can be quite complex, you could also only check for one one of the groups by modifying it a bit.
In the end even your current approach should work. The problem is that you're comparing the AD object against the list of SAM Accountnames. You would need to check for the SAM Accountnames as well.
if($members -contains $dUsers.SamAccountName)
if($members -contains $dUsers | Select-Object -ExpandProperty SamAccountName)
One of these should work if you change your $dUsers as well. As it currently is you end up with a giant string. You probably can check that by checking $dUsers.length. Just drop the Format-Table and Out-String.

Multiple rows in a grid [duplicate]

This question already has answers here:
Export hashtable to CSV with the key as the column heading
(2 answers)
Closed 4 years ago.
I'm trying to list all ad group memberships of specific users. The input would be a string of logins split with a comma 'login1,login2'.
So I go over each user and list their memberships with the username as title. Somehow it only shows the first entry. Also it shows the user groups in one row and I don't know how to change that.
Code below:
$users = $logon -split ','
$q = #()
foreach ($user in $users) {
$usernm = Get-ADUser -Filter 'samAccountName -like $user' | select Name
$useraccess = Get-ADPrincipalGroupMembership $user | Select-Object Name
$userobj = New-Object PSObject
$userobj | Add-Member Noteproperty $usernm.Name $useraccess.Name
$q += $userobj
}
Expected output would be something like:
fullnameuser1 fullnameuser2 list of users goes on...
------------- ------------- ------------------------
adgroup1 adgroup3 ...
adgroup2 adgroup4
... ...
In principle this would also mean that if i typed $q.'fullnameuser1' output would be:
fullnameuser1
-------------
adgroup1
adgroup2
...
Whenever the code is ran, it will only ever add the first user's access, also returning all groups on one row. So somehow I need to go over all the group memberships and add a row for each one.
First and foremost, PowerShell does not expand variables in single-quoted strings. Because of that Get-ADUser will never find a match unless you have a user with the literal account name $user. Also, using the -like operator without wildcards produces the same results as the -eq operator. If you're looking for an exact match use the latter. You probably also need to add nested quotes.
Get-ADUser -Filter "samAccountName -eq '${user}'"
Correction: Get-ADUser seems to resolve variables in filter strings by itself. I verified and the statement
Get-ADUser -Filter 'samAccountName -eq $user'
does indeed return the user object for $user despite the string being in single quotes.
If you want a fuzzy match it's better to use ambiguous name resolution.
Get-ADUser -LDAPFilter "(anr=${user})"
You may also want to avoid appending to an array in a loop, and adding members to custom objects after creation. Both are slow operations. Collect the loop output in a variable, and specify the object properties directly upon object creation.
$q = foreach ($user in $users) {
...
New-Object -Type PSObject -Property {
$usernm.Name = $useraccess.Name
}
}
Lastly, I'd consider using the user's name as the property name bad design. That would be okay if you were building a hashtable (which is mapping unique keys to values), but for custom objects the property names should be identical for all objects of the same variety.
New-Object -Type PSObject -Property {
Name = $usernm.Name
Group = $useraccess.Name
}
Basily query all the users and store it in $users, example:
Get-ADUser -Filter * -SearchBase "dc=domain,dc=local"
And then you can export the results as csv or a table.
To Export as CSV :
Get-ADPrincipalGroupMembership <Username> | select name, groupcategory, groupscope | export-CSV C:\data\ADUserGroups.csv`
To Format the result as Table in the console itslef :
Get-ADPrincipalGroupMembership <Username> | select name, groupcategory, groupscope | Format-Table

How to loop through Active Directory group members in Powershell

I am trying to make a list of all groups that contain members from a specific OU. To do this, I am reading a file with the list of all group names and using get-ADGroupMember $groupName to find the members of each group. I am converting the value of each member to string and comparing to $member.indexOf("specific OU"). The problem is, I haven't figured out how to get $member[$i].indexOf("specific OU"). My code is below.
EDIT: If I use a for-each loop I can loop through the members properly, but I cannot use the break, which prevents duplicates.
Import-Module ActiveDirectory
#declaring end result group array
$results = #()
#sets path for files
$pathName = $MyInvocation.MyCommand.Path
$pathLen = $pathName.LastIndexOf("\")
$pathStr = $PathName.Substring(0,$pathLen +1)
$APgroupFile = $pathStr + "APGroups.csv"
$groupFile = $pathStr + "GGroups.csv"
#gets the list of group names and loops through them
get-content $groupFile | foreach-object{
#sets all of the group names
#start of if1
if($_.IndexOf("CN=") -ge 0){
$nameStart = $_.IndexOf("CN=")+3
$nameEnd = $_.indexOf(",")-$nameStart
$name = $_.substring($nameStart, $nameEnd)
#issue starts here
#goal is to find member of "specific OU".
#If found, add group to $results. If not, go to next group
$members = get-ADGroupMember -Identity $name
if($members.length -gt 0){
$i=0
for($i=0; $i -le ($members.length -1); $i++){
#need to check users OU for specific OU. If a user is member, save group to .txt. If none are members, move to next group
$OU = $members.toString()
if($OU.indexOf("OU=specific OU") -ge 0){#start of if OU
$results += New-Object psObject -Property #{'GroupName'=$name; 'Member'=$OU}
break
}#end if OU
}#end for mem.length
}#end if mem.length
}#end if1
}#end foreach
$results | Export-Csv $APgroupFile -NoTypeInformation
Try this:
Get-ADGroupMember -Identity $name |
Where-Object {$_.distinguishedName -like '*OU=specific OU*'}
A consideration that you did not explicitly mention: what about nested OUs? I.e., are you only considering a member immediately within your target OU? Or would a member of an OU two or three levels deep also logically, for your purposes, be included? E.g., if "California" is your target OU, do you want only objects directly under California? Or do you want to include all the members of San Francisco and all the members of San Diego (which are sub-OUs of California) in the result set? Either way:
Get-ADUser includes search and filter capability - http://technet.microsoft.com/en-us/library/ee617241
Get-ADuser -Filter {sAMAccountName -eq "jdoe"} -SearchBase "OU=IS,DC=foodomain,DC=com" -SearchScope SubTree
Get-ADuser -Filter {sAMAccountName -eq "jdoe"} -SearchBase "OU=IS,DC=foodomain,DC=com" -SearchScope OneLevel
So the approach here is to search your target OU for your target user, a separate search for each user. You will get that user in the returned result-set, or not. That can be the condition you test for. Obviously you will want to replace "jdoe" with the appropriate variable (I think $members[i].SamAccountName will do the trick there).
Just saw Shay Levy's answer. Much more concise, good job. Without explicit searchbase, you might end up visiting containers with the same name that are buried under other parent OUs, but the result set should be the same. E.g., if every city contains a standard "Office Staff" OU, each of them will be searched with the "OU=Office Staff" expression. You might want to try both approaches and test for performance differences. That being equal, Shay Levy's reads more pleasantly.