best way to compare different objects in powershell - powershell

Not sure if this is even possible, but I'm trying to compare properties of an ADgroup object and a PSCustomObject object. We're in the middle of a user audit which requires validating a list of active employees against our active AD user accounts along with their AD group memberships. Here's a basic breakdown of what I have so far:
(we're defining two separate search paths because we have groups in different OUs)
$mainGroups = get-adgroup -filter * -searchbase 'OU_here'
$subGroups = get-adgroup filter * -searchbase 'Different_OU_here'
List of usernames from HR system
$sourceUsers = get-content -path 'c:\temp\users.txt'
List of usernames from AD
$ADUserName = get-aduser -filter * -searchbase 'User_OU' -searchscope subtree | select -expandproperty SamAccountName
Empty array to store custom object/properties
$userObjEQ = #()
Compare HR to AD
$compareResults = compare-object -referenceobject $sourceUsers -differenceObject $ADUserName
Find group memberships of all matching users, create custom object, etc
foreach ($result in $compareResults) {
if ($result.SideIndicator -eq '==') {
$groupMem = get-adprincipalgroupmembership -identity $result.InputObject
}
$userObjEQ += [pscustomobject] #{
'UserName' = $result.InputObject
'Groups' = $groupMem.Name
}
}
From this point on, I want to compare every group from each matching user to the group name from the $mainGroups to see if there's a match. If there isn't then compare it to the $subGroups group names. If there's a match do nothing, if there's a mismatch, output the username along with any mismatched group names. Just not sure how best to compare these objects. Any hints will be appreciated.

If your groups are arrays, then use the -contains operator -- if they're not, make them arrays:
foreach ($u in $users) {
foreach ($groupdn in $u.memberof) {
if ($mainGroups -contains $ug -or $subGroups -contains $ug) {
## do something when the users' group exists in the checked sub-groups
}
}
}
...this assumes the $maingroups array is an array of group DNs...

Related

Why does -contains not seem to work with AD security permissions PSCustomObjects

I am trying to make a list of job titles and their security permissions. I would love to have two lists, a list of security permissions that are shared with more than one member with that title, and permissions that are outliers so that we can better make templates for new hires with certain job roles. I am using -contains to match security permissions with recurring security permissions but it always returns false. I am guessing that each security permission has some unique value to it per user, but even when I try to just match something like the name, it doesn't work
Here is my code.
#get all the users with a title and group them by title
$Titles = Get-ADUser -Filter * -Properties Title | Where-Object Title | Sort-Object Title | Group-Object Title
#loop through each group
foreach ($Title in $Titles){
#zero out outliers and shared permissions
$OutlierPermissions = #()
$SharedPermissions = #()
#loop through each user in each group
foreach ($User in $Title.Group){
$Permissions = #()
#get all the permissions of the user
$Permissions = Get-ADPrincipalGroupMembership $User | Sort-Object Name
#loop through each permission
foreach ($Permission in $Permissions){
#if this permission is shared by more than one user
if($OutlierPermissions -contains $Permission){
#and not already added
if($SharedPermissions -notcontains $Permission){
$SharedPermissions += $Permission
}
#else add to list of outliers
}else{$OutlierPermissions += $Permission}
}
}
}
I have also tried
-contains $Permission.Name
$Permissions = Get-ADPrincipalGroupMembership $User | Sort-Object Name | Select Name
anything to get some part of redundancy to the security permission
is there a way around this?
To put it simple $OutlierPermissions -contains $Permission will never work because $Permission is an instance of ADGroup and $OutlierPermissions is a collection of ADGroup instances and these objects are not comparable or equatable so -contains will always return $false. Instead what you want to do if pick one property of these objects that has such capabilities to have a proper comparison, in example, you can use the .ObjectGUID property.
If I'm understanding your code correctly, it could be simplified to this logic:
$map = #{}
# get all the users with the `Title` populated
foreach($user in Get-ADUser -LDAPFilter "(title=*)" -Properties Title) {
# if this `Title` has not yet been added to the hash
if(-not $map.ContainsKey($user.Title)) {
# use a `List<T>` for dynamic additions of new groups
# and a `HasShet<T>` to not duplicate additions
$map[$user.Title] = #{
ProcessedMembership = [System.Collections.Generic.HashSet[guid]]::new()
Membership = [System.Collections.Generic.List[object]]::new()
}
}
# for each group this user is a member of
foreach($group in Get-ADPrincipalGroupMembership $user) {
# if this group has not yet already been added
if(-not $map[$user.Title]['ProcessedMembership'].Add($group.ObjectGUID)) {
# add this group to the `Membership` List for this `Title`
$map[$user.Title]['Membership'].Add($group)
}
}
}
Using a Hashtable to gather all results where the Keys are the unique Titles and the Values is a nested Hashtable consisting of groups already processed ProcessedMembership (this keeps track of duplicates using a HashSet<T>) and a List<T> where you can keep a reference of all groups objects associated with each Title.

Return list of ad accounts and check if member of group

I've written the below, and it works (based on Check if the user is a member of a list of AD groups), however it takes an incredibly long time to run - I'm assuming this is because it retrieves the full group for every user. I've tried moving the $members... line out of the function at the start to retrieve the group list once, but doesn't seem to make any difference.
Is there a more efficient way of returning this info?
samaccountname enabled InDenyGroup
-------------- ------- -----------
admin-abc True yes
admin-def True yes
In this example, the account name filter is "king", as the check is whether an account is in a group or not.
Get-ADUser -Filter "(SamAccountName -like 'admin*') -and (enabled -eq 'true')" |
ft -AutoSize samaccountname,enabled,#{Name='InBlockGroup'; Expression={InDenyGrp($_.samaccountname)}}
Function InDenyGrp([string]$UserID) {
$members = Get-ADGroupMember -Identity "myBlockGroup" | Select -ExpandProperty SamAccountName
If ($members -contains $UserID) {
Return "yes"
} Else {
Return "not in group"
}
}
Thanks.
You query all ADGroup members (not only the DistinguishedNames) of the same ADGroup on each iteration in your Foreach-Object loop again and again (That's the bottleneck).
Either you just query the "blockGroup"'s members (see your posted link) and loop over the members and check whether your users are part of them (there are some properties to compare it with) or you try the code below:
Building a lookup table should increase the performance.
Furthermore, we don't need more information about group members than the DistinguishedNames, therefore Get-ADGroupMember is overkill.
You can extend the LookupTable with members of different groups.
# query blocking group with it's members first (only DistinguishedNames)
$adGroup = Get-ADGroup -Identity '<myBlockGroup>' -Properties 'Members'
# build lookup table of members' DistinguishedNames
$adGroupMemberLookupTable = [System.Collections.Generic.HashSet[string]]::new()
foreach ($member in $adGroup.Members) {
[void]$adGroupMemberLookupTable.Add($member)
}
Get-ADUser -Filter "(SamAccountName -like 'admin*') -and (enabled -eq 'true')" |
Format-Table -AutoSize samaccountname, enabled,
#{Name ='InBlockGroup';
Expression = {
# lookup if user is member of a "blocking" group
$adGroupMemberLookupTable.Contains($_.DistinguishedName)
}
}

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.