memberof inconsistent filtering

I want all AD users who are Not a member of “this group”

neither of these work.

get-aduser -filter * -properties memberof | ? memberof -notlike “this group

get-aduser -filter {memberof -notlike “this group”}

you’re close.
try something like

get-aduser -filter * -properties memberof|where-object -filterscript {$_.memberof -notlike "*groupname*"}

thank you. I’ve never heard of filterscript, so now my obvious next question is: why do I have to use filterscript for memberof, when I don’t have to use it for other AD attributes?

get-aduser -filter * -properties samaccountname | ? samaccountname -like “*john.curtiss”

works just fine.

and: what other AD attributes require filterscript?

 

well you’re confusing 2 things.
first, the aduser filter has some limitations on exactly what your allowed to use for filtering

per get-aduser ms documentation:
To search for and retrieve more than one user, use the Filter or LDAPFilter parameters. The Filter parameter uses the PowerShell Expression Language to write query strings for Active Directory. PowerShell Expression Language syntax provides rich type conversion support for value types received by the Filter parameter. For more information about the Filter parameter syntax, type Get-Help about_ActiveDirectory_Filter. If you have existing Lightweight Directory Access Protocol (LDAP) query strings, you can use the LDAPFilter parameter.

I believe memberof is not a valid ldapfilter, so what we actually do is return every user, and their memberof value. we pipe those into the where-object powershell cmdlet. thats where the -filterscript comes in.
technnically i don’t have to call out the -filterscript but i wanted to use the verbose process so you could see exactly what happened.

documentation for where-object is here:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/where-object?view=powershell-6

actually I take it back, don’t think any of these are working…

#count all users in the OU, returns 618

(get-aduser -searchbase $searchbase -filter * | ? {$_.distinguishedname -notlike "*service accounts*"}).count
 

#count all users in the OU, in the group, returns 252

(get-aduser -searchbase $searchbase -filter * -Properties memberof | ? {$_.distinguishedname -notlike "*service accounts*" -and $_.memberof -like $group}).count
 

#count all users in the OU, NOT in the group, returns 602

(get-aduser -searchbase $searchbase -filter * -Properties memberof | ? {$_.distinguishedname -notlike "*service accounts*" -and $_.memberof -notlike $group}).count


#count all users in the OU, NOT in the group, using filterscript, returns 602

(get-aduser -searchbase $searchbase -filter * -Properties memberof | ? -filterscript {$_.distinguishedname -notlike "*service accounts*" -and $_.memberof -notlike $group}).count 

I have the wildcards included in the string assigned to the $group variable. if I count all users in the OU, I get 618. if I count all users in the OU who ARE in the group, I get 252, which is also confirmed by get-adgroupmember. if I count all users in the OU who ARE NOT in the group, I get 602, whether or not I use filterscript. so if there are only 618 users in the OU, how can 252 of them be in the group and 602 of them not be in the group?

help?

So, as for your original query…

I want all AD users who are Not a member of "this group"

neither of these work.

get-aduser -filter * -properties memberof | ? memberof -notlike “this group
get-aduser -filter {memberof -notlike “this group”}

There are a half dozen ways to get this information. Some more direct than others. So, my question is on the more direct way to address your use case. Why are you not using…

Get-ADGroupMember

… since it’s specific goal is to return users of the group(s) specified directly?
Of course any user not in the list returned, well you know. You can use this list to compare / match, etc… against ADDS all users list via Get-ADUser.

# Total AD users
(Get-ADUser -Filter *).Count
# 46

# Users in the group
((Get-ADUser -Filter *).SamAccountName | 
% {If($_ -eq $((Get-ADGroupMember $group).SamAccountName)){$_}}).Count
# 1

# Users not in the group
((Get-ADUser -Filter *).SamAccountName | 
% {If($_ -ne $((Get-ADGroupMember $group).SamAccountName)){$_}}).Count
# 45

# This

Clear-Host
# Return a group name by array index
# Note - I am doing this to not how sensitive info in the post

"Listing non-members of the group $TargetGroupName"
$users = (Get-ADUser -Filter *).SamAccountName
$group = $TargetGroupName = ((Get-ADGroup -Filter *).Name)[75]((Get-ADGroupMember -Identity 
$members = (Get-ADGroupMember -Identity $TargetGroupName).SamAccountName

ForEach ($user in $users) 
{
    If ($members -contains $user) 
    {"$user exists in the group"} 
    Else 
    {Write-Warning -Message "$user not in the group"}
}


# Results

...
WARNING: labuser001 not in the group
labuser002 exists in the group
WARNING: labuser003 not in the group
…

If you want to stick with the indirect memberof route, then you have to deal with that with more work and specificity and be cautious logic issues. I decided to give you an approach that should work for your use case, vs figuring out why you are getting the oddities you are seeing. Yet, it’s always a matter of using the right tool for the right job, but even that is based on what you want to do/use/believe or trust.

I guess it never occurred to me that the “memberof route” was the indirect one. I must be used to the one-liners

get-aduser -filter * -properties [ad attribute] | ? [ad attribute] -notlike “something

and

get-aduser -filter {[ad attribute] -notlike “something”}

working well for me for other [ad attribute]s. so I expected to be able to one-liner this one.

but (i think) I read somewhere that since memberof contains DistinguishedNames, it doesn’t work with wildcards. which is kind of weird, since “where memberof -like $group” returns the correct number of members, but “where memberof notlike $group” doesn’t work at all. but I just used the whole DN of the group, and a one-liner with contains or notcontains works correctly.

get-aduser -filter * -properties memberof | ? memberof -notcontains "[dn of this group]"

(contains and notcontains are not supported in a get-aduser -filter)

I got a link for you, please try this.

https://serverfault.com/questions/866510/get-all-ad-users-not-in-parent-group

No worries, and yes ADDS is very finicky about how you ask it for things. There are lots of articles on the web regarding it.

As for this…

whole DN of the group, and a one-liner with contains or notcontains works correctly.'
… this is because without the fully qualifying, you are not bringing back a specific object but the needed for the select, so, it can't be parsed for what you were after.

A Few things:

First:

The negative of a query is sometimes hard to do when dealing with logical operators. Here is an option that will accept wildcards (regex).

note that the memberof property does not include the user’s primary group, so we may need to account for that.

 

# define the search string

#  can be any part of the full distinguishedName

#    ( Group Name, OU Name, or part of CN path )

$GroupName = "admins"

Get-ADUser -filter * -properties memberof, primaryGroup | ForEach-Object {

# initalize $MATCH to FALSE (NOT FOUND)

$matchFlag = $false

 

# memberOf of is a collection

# PowerShell will implicitly expand ALL the Values in memberOf

# and try to match the regex

# if a match is found, set matchFlag to TRUE

# *** using -MATCH because it accepts a regex (wildcard support)

if ($_.memberof -match ".*$GroupName.*" ) {

$matchFlag = $true

}

 

# this is a single value

# checking PrimaryGroup for a match too

if ($_.PrimaryGroup -match ".*$GroupName.*" ) {

$matchFlag = $true

}

 

# if a match WAS found, then do nothing

# if a match was NOT found, then put the current

# object back out on the pipeline

if (-not $matchFlag) {

Write-Output $_

}

}

 

Second:

Test in your environment and break down what you are really looking for.

In my sample, above, I’m pulling ALL AD users and then checking for a match. How would this scale is you had 60,000 accounts and users were in 100 groups, on average?

If you have the reverse; you can loop across the groups which have a member property, then perform similar logic.

 

Lastly,

This method deals with direct group assignment, if you need to account for recursive groups the you’ll need to look into LDAP filter memberOf:1.2.840.113556.1.4.1941:=

A Few things:

First:

The negative of a query is sometimes hard to do when dealing with logical operators.

Here is an option that will accept wildcards (regex).

note that the memberof property does not include the user’s primary group, so we may need to account for that.

 

# define the search string
#  can be any part of the full distinguishedName
#    ( Group Name, OU Name, or part of CN path )
$GroupName = "admins"
Get-ADUser -filter * -properties memberof, primaryGroup | ForEach-Object {
    # initalize $MATCH to FALSE (NOT FOUND)
    $matchFlag = $false
 
    # memberOf of is a collection
    # PowerShell will implicitly expand ALL the Values in memberOf
    # and try to match the regex
    # if a match is found, set matchFlag to TRUE
    # *** using -MATCH because it accepts a regex (wildcard support)
    if ($_.memberof -match ".*$GroupName.*" ) {
        $matchFlag = $true
    }
 
    # this is a single value
    # checking PrimaryGroup for a match too
    if ($_.PrimaryGroup -match ".*$GroupName.*" ) {
        $matchFlag = $true
    }
 
    # if a match WAS found, then do nothing
    # if a match was NOT found, then put the current
    # object back out on the pipeline
    if (-not $matchFlag) {
        Write-Output $_
    }
}

Second:

Test in your environment and break down what you are really looking for.

In my sample, above, I’m pulling ALL AD users and then checking for a match. How would this scale if you had 60,000 accounts and users were in 100 groups, on average?

If you have the reverse; would it be better to loop across the groups, which have a member property, then perform similar logic to find the negative.

 

Lastly:

This method deals with direct group assignment, if you need to account for recursive groups the you’ll need to look into LDAP filter memberOf:1.2.840.113556.1.4.1941:=