Find user and AD group relation via nested AD groups - powershell

Basically, in our environment, we have a ton of security groups. Security groups that are nested within other groups etc. So it is a real PITA to find out why a setting is applying to a user, because of one of the nested groups they may or may not be a part of
For e.g. If you add a user to group X, they suddenly have a published application in Citrix. Citrix is configured for security group Y. Attempting to find the link between X and Y is very time consuming but can be automated.
I want to create a script where, you enter a user and the end security group (group Y from above), and the script outputs the intermediary groups that connects the user to the final group. If this makes sense?
Something like this:
function get-grouprelationship($username, $knownsecuritygroup)
{
$getallgroups = get-adgroupmember $knownsecuritygroup | where-object {$_.ObjectClass -eq "Group" | select-object SamAccountName | foreach-object {get-adgroupmember $_.SamAccountName}
}
(The above variable takes your group, and loops through all members of that group, printing their members)
$usergroups = (get-aduser -identity $username -Properties memberof | select-object memberof).memberof
(The above gets all groups that a user is in)
$usergroups1 = $usergroups.split(",")
$usergroups2 = $usergroups1[0]
$usergroups3 = $usergroups2.substring(3)
(the above formats the text nicely)
if ($usergroups3 -contains $groupname){write-host "$username is directly in $groupname}
From here, I am quite stuck as I basically need to nest multiple for loops, depending on how many groups are in each group. Then do a condition check that
if ($groupname -eq $currentgroup){write-host "connected by $groupname and $currentgroup}
I'm also stuck with the $getallgroups variable, because it only checks 1 level down. It would then need another foreach loop inside that, which would need another one inside that etc.
Having no prior coding experience, I am really struggling to get my head around an easy way to achieve my goal.
EDIT:
I found this script here - script. Below basically works, except it is way to verbose:
import-module activedirectory
$username = read-host "What's their username?"
Function RecurseUsersInGroup {
Param ([string]$object = "", [int]$level = 0)
$indent = "-" * $level
$x = Get-ADObject -Identity $object -Properties SamAccountName
if ($x.ObjectClass -eq "group") {
Write-Host "# $($x.SamAccountName)"
$y = Get-ADGroup -Identity $object -Properties Members
$y.Members | %{
$o = Get-ADObject -Identity $_ -Properties SamAccountName
if ($o.ObjectClass -eq "user" -and $o.SamAccountName -eq $username) {
Write-Host "-> $($o.SamAccountName)"
} elseif ($o.ObjectClass -eq "group") {
RecurseUsersInGroup $o.DistinguishedName ($level + 1)
}
}
} else {
Write-Host "$($object) is not a group, it is a $($x.ObjectClass)"
}
}
$thegroup = read-host "What's the Group?"
RecurseUsersInGroup (get-adgroup $thegroup).DistinguishedName
That works fine, but appears to output every security group, oppose to the connecting ones. Certainly a step in the right direction though! If I find the source I will post the credit as well.

The following version is not less verbose (could probably be written a lot more terse, but I'm hoping the script is at least semi-readable), but it does a search for the group and returns the Active Directory group objects for each group along the branch in which the group was found.
function Get-GroupConnection
{
[CmdletBinding()]
PARAM (
$Username,
$GroupName
)
$User = Get-AdUser -Identity $Username -Properties MemberOf
if (-Not ($User))
{
return;
}
$SearchedGroups = #()
function Find-GroupBranches
{
[CmdletBinding()]
PARAM (
$GroupNameList,
$SearchForGroupName
)
$ADGroups = $GroupNameList | Foreach { Get-ADGroup -Identity $_ -Properties MemberOf }
foreach($group in $ADGroups)
{
Write-Verbose "Testing if either '$($Group.SamAccountName)' or '$($Group.DistinguishedName)' are equal to '$SearchForGroupName'"
if ($Group.SamAccountName -eq $SearchForGroupName -OR $Group.DistinguishedName -eq $SearchForGroupName)
{
Write-Verbose "Found $($Group.DistinguishedName)"
Write-Output $Group
return
}
}
Write-Verbose "No match in current collection, checking children"
foreach ($currentGroup in $ADGroups)
{
if ($SearchedGroups -Contains $currentGroup.DistinguishedName)
{
Write-Verbose "Already checked children of '$($currentGroup.DistinguishedName)', ignoring it to avoid endless loops"
continue
}
$SearchedGroups += $currentGroup.DistinguishedName
if ($currentGroup.MemberOf)
{
Write-Verbose "Checking groups which $($currentGroup.DistinguishedName) is member of"
$foundGroupInTree = Find-GroupBranches -GroupNameList $currentGroup.MemberOf -SearchForGroupName $SearchForGroupName
if ($foundGroupInTree)
{
Write-Output $currentGroup
Write-Output $foundGroupInTree
break
}
}
else
{
Write-Verbose "$($currentGroup.DistinguishedName) is not member of any group, branch ignored"
}
}
}
Write-Verbose "Searching immediate group membership"
Find-GroupBranches -GroupNameList $User.MemberOf -SearchForGroupName $GroupName
}
Get-GroupConnection -Username MyUser -GroupName SubSubGroup -Verbose
A description of how it searches follows.
Given the following Active Directory structure:
MyUser
Domain Admins
AnotherSubGroup
Other Group
DirectMemberGroup
Domain Admins (same group as MyUser is direct member of, above)
AnotherSubGroup (which is of course the same as above too)
SubGroup
SubSubGroup
Some Other Group
If we search for the connection between MyUser and 'SubSubGroup' the script would search first the direct membership of the MyUser user, i.e. 'Domain Admins', 'Other Group', 'DirectMemberGroup' and 'Some Other Group'. None of these match the 'SubSubGroup' we search for, so it starts checking 'child'groups.
'Domain Admins' is member of 'AnotherSubGroup', but that does not match 'SubSubGroup'. 'AnotherSubGroup' is not member of any group, so that branch is ignored.
'Other Group' is not member of any group, so that branch are ignored.
'DirectMemberGroup' is member of other groups, so it iterates through those groups. It has already checked 'Domain Admins' for children, so that group is skipped to avoid getting stuck in a circular search. Therefore it check 'SubGroup'.
'SubGroup' does not match 'SubSubGroup' so it check the groups which 'SubGroup' is member of. 'SubGroup' is member of 'SubSubGroup', so it checks that group.
'SubSubGroup' does match 'SubSubGroup' and will therefore be chosen as a match.
In the above example, the output group object will be branches which lead to the 'SubSubGroup' group, in the following order:
DirectMemberGroup
SubGroup
SubSubGroup
Observe that this method will return the first connection it finds between the user and the group. If, for example, the 'Some Other Group' group would also be a member of 'SubSubGroup' this would not change the output, nor the search process, mentioned above.

Related

How come my condition determining if a user is in a group always returns as false?

I'm writing a script that requires detecting if the executing user account is a domain admin. I do this by getting the current user and checking if they are in the Domain Admins security group.
#Get current user
$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name | Out-String
$CurrentUser = $CurrentUser -replace 'DOMAIN\\'
#Get list of Domain Admins members
$DomainAdmins = Get-ADGroupMember -Identity "Domain Admins" -Recursive | Select -ExpandProperty SamAccountName | Out-String
#Relevant condition
If ($DomainAdmins -like ($CurrentUser)) {
Write-Output "You're a domain admin." #example
}
Else {
Write-Output "You're not a domain admin."
}
Without fail, this script always runs the Else code when run from our domain controller using a domain administrator account.
I have also tried using -contains and .contains() with the exact same results. I've verified that the $CurrentUser value represents the current user accurately and that $DomainAdmins lists out the expected list of users.
I can also do this:
if ($DomainAdmins -contains ("USERNAME")) {Write-host "true"}
Where USERNAME is the current user typed out directly (case-correct or not) and it correctly returns true when that user is a member of the group.
Try with this:
$userSID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$DomainAdmins = Get-ADGroupMember -Identity "Domain Admins" -Recursive
if($DomainAdmins.SID.Contains($userSID))
{
Write-Output "You're a domain admin."
}
...
# OR
if($userSID -in $DomainAdmins.SID)
{
Write-Output "You're a domain admin."
}
...
# OR
if($DomainAdmins.SID -contains $userSID)
{
Write-Output "You're a domain admin."
}
The Out-String on Get-ADGroupMember is converting your array into a string which is why you can't use it as comparison:
PS /> #(
'one'
'two'
'three'
) -contains 'one'
True
PS /> (#(
'one'
'two'
'three'
) | Out-String) -contains 'one'
False
An alternative, instead of using Get-ADGroupMember:
$userSID = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value
$domainAdminsDN = (Get-ADGroup -Identity "Domain Admins").DistinguishedName
$recursiveMembers = Get-ADUser -LDAPFilter "(memberOf:1.2.840.113556.1.4.1941:=$domainAdminsDN)"
if($recursiveMembers.SID.Contains($userSID))
{
Write-Output "You're a domain admin."
}
...
...
My preferred way to do this is by checking the MemberOf property of the user. It works a little better to keep them as AD objects when you have multiple domains or other oddities:
# check if DA
$DAdn = (Get-ADGroup 'Domain Admins').distinguishedname
If ( Get-ADUser -LDAPFilter "(&(SamAccountName=$Env:USERNAME)(MemberOf:1.2.840.113556.1.4.1941:=$DAdn))" ) {
Write-Output "You're a domain admin." #example
}
Your $DomainAdmins variable is a single string with the members instead of a list object. Using ... | Select -ExpandProperty SamAccountName is enough to get your list without Out-String.
-contains Looks for exact matches in collections, not for substrings.
You have to either remove Out-String from $DomainAdmins, to make it a list and make sure that the name matches entirely, or use .Contains(...) to look for a substring inside the $DomainAdmins string

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 Script to query Active Directory

I am trying to query all users in multiple OUs of the same name. Get the SamAccountName attribute and then check for a file at a specific location with that name.
Here is what I have so far:
$ous = Get-ADOrganizationalUnit -Filter "Name -eq 'Admin-User-Accounts'"
$ous | ForEach-Object {
$AccountName = Get-ADUser -Filter * -SearchBase $_.DistinguishedName |
Select SamAccountName
Test-Path "\\domain.net\SYSVOL\domain.net\IA\$AccountName.pdf"
}
If a file is not found. I want to add the user to a group, however here is the kicker. The account has to be added to the non-compliance group for the organization that the account belongs to.
I.E an admin account found under:
OU=Admin-User-Accounts,OU=Administration,OU=ORG1,OU=ORGS,DC=domain,DC=net
would be added to the group named 'ORG1 IA - Non-Compliant Users' located under:
OU=Groups,OU=ORG1,OU=Information Assurance,OU=ORGS,DC=domain,DC=net
Well your post is a bit confusing, and no way to really validate because I have nothing setup like this.
Yet, querying for users in all OU or the enterprise is a common everyday thing.
However, an OU name, just like any other AD object name, must be unique. So, querying for the same OU name is not a thing, in a single AD forest / domain. If you meant querying every OU for the same username, then alrighty then.
By stepping thru how you are explanation for your use case, that you have laid out.
(though maybe you want to edit your post to make it's more clear, well to me anyway...)
Using pseudo code, then trying to map that out... and with no real way to determine what you mean by several things in your post/sample. So, the below is a rough first example of how I'd do approach this... again this is untested, so, I leave that homework to you.
# query all users in multiple OUs
(Get-ADOrganizationalUnit -Filter *).DistinguishedName |
ForEach{
# Collect all members of the current OU
$AccountNames = Get-ADUser -SearchBase $PSItem -Filter *
# Process each member in the current OU collection
ForEach($AccountName in $AccountNames)
{
"Processing $($AccountName.SamAccoutnName)`n"
# Initialize properties needed for processing
$UserOrg = $AccountName.DistinguishedName.split(",")[1]
$MemberCheckOU = "OU=Admin-User-Accounts,OU=Administration,OU=ORG1,OU=$UserOrg,DC=domain,DC=net"
$NonCompliantOU = "OU=Groups,OU=ORG1,OU=Information Assurance,OU=$UserOrg,DC=domain,DC=net"
# Validate user file existence for the current user
If(-Not (Test-Path -LiteralPath "\\domain.net\SYSVOL\domain.net\IA\$($AccountName.SamAccoutnName).pdf)"))
{
# if no file Process the user groupmebership modification
"Processing $($AccountName.SamAccoutnName)"
# Notify that the file was not found and processing is required
Write-Warning -Message "$($($AccountName.SamAccoutnName).pdf) not found. Process group modify actions"
# If the current user is in the MemberCheckOU, add to the NonCompliantOU
If(Get-ADPrincipalGroupMembership -Identity $($AccountName.SamAccoutnName) | Where-Object -Property DistinguishedName -Match $MemberCheckOU )
{ Add-ADGroupMember -Identity $NonCompliantOU -Members $($AccountName.SamAccoutnName) }
Else
{
# Do something else
}
}
Else
{
# Notify that the file was found and no processing required
Write-Host "$($AccountName.pdf) found. No further actions taken" -ForegroundColor Green }
}
}
It seems that one of the variables is incorrect because PowerShell is giving me the following:
Get-ADPrincipalGroupMembership : Cannot validate argument on parameter 'Identity'. The argument is null or empty. Provide an argument that is not null or empty, and then try the command
again.
Okay, so here is what I have so far based on your post above Postanote:
# query all users in multiple OUs
(Get-ADOrganizationalUnit -Filter "Name -eq 'Admin-User-Accounts'") |
ForEach{
# Collect all members of the current OU
$AccountNames = Get-ADUser -SearchBase $PSItem -Filter *
# Process each member in the current OU collection
ForEach($AccountName in $AccountNames)
{
"Processing $($AccountName.SamAccoutnName)`n"
# Initialize properties needed for processing
$UserOrg = $AccountName.DistinguishedName.split(",")[1]
$MemberCheckOU = "OU=Admin-User-Accounts,OU=Administration,OU=$UserOrg,OU=ORGS,DC=domain,DC=net"
$NonCompliantOU = "OU=Groups,OU=$UserOrg,OU=Information Assurance,OU=ORGS,DC=domain,DC=net"
# Validate user file existence for the current user
If(-Not (Test-Path -LiteralPath "\\domain.net\SYSVOL\domain.net\IA\$($AccountName.SamAccoutnName).pdf)"))
{
# if no file Process the user groupmebership modification
"Processing $($AccountName.SamAccoutnName)"
# Notify that the file was not found and processing is required
Write-Warning -Message "$($($AccountName.SamAccoutnName).pdf) not found. Process group modify actions"
# If the current user is in the MemberCheckOU, add to the NonCompliantOU
If(Get-ADPrincipalGroupMembership -Identity $($AccountName.SamAccoutnName) | Where-Object -Property DistinguishedName -Match $MemberCheckOU )
{ Add-ADGroupMember -Identity "$UserOrg IA - Non-Compliant Users" -Members $($AccountName.SamAccoutnName) }
Else
{
# Do something else
}
}
Else
{
# Notify that the file was found and no processing required
Write-Host "$($AccountName.pdf) found. No further actions taken" -ForegroundColor Green }
}
}
Looking at the original script fragment:
$ous = Get-ADOrganizationalUnit -Filter "Name -eq 'Admin-User-Accounts'"
$ous | ForEach-Object {
$AccountName = Get-ADUser -Filter * -SearchBase $_.DistinguishedName |
Select SamAccountName # note 1
Test-Path "\\domain.net\SYSVOL\domain.net\IA\$AccountName.pdf" # note 2
}
Note 1: Your going to end up with $accountname.accountname holding your value. I think your going to want to expand this instead.
Note2: Powershell may be getting confused and thinking your looking for the variable $accountname.pdf
Instead, try this...
$ous = Get-ADOrganizationalUnit -Filter "Name -eq 'Admin-User-Accounts'"
$ous | ForEach-Object {
$AccountName = $(Get-ADUser -Filter * -SearchBase $_.DistinguishedName).SamAccountName
Test-Path "\\domain.net\SYSVOL\domain.net\IA\$($AccountName).pdf"
}
here, we save the value of just .SamAccountName for the query to the $AccountName, and by adding $($accountname) we make clear the variable we want, and that .pdf is not part of the variable name.
Now, note as well, this doesn't save the results anywhere, it will just flash them to screen.

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.

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.