User cleanup with get-aduser filtering AD group

I am looking for some help with my one liner, or a better approach. I am attempting to do user cleanup, but I have a very large dataset, 2 million users. The code below does work for me, and generally returns around 700K accounts. The problem I am having is the filter is inconsistent and I need to recurse the group I am trying to exclude. I am trying to stay away from storing the data to an array because of the large size, where-object seems to be a bad idea too. Any help from the PS community would be most appreciated.

I have tried recursing the group with get-adgroupmember and outputting to an array, but the filter in get-aduser didn’t work.

Thanks.

[pre]$Since = (Get-date).AddMonths(-26).Date

Get-ADUser -Filter {(memberOf -ne “DN of AD group”) -and (LastLogonTimestamp -lt $Since} -Properties DisplayName,SamAccountName,EmailAddress,memberof,lastlogontimestamp -SearchBase “OU name” -SearchScope Subtree -Server “DC ip” | Select DisplayName,SamAccountName,EmailAddress,@{name=‘MemberOf’;expression={ ($.MemberOf | ForEach { ($ -split ‘,’)[0] -replace ‘CN=’,’’}) -join ‘,’}},@{N=‘LastLogonTimestamp’; E={[DateTime]::FromFileTime($_.LastLogonTimestamp)}} | export-csv c:\InactiveUsers.txt -NoTypeInformation[/pre]

Consider using the -LDAPFilter parameter (much faster than -Filter). Because we want to filter as far left as possible, we need to convert the date into ticks so we can use it in our LDAP query. I think the below example will help you get going in the right direction.

$Since = ((Get-date).AddMonths(-26).Date).Ticks
$groupName = 'CN=Group,OU=Technology,OU=Departments,DC=Acme,DC=com'
Get-ADUser -LDAPFilter "(&(LastLogonTimeStamp<=$Since)(!MemberOf=$groupName))" -Properties LastLogonTimeStamp,memberOf,EmailAddress

I’ll give that a try today, thanks for your help.

The problem is memberof is an array. “memberof -ne ‘DN of AD group’” will always be true (unless memberof contains only one group), since it returns the other groups that don’t match. I’ve been unsuccessful with -notlike. -notlike treats arrays in the same way. Unfortunately -contains isn’t supported. Using -not and -eq seems to be the only way to get at nonmembers. (https://social.technet.microsoft.com/Forums/windowsserver/en-US/b3408ac8-131e-4055-94c5-24cb7e9bc5bf/find-users-not-in-group?forum=winserverpowershell) The filter is actually not a script block, even though script blocks get converted to strings and work. (And the official documentation in error has a script block filter example.) Script blocks won’t work in all cases.

The parentheses may be optional here, but usually you would need them with -not and -eq together.

PS C:\> 1,2,3 -ne 1                                                                                             
2
3

PS C:\> [boolean](1,2,3 -ne 1)                                                                                  
True

PS C:\> 1,2,3 -eq 3
3

PS C:\> [boolean](1,2,3 -eq 3)                                                                                  
True

Get-ADUser -Filter "-not (memberOf -eq 'DN of AD group') -and LastLogonTimestamp -lt '$Since'"

Maybe you can exclude parent groups as well.

Would it be easier to just use an array of SamAccountNames? For instance, if I just recurse the group?

I’ve had no luck getting something like this to work.

[pre]$UserstoExclude = get-adgroupmember -identity ‘DN of group’ -Recursive | Select-object -expandproperty SamAccountName

Get-Aduser -filter {SamAccountName -ne $UserstoExclude}[/pre]

Maybe I’m not setting that up right?