I often see people asking questions about the syntax of the -Filter parameter of the AD cmdlets. It is a strange syntax in that you have to think about them differently than you would just about any other comparision operation in Powershell.
Simple variables
The following filters will work.
$upn = 'jdoe@contoso.com'
Get-ADUser -Filter {UserPrincipalName -eq $upn}
Get-ADUser -Filter 'UserPrincipalName -eq $upn'
Get-ADUser -Filter "UserPrincipalName -eq '$upn'"
However, these will not work.
Get-ADUser -Filter {UserPrincipalName -eq "$upn"}
Get-ADUser -Filter "UserPrincipalName -eq $upn"
Subtle differences, I know, but it's code syntax. To understand how to build a proper filter, you need to understand how the filter is processed, and you need to know what the -Filter parameter expects.
Anything you pass to a parameter is first processed by Powershell, and then the results are passed to the cmdlet.
Script Blocks: Powershell doesn't do any processing to script blocks before they are passed to the cmdlet. Whatever you give it makes it to the ActiveDirectory module.
Get-ADUser -Filter {UserPrincipalName -eq $upn}
With this you are passing the ActiveDirectory
module the script block {UserPrincipalName -eq $upn}
Single Quoted Strings: Single quoted strings are considered literal strings and are passed to the cmdlet unmodified with one small exception. Any two consecutive single quotes inside the string are turned into one single quote. You would probably never see this in an AD filter, but I mention it to be as accurate as I can.
Get-ADUser -Filter 'UserPrincipalName -eq $upn'
With this you are passing the ActiveDirectory
module the string: UserPrincipalName -eq $upn
Double Quoted Strings: Double quoted strings are processed to expand variables and special escape characters, before it is passed to the cmdlet. So, what you give the -Filter parameter isn't neccesarily what it gets.
Get-ADUser -Filter "UserPrincipalName -eq '$upn'"
Since Powershell first expands the variables when
passing a double quoted string, what you are actually
passing to the ActiveDirectory module is the string
UserPrincipalName -eq 'jdoe@contoso.com'
. So,
you are passing it a comparison with actual values
instead of variables.
What to pass to -Filter? By now you should see that when passing a filter that contains a variable to an AD cmdlet, regardless of whether you use a script block or a string is the either a variable without any surrounding quotes, or a literal value.
Why don't these work?
Get-ADUser -Filter {UserPrincipalName -eq "$upn"}
Get-ADUser -Filter "UserPrincipalName -eq $upn"
The first one passes a script block, but the variable name is quoted. Since it is a script block, Powershell doesn't do any processing first. And, the ActiveDirectory module expects a variable name not surrounded in quotes. This will look for a UserPrincipalName that is literally $upn and not jdoe@contoso.com as you might expect.
The second one passes the string, but since it is double quoted, Powershell expands $upn before sending it to the ActiveDirectory module. The module gets the string 'UserPrincipalName -eq jdoe@contoso.com', which is an invalid comparison operation, because the literal value jdoe@contoso.com needs to be surrounded in quotes.
Variables of objects
Now, let's say you have an object and want to pass a property of the object in the filter. You can do that as outlined at MSDN Blogs Active Directory Powershell - Advanced Filter, but there is a major caveat. I mention it here just in case you get it to work, and then the next time you try it doesn't work, you'll understand why.
Say you get an object and store it in an variable, this works.
$user = Get-ADUser jdoe
Get-ADUser -Filter {UserPrincipalName -eq $user.UserPrincipalName}
Guess what doesn't work?
$user2 = Get-ADUser jdoe | Select Name,UserPrincipalName
Get-ADUser -Filter {UserPrincipalName -eq $user2.UserPrincipalName}
Frustrating, right? Well, I can only hazard a guess, but my observations tell me that when you do a Select-Object against the object returned from Get-ADUser, it turns all of the properties from the type Property to NoteProperty. To see for yourself what I mean, run the objects into Get-Member.
$user | Get-Member
$user2 | Get-Member
Apparently, the filter expects any properties referenced to be a Property. Guess what type of property you get when you do anything you would normally do in powershell; that's right, a NoteProperty, which won't work in an ActiveDirectory filter.
The whole point of this section was to illustrate that you can use a property of an object stored in a variable, but in only so particular an instance that it is best to avoid trying it. There are two acceptable workarounds. Either assign the value to another variable or pass the filter parameter a string in such a way that Powershell expands the value before it is passed down to the ActiveDirectory module.
$upn = $user2.UserPrincipalName
Get-ADUser -Filter {UserPrincipalName -eq $upn}
Get-ADUser -Filter "UserPrincipalName -eq '$($user2.UserPrincipalName)'"
Hash Tables and Arrays
Under no circumstances will attempting to pass the -Fitler parameter a value from a hash table or an array work.
These filters will not work.
$hash = @{'UPN'='jdoe@contoso.com'}
Get-ADUser -Filter {UserPrincipalName -eq $hash['UPN']}
$array = @('jdoe@contoso.com')
Get-ADUser -Filter {UserPrincipalName -eq $array[0]}
The only way to get this to work is to either assign the value to another variable or build the string so that powershell expands it, as illustrated in the section about objects and properties.
Dates
There can be some caveats with dates, given that there are two different formats for dates in Active Directory. On top of that, there are Powershell created properties -- that aren't stored in Active Directory -- that use a .Net DateTime object. For a full explanation see Filters with PowerShell Active Directory Module Cmdlets.
My rules of thumb for doing filters with dates are:
- whenever possible use the Powershell created properties, and
- convert dates to DateTime objects.
This is ideal.
$date = Get-Date '11/19/2015' # DateTime object
Get-ADUser -Filter {PasswordLastSet -eq $date} #Powershell property
These work.
$date = Get-Date '11/19/2015' # DateTime object
Get-ADUser -Filter {pwdLastSet -eq $date} # LDAP attribute
$date = '11/19/2015' # Just a string
Get-ADUser -Filter {PasswordLastSet -eq $date} #Powershell property
This won't work.
$date = '11/19/2015' # Just a string
Get-ADUser -Filter {pwdLastSet -eq $date} # LDAP attribute
Powershell created properties will work if you compare against a DateTime or a String. DateTime objects will work if you compare against a Powershell created property or an LDAP attribute. Why not just use the Powershell created properties and DateTime objects, and save yourself troubleshooting and headaches?
So, how to do you tell what the Powershell created properties are vs the LDAP attributes? Run this on a user.
Get-ADUser jdoe -Properties * |
Get-Member -MemberType Properties |
Select Name
Any properties that start with a lowercase letter are LDAP properties, any that start with an uppercase letter are Powershell created properties. The names are very similar so which ones are which shouldn't be hard to figure out in most cases (e.g. pwdLastSet vs. PasswordLastSet).