Get-ADUser Filter comparison [duplicate] - powershell

This question already has answers here:
Why doesn't $PSItem behave as expected when using a bracket-based -Filter argument?
(2 answers)
Closed 3 years ago.
I'm beginning in PowerShell and trying to make this line working :
(Get-ADUser -Filter {userPrincipalName -eq $($ListUsers[100].UserPrincipalName)} -SearchBase "..." -Properties *).distinguishedName
I tried a lot of different things but nothing worked.
The call of $ListUsers[100].UserPrincipalName) works 100% sure, I tried it outside of the line, so it's just a logical or syntaxic problem.
Could someone tell me how to get it to work ? Thanks

Use double quotes.
The -Filter argument to the Get-AD* cmdlets takes a string (!), not a script block (even though it looks like it, because braces can also be used instead of quotes).
Get-ADUser -Filter "userPrincipalName -eq '$($ListUsers[100].UserPrincipalName)'"
This way you get proper variable substitution. Note you must end up with a valid filter string, so putting single quotes around the values is necessary when those values are strings.
If you have many objects to fetch from AD, it can be beneficial to fetch them all in one go, instead of one-by-one in a loop. Consider this:
$ListUsers = #(<# ...list of items... #>)
# build LDAP filter string
$upn_filter = $ListUsers.UserPrincipalName -join ')(userPrincipalName='
$upn_filter = "(|(userPrincipalName=$upn_filter))"
$users = Get-ADUser -LDAPFilter $upn_filter
$users.distinguishedName
This builds an LDAP filter in the following form:
(|(userPrincipalName=A)(userPrincipalName=B)(userPrincipalName=C)(userPrincipalName=D))
which would be able to fetch 4 matching objects from AD in one step. Of course you can also build a "PowerShell-style" filter string, but I find the LDAP syntax a lot easier to handle, and it's shorter.
LDAP filter strings can get pretty long without the server complaining, and doing only one round-trip to the domain controller saves time.

Related

Searching partial names with Powershell [duplicate]

I'd like to check if a user account already exists in the system.
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter {sAMAccountName -eq "$SamAc"}
I'm not sure why, but $User will always return null even if {sAMAccountName -eq "$SamAc"} is supposed to be true.
What am I missing here?
Edit:
This is what was missing:
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
Editor's note: The script block ({ ... }) was replaced with a string.
There is valuable information in the existing answers, but I think a more focused summary is helpful. Note that the original form of this answer advocated strict avoidance of script blocks ({...}) and AD-provider variable evaluation, but this has been replaced with more nuanced recommendations.
Option A: Letting the AD provider resolve - stand-alone only - variable references:
Get-ADUser -Filter 'sAMAccountName -eq $SamAc' # note the '...' quoting
Note the use of '...', i.e. a verbatim (single-quoted) string, because the string's value is to be passed as-is to the AD provider (cmdlet).
While use of a script block ({ ... }), Get-ADUser -Filter { sAMAccountName -eq $SamAc }, technically works too (its verbatim content, sans { and }, is converted to a string), it is conceptually problematic - see bottom section.
Do not quote the variable reference ("$SamAc").
Use only stand-alone variable references (e.g, $SamAc); expressions are not supported (e.g., $user.SamAccountName or "$name*" or $("admin_" + $SamAc)); if necessary, use an intermediate, auxiliary variable; e.g.:
$name = "admin_" + $SamAc; Get-ADUser -Filter 'sAMAccountName -eq $name'
Generally, only a subset of PowerShell's operators are supported, and even those that are do not always behave the same way - see bottom section.
Caveat: If you use Get-ADUser via an implicitly remoting module - whether self-created via Import-PSSession or, in PowerShell v7+, via the Windows Compatibility feature - neither '...' nor { ... } works, because the variable references are then evaluated on the remote machine, looking for the variables there (in vain); if (Get-Command Get-ADUser).CommandType returns Function, you're using an implicitly remoting module.
If implicit remoting is involved, you MUST use string interpolation, as shown next.
Option B: Using PowerShell's string interpolation (expandable strings), up front:
Get-ADUser -Filter "sAMAccountName -eq `"$SamAc`"" # note the "..." quoting
Using "...", i.e. an expandable (double-quoted) string makes PowerShell interpolate (expand) all variable references and subexpression up front, in which case the AD provider sees only the (variable-free) result.
As shown above, for string operands embedded quoting then is necessary.
For embedded quoting, '...' is a simpler alternative to `"...`" (`" is an _escaped "), but note that this assumes that an expanded value doesn't itself contain ', which is a distinct possibility with last names, for instance.
Also, be sure to `-escape constants such as $true, $false, and $null inside the "..." string, which are always recognized by the AD provider; i.e., use `$true, `$false and `$null, so that PowerShell doesn't expand them up front.
Caveat: Using an expandable string does not work with all data types, at least not directly: for instance, the default stringification of a [datetime] instance (e.g., 01/15/2018 16:00:00 is not recognized by the AD provider; in this case, embedding the result of a call to the instance's .ToFileTime() (or .ToFileTimeUtc()?) method into the string may be necessary (as suggested in the comments on this post); I'm unclear on whether there are other data types that require similar workarounds.
On the plus side, string interpolation allows you to embed entire expressions and even commands in a "..." string, using $(...), the subexpression operator; e.g.:
# Property access.
Get-ADUser -Filter "sAMAccountName -eq `"$($user.SamAccountName)`""
# String concatenation
Get-ADUser -Filter "sAMAccountName -eq `"$('admin_' + $SamAc)`""
Background
Any argument you pass to -Filter is coerced to a string first, before it is passed to the Get-ADUser cmdlet, because the -Filter parameter is of type [string] - as it is for all provider cmdlets that support this parameter; verify with Get-ADUser -?
With -Filter in general, it is up to the cmdlet (the underlying PowerShell provider) to interpret that string, using a domain-specific (query) language that often has little in common with PowerShell.
In the case of Get-ADUser, that domain-specific language (query language) is documented in Get-Help about_ActiveDirectory_Filter.
Note: As of this writing, no newer version of this legacy topic exists; this GitHub issue requests one.
With Get-AdUser, the language supported by -Filter is certainly modeled on PowerShell, but it has many limitations and some behavioral differences that one must be aware of, notably:
As Santiago Squarzon points out, these limitations and difference stem from the fact that the language is translated into an LDAP filter behind the scenes, it is therefore constrained by its features and behaviors. (Note that you can use the -LDAPFilter parameter in lieu of -Filter to directly pass an LDAP filter).
Only a limited subset of PowerShell operators are supported, and some exhibit different behavior; here's a non-exhaustive list:
-like / -notlike only support * in wildcard expressions (not also ? and character sets/ranges ([...])
'*' by itself represents any nonempty value (unlike in PowerShell's wildcard expressions, where it also matches an empty one).
Instead of -eq "" or -eq $null to test fields for being empty, use
-notlike '*'.
Certain AD fields, e.g., DistinguishedName, only support '*' by itself, not as part of a larger pattern; that is, they only support an emptiness test.
There is no support for regex matching.
-lt / -le and -gt / -ge only perform lexical comparison.
Referencing a nonexistent / misspelled property name causes the Get-ADUser command to quietly return $null.
As stated, only stand-alone variable references are supported (e.g, $SamAc), not also expressions (e.g., $SamAc.Name or $("admin_" + $SamAc))
While you can use a script block ({ ... }) to pass what becomes a string to -Filter, and while this syntax can be convenient for embedding quotes, it is problematic for two reasons:
It may mislead you to think that you're passing a piece of PowerShell code; notably, you may be tempted to use unsupported operators and expressions rather than simple variable references.
It creates unnecessary work (though that is unlikely to matter in practice), because you're forcing PowerShell to parse the filter as PowerShell code first, only to have the result converted back to a string when the argument is bound to -Filter.
This one bit me when I first started to work with the ActiveDirectory module, and it was a pain to figure out.
The -Filter parameter for the ActiveDirectory module cmdlets is actually looking for a string. When you do {sAMAccountName -eq "$SamAc"} as the value, it is actually looking for "sAMAccountName -eq ""`$SamAc"""
Basically, Powershell parses the parameter and turns its value into a string, and will not interpolate the variable. Try building the string before hand, and it should work.
Something like this:
$SamAc = Read-Host 'What is your username?'
$filter = "sAmAccountname -eq ""$SamAc"""
$User = Get-ADUser -Filter $filter
I have to comment on this because it really aggravated me to sort this out.
Joseph Alcorn has the right idea. The filter parameter takes a string and then evaluates that in order to process the filter. What trips people up with this is that you are given the option to use curly brackets instead {}, and this doesn't work as you'd expect if you were using Where... it still has to be treated like a string.
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
I recommend sticking to quotes to make it more clear/readable for yourself and others and to avoid potential syntax errors, or stick to Where{} in the pipeline. When doing so, I find it best to use double-quotes on the outside & single-quotes on the inside so you still get intellisense detection on the variable.
Simply remove the quotes around your variable:
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter {sAMAccountName -eq $SamAc}
This should work just fine.
if (($ADUser = Get-ADUser -filter "SamAccountName -eq '$(Read-Host Username)'") -ne $null) {$ADUser.SamAccountName} else {"Not Found"}
Little addendum if anyone like me got here and was still tearing their hair out:
-properties *
Would be quite a common this to have in this query. Doesn't work, I'm sure someone smarter than me can figure it out
-properties mail,cn,wtf
etc does work as expected
It took me quite a bit to just use
Do not quote the variable reference ("$SamAc").
TXH so much
Okay, I got mine to finally work using the following syntax and using the following example from up above:
Previously:
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
Working Version:
$user = Get-aduser -Filter "sAMAccountName -eq '$($SamAc)'"
I had to add $($ ) to $SamAc before PowerShell could access the variable string value.

Issues getting get-adcomputer to recognize variable in filter

Below is the code I am working with. I have verified that the initial import-csv is working as it should be, and if I change out the variable object for a concrete object, the script works as it should. It just seems to not recognize/use the variable the way that it should.
$CSVOutput = "C:\temp\output.csv"
$Output = foreach($u in $userlastname)
{
Get-ADComputer -Filter {Description -Like '*$u*'} -properties Description | Select Name, Description
}
$Output | Export-Csv $CSVOutput
If I replace the $u in the filter with one of the values from the $userlastname variable, it works, but it just runs the search with the set value as many times as it runs the foreach loop. I am expecting to see several different computer objects that have the different values from $userlastname in their description. Currently it returns nothing, as if it found no values that matched in the description field.
While it’s technically possible to use a scriptblock as a filter in the ADCommands, it isn’t recommended - use a string instead:
Get-ADComputer -Filter "Description -like '*$($u.name)*'" -Properties ...
Using a string will solve your variable substitution issue.
ETA: Comments indicated that you were getting #{Name=User} as the expansion for $u in the filter expression. This is because $u was a structured [PSCustomObject], and you were looking for a single field from that object. The easiest way to get the value of the desired field of the object is simply to use the PowerShell evaluation construct, as given in the edited answer.

How to effectively use the `-Filter` parameter on Active Directory cmdlets?

All too often I see the following type of code on this site, specific to the AD cmdlets:
Get-ADUser -Filter * | Where-Object { $_.EmailAddress -eq $email }
The problem is that you are returning every single user object in Active Directory, and then processing it a second time. How can we improve upon this, not only to reduce the time it takes to run the script, but to also take the unnecessary load off of Active Directory, and possibly the network?
Note about Azure AD cmdlets
This answer is crafted around the Active Directory cmdlets installed and available from Remote Server Administration Tools (RSAT). However, the Azure AD cmdlets make use of Microsoft Graph (OData v4.0 specification) to run queries against Azure AD while the RSAT cmdlets[1] rely on an implementation of the PowerShell Expression Engine intended to replace LDAP filters.
As such, the filter examples below will not work with Azure AD cmdlets without some modification to be compatible with the Microsoft Graph specification, specifically, its filter syntax. However, the general practices mentioned here should still apply.
[1] - This is the most recent version of this document I could find.
What is so bad about -Filter *?
You are effectively selecting and returning every object that exists in AD, based on the cmdlet you are using (e.g. Get-ADUser, Get-ADComputer, Get-ADGroup, the generic Get-ADObject, etc.). This is an expensive thing to do, especially in larger AD environments. In large enough environments you will want to split up your queries in batches, even if you legitimately need to operate on every AD object of a given type. On top of this, your script will end up processing far more data than it needs to, increasing execution time and used processing time when it just isn't necessary.
The -Filter parameter can do more than just match on everything, which is effectively what -Filter * does. The -Filter string is very much like Powershell syntax (not quite, but most of the way there). You can use most of the same logical operators that Powershell supports, and they work much in the same way that Powershell operators do. This answer aims to clarify this and explain how to use this elusive parameter. These examples will use the Get-ADUser cmdlets but this also extends to the other Get-ADObject cmdlets which use filters as well.
Syntax
The syntax for the -Filter string is "PropertyName -comparisonoperator 'somevalue'", though you can string multiple conditions together with logical operators such as -and and -or. Note that there are no regex matching operators, so you will have to make do with -like and-notlike globbing.
Comparison Operators
MS calls these FilterOperators but they are used in the same way as
PowerShell's comparison operators are (ignoring the fact that technically -bor and -band are arithmetic operators). These are used for comparing values:
Note: AD attributes in DistinguishedName format will not have globbing applied when-like or -notlike are used, in other words you have to look for an exact match. If you need a DN to match any pattern, this cannot be performed with -Filter or-LDAPFilter. You will have to -Filter where you can, and perform additional processing with the -like or -match operators once your Get-ADObject cmdlet returns.
-eq, -le, -ge, -ne, -lt, -gt, -approx, -bor, -band, -recursivematch, -like, -notlike
The only ones which are unique to the -Filter query syntax are -approx and-recursivematch. Don't worry about -approx, it is functionally equivalent to -eq in Active Directory.
Despite its name, -recursivematch is not a regex matching operator, it works like PowerShell's-contains operator in that it will return $true if the collection contains the target value.
Logical Operators
MS calls these JoinOperators but they fill the same role as their PowerShell logical operator equivalent. These are used to join multiple conditions together in a single query:
-and, -or
Strangely enough, MS gives negation to a special operator type called NotOperator, which consists of a single operator:
-not
Matching on a property
To use the example in the question, let's find a user matching an email address, but without piping to Where-Object (crazy right???):
$email = 'box#domain.tld'
Get-ADUser -Filter "EmailAddress -eq '${email}'"
Done. Get-ADUser will return any accounts where the EmailAddress property equals whatever the $email variable is.
What if we want to find all user accounts that haven't been logged onto in the last 30 days? But a date string is more complex than an email! Who cares, still pretty simple!
# Get the date from 30 days ago
$notUsedSince = ( Get-Date ).AddDays( -30 )
Get-ADUser -Filter "LastLogonDate -lt '${notUsedSince}'"
This returns all users who have not logged on in the last 30 days.
Getting users who are members of a group
If you want to get all ADUsers who are members of a certain group, we can make use of the-recursivematch operator for this:
Get-ADUser -Filter "memberOf -recursivematch 'CN=test_group,CN=Users,DC=exampledomain,DC=net'"
memberOf is an array of Distinguished Names, -recursivematch returns true if the array on the lefthand side contains the value on the righthand side.
You can alternatively avoid the use of Get-ADUser at all in this scenario and use Get-ADGroup to retrieve the members from that:
( Get-ADGroup group_name -Properties Members ).Members
While the Get-ADGroup example above is shorter to type, filtering on memberOf withGet-ADUser can be effective when you have multiple conditions and need to return users that are members of a group, but not necessarily need to return the group for local processing. It may be interactively inconvenient but it is a worthy technique in any automated process integrating with Active Directory and may become necessary in cases where you have extremely large groups.
One example is when enumerating Domain Users in a very large domain. You might want to rethink returning 32,000 users from ( Get-ADGroup ).Members to then have to apply additional filtering.
Note: Most users will actually have Domain Users set as their PrimaryGroup. This is the default and most times this doesn't need to be changed. However, you must use-Filter on PrimaryGroup instead as the PrimaryGroup is not stored under MemberOf for an ADUser. It is also a single value, not a collection, so use -eq:
Get-ADUser -Filter "PrimaryGroup -eq 'PRIMARY_GROUP_DN'"
What if the query term contains a quote?
Quotes in the query term will throw a wrench in your query in most cases. Consider the example of searching for users with O'Niel in the name. This can break either the query or your script logic depending on the quoting technique used:
# Our heroic search term
$term = "O'Niel"
# Dragons abound (results in a query parsing error)
Get-ADUser -Filter "Name -like '*${term}*'"
# Your princess is in another castle ($term is not expanded
# and the literal string ${term} is instead searched for)
Get-ADUser -Filter 'Name -like "*${term}*"'
In this case you will have to use double-quoted strings in both places, but fortunately the escape-hell isn't too bad. Using the same value for $term as before:
# Your quest is over (this works as intended and returns users named O'Niel)
Get-ADUser -Filter "Name -like ""*${term}*"""
# Backticks are ugly but this also works
Get-ADUser -Filter "Name -like `"*${term}*`""
Note: If your query looks for a field value which contains both single and double quotes, I'm not sure how to facilitate this with one command when using the -Filter parameter. However, -LDAPFilter should be able to facilitate this, as parentheses (), not quotes, are used for the internal query bounds. See the Filter Examples in about_ActiveDirectory_Filter and the LDAP Filters section of this AD Escape Characters post for more information, as -LDAPFilter is beyond the scope of this answer.
Matching on multiple properties
Matching on multiple properties is not much different, but it's best to wrap each condition in parentheses (). Here's an example, let's find non-domain admin accounts (assuming we know this by the username nomenclature *-da) that don't have an email address associated with them.
Get-ADUser -Filter "(samaccountname -notlike '*-da') -and (EmailAddress -notlike '*')"
This one is a little trickier, because we can't pass in an empty value for the right side of a condition in the -Filter, as in the case for EmailAddress. But '*' matches any non-empty value, so we can leverage that behavior with the -notlike comparison operator to find empty values for EmailAddress. To break down the filter, make sure that any accounts ending in -da aren't matched by the filter, and then also only match accounts that do not have an EmailAddress value.
Things to avoid
Don't try to use a { ScriptBlock } for your filter parameters. Yes, we are all more comfortable with writing a ScriptBlock than worrying about building a string and making sure it's escaped properly. There is definitely an attraction to using them. I've seen so many answers using a ScriptBlock as a -Filter argument, or people having problems (myself included) trying to do something like this, and SURPRISE!!! Nothing gets returned:
Import-Csv C:\userInfoWithEmails.csv | Foreach-Object {
Get-ADUser -Filter { EmailAddress -eq $_.Email }
}
-Filter doesn't support ScriptBlocks, but they Kind of Work Sometimes™ because while they get rendered as a literal string, the PowerShell Expression Engine used by -Filter is capable of rendering your variables before running the query. Because of this, they will technically work if you use simple variable expansion like $_ or $emailAddress, but it will eventually cause you a headache, especially if you try to access an object property (like above) because it simply won't work.
In addition, you get largely undocumented (or difficult to locate info) behavior for how these variables are expanded, because they are not always ToString'd like you would expect. Figuring it out becomes a trial-and-error affair. Some attributes are admittedly easier to obtain this way, but using techniques you don't understand and which have little documentation is a risky move when programming. Because of this, I don't relying on the cmdlet-internal variable expansion that occurs whether you use a literal string or a ScriptBlock with the AD cmdlets.
Use a string filter every time, and if you need to use a variable value or object property as a portion of the filter, use Variable Substitution or Command Substitution.
You do not need to specify additional -Properties if you only care about a property to filter on it. The AD cmdlets can evaluate all properties within the -Filter parameter without needing to pass them down the pipeline.
And while I'm at it, don't ever use -Properties *, excepting maybe if you are inspecting all properties on a returned object for some reason, such as during script development, or interactively you're not quite sure what you're looking for (note that not all attributes are returned by default).Only specify the properties you need to process after the AD object has been returned. There is a reason for this - some properties are particularly expensive to get the values for. Best practice is to only forward the properties you need to process down the pipeline.
You cannot use the -Filter parameter to filter on Constructed Attributes using either-Filter or -LDAPFilter. This is because constructed attributes are, by definition, computed (or "constructed") on the fly, and are not actually stored values within Active Directory. I imagine it's because many Computed Attributes are expensive to compute, which would have to be performed on every relevant ADObject to filter on it from the AD side.
If you need to filter on Constructed Attributes, you will need to first return a set of ADObjects, specifying the Computed Attribute with -Properties, then further filter with Where-Object or some other technique.
Wildcards * do not work for fields that return a DistinguishedName type, such as DistinguishedName, manager, PrimaryGroup, etc. In addition, DistinguishedNames carry their own set of escaping rules.
Some AD attributes are returned as a proper DateTime for easier processing in PowerShell, but the basic time comparison exemplified above requires the underlying ADAttribute to be defined as the Interval type. Some time-based properties, such as whenCreated are defined as Generalized-Time strings, which are UTC timezone and formatted as yyyMMddHHmmss.Z. Additionally, some properties likemsDS-UserPasswordExpiryTimeComputed are in file-time format (and is returned as such with the AD cmdlets).
Convert a target DateTime for filtering to the Generalized-Time string format like so:
( Get-Date ).ToUniversalTime().ToString('yyyMMddHHmmss.z').
Note that this string is not directly convertible back to a DateTime.
Convert a returned file-time to a DateTime like so (using the aforementioned property as an example):
[DateTime]::FromFileTime($adUser.'msDS-UserPasswordExpiryTimeComputed')
In summarium
These techniques using the -Filter parameter with the AD cmdlets will save you costly processing time when iterating over large AD environments, and should improve the performance of your Powershell AD operations. I hope this helps explain some of the elusive behaviors of the AD cmdlets' -Filter parameter.
Additional Resources
As it is a good idea to understand the AD attributes you are working with, below are some Microsoft resources to help you identify and understand how different attributes are defined and function within the AD schema, as well as learn more about the -Filter syntax:
OpenSpecs
AD Schema
about_ActiveDirectory_Filter
While outdated, this document help is still accurate. Due to a bug it has not been updated since 2013. Until this bug is fixed and the documentation has been updated, the above link will have to suffice.

Why doesn't $PSItem behave as expected when using a bracket-based -Filter argument?

I was assisting a user with this question, linked to my answer here: Powershell script to add users to A/D group from .csv using email address only?
Initially I wrote the script as follows, using a bracket-based filter for Get-AdUser like follows:
Import-CSV "C:\users\Balbahagw\desktop\test1.csv" |
Foreach-Object {
# Here, $_.EmailAddress refused to resolve
$aduser = Get-ADUser -Filter { EmailAddress -eq $_.EmailAddress }
if( $aduser ) {
Write-Output "Adding user $($aduser.SamAccountName) to groupname"
Add-ADGroupMember -Identity groupname -Members $aduser
} else {
Write-Warning "Could not find user in AD with email address $($_.EmailAddress)"
}
}
However, $_.EmailAddress failed to populate a value. However, changing the Get-ADUser filter to a string-based filter worked as intended:
$aduser = Get-ADUser -Filter "EmailAddress -eq '$($_.EmailAddress)'"
What is the strangeness I'm experiencing, and why? Is it because when I'm using brackets, it's treated as a new scope and the $PSItem won't follow?
-Filter parameters are generally string parameters (verify with
Get-Help Get-AdUser -Parameter Filter)
They generally do not accept PowerShell code - filters are provider-specific and often have their own syntax, although it happens to be PowerShell-like in the case of the AD cmdlets.
Also, they generally have no knowledge of PowerShell variables (see below).
Thus, when a script block ({ ... }) is passed, it is converted to a string, which evaluates to its literal contents (everything between the opening { and the closing }):
{ EmailAddress -eq $_.EmailAddress }.ToString() yields the literal string EmailAddress -eq $_.EmailAddress - without any evaluation - and that's what Get-AdUser sees - no evaluation takes place.
In a presumably well-meaning but misguided effort to support the widespread, but ill-advised practice of passing script blocks to the -Filter parameter of AD cmdlets, it seems that these cmdlets actually explicitly expand simple variable references such as $_ in the string literal they receive, but that doesn't work with expressions, such as accessing a property of a variable ($_.EmailAddress)
Therefore, -Filter arguments should generally be passed as expandable strings ("..."); in the case at hand:
-Filter "EmailAddress -eq '$($_.EmailAddress)'"
That is, the only robust solution is to use strings with the variable parts baked in, up front, via string expansion, as shown above.
For values that are neither numbers nor strings, such as dates, you may have to use a literal string ('...') and rely on the AD provider's ability to evaluate simple references to PowerShell variables (e.g., $date) - see this answer of mine for details.
As stated, the syntax of AD filters is only PowerShell-like: it supports only a subset of the operators that PowerShell supports and those that are supported differ subtly in behavior - see Get-Help about_ActiveDirectory_Filter.
It is tempting to use script blocks, because the code inside requires no escaping of embedded quotes / no alternating of quote chars and no use of subexpression operator $(...). However, aside from using script blocks as strings being inefficient in general, the problem here is that the script block is making a promise that it cannot keep: it looks like you're passing a piece of PowerShell code, but you're not - and it works only in simple cases (and then only due to the misguided accommodation mentioned above); generally, it's hard to remember under what circumstances it doesn't work and how to make it work if it fails.
It is therefore really unfortunate that the official documentation uses script blocks in its examples.
For a more comprehensive discussion, see this answer of mine.
You're not wrong, it's the module's fault
The type of payload you have to use with the -Filter parameter differs depending on which provider you're working with, a design decision which can be pretty confusing!
The output of Get-Help Get-ADUser -Parameter Filter gives you some pretty detailed examples of the different syntax options you can use with the Active Directory Provider's implementation of Filter syntax.
Here's an example:
#To get all user objects that have an e-mail message attribute, use one of the following commands:
Get-ADUser -Filter {EmailAddress -like "*"}
It looks like the ActiveDirectory provider places the specific restriction that you must wrap the input in quotes. Here's what happens when I look for my account without putting quotes around my e-mail.
Get-ADUser -Filter {EmailAddress -eq stephen#foxdeploy.com}
Get-ADUser : Error parsing query: 'EmailAddress -eq stephen#foxdeploy.com'
Error Message: 'syntax error' at position: '18'.
But adding quotes? It works!
Get-ADUser -Filter {EmailAddress -eq "stephen#foxdeploy.com"}
DistinguishedName : CN=Stephen,CN=Users,DC=FoxDeploy,DC=local
Enabled : True
GivenName : Stephen
Name : Stephen
ObjectClass : user
ObjectGUID : 6428ac3f-8d17-45d6-b615-9965acd9675b
SamAccountName : Stephen
SID : S-1-5-21-3818945699-900446794-3716848007-1103
Surname :
UserPrincipalName : Stephen#FoxDeploy.local
How to make yours work
Now, because of this confusing filter implementation, you will need to change your user lookup on line 5 to the following:
$aduser = Get-ADUser -Filter "EmailAddress -eq `"$($_.EmailAddress)`""
We are providing the -Filter payload as a String. Next we want to use String Expansion to pull out the .EmailAddress property, so we wrap the string in $( ) to signal string expansion. Finally, the provider wants our filter comparison wrapped in quotes, so we put double quotes around it, and then escape the quotes using the backtick character.
And now it should work.
TLDR - blame the provider and blame the module, there are so many inconsistencies with the Active Directory module.

Get-Aduser -Filter will not accept a variable

I'd like to check if a user account already exists in the system.
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter {sAMAccountName -eq "$SamAc"}
I'm not sure why, but $User will always return null even if {sAMAccountName -eq "$SamAc"} is supposed to be true.
What am I missing here?
Edit:
This is what was missing:
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
Editor's note: The script block ({ ... }) was replaced with a string.
There is valuable information in the existing answers, but I think a more focused summary is helpful. Note that the original form of this answer advocated strict avoidance of script blocks ({...}) and AD-provider variable evaluation, but this has been replaced with more nuanced recommendations.
Option A: Letting the AD provider resolve - stand-alone only - variable references:
Get-ADUser -Filter 'sAMAccountName -eq $SamAc' # note the '...' quoting
Note the use of '...', i.e. a verbatim (single-quoted) string, because the string's value is to be passed as-is to the AD provider (cmdlet).
While use of a script block ({ ... }), Get-ADUser -Filter { sAMAccountName -eq $SamAc }, technically works too (its verbatim content, sans { and }, is converted to a string), it is conceptually problematic - see bottom section.
Do not quote the variable reference ("$SamAc").
Use only stand-alone variable references (e.g, $SamAc); expressions are not supported (e.g., $user.SamAccountName or "$name*" or $("admin_" + $SamAc)); if necessary, use an intermediate, auxiliary variable; e.g.:
$name = "admin_" + $SamAc; Get-ADUser -Filter 'sAMAccountName -eq $name'
Generally, only a subset of PowerShell's operators are supported, and even those that are do not always behave the same way - see bottom section.
Caveat: If you use Get-ADUser via an implicitly remoting module - whether self-created via Import-PSSession or, in PowerShell v7+, via the Windows Compatibility feature - neither '...' nor { ... } works, because the variable references are then evaluated on the remote machine, looking for the variables there (in vain); if (Get-Command Get-ADUser).CommandType returns Function, you're using an implicitly remoting module.
If implicit remoting is involved, you MUST use string interpolation, as shown next.
Option B: Using PowerShell's string interpolation (expandable strings), up front:
Get-ADUser -Filter "sAMAccountName -eq `"$SamAc`"" # note the "..." quoting
Using "...", i.e. an expandable (double-quoted) string makes PowerShell interpolate (expand) all variable references and subexpression up front, in which case the AD provider sees only the (variable-free) result.
As shown above, for string operands embedded quoting then is necessary.
For embedded quoting, '...' is a simpler alternative to `"...`" (`" is an _escaped "), but note that this assumes that an expanded value doesn't itself contain ', which is a distinct possibility with last names, for instance.
Also, be sure to `-escape constants such as $true, $false, and $null inside the "..." string, which are always recognized by the AD provider; i.e., use `$true, `$false and `$null, so that PowerShell doesn't expand them up front.
Caveat: Using an expandable string does not work with all data types, at least not directly: for instance, the default stringification of a [datetime] instance (e.g., 01/15/2018 16:00:00 is not recognized by the AD provider; in this case, embedding the result of a call to the instance's .ToFileTime() (or .ToFileTimeUtc()?) method into the string may be necessary (as suggested in the comments on this post); I'm unclear on whether there are other data types that require similar workarounds.
On the plus side, string interpolation allows you to embed entire expressions and even commands in a "..." string, using $(...), the subexpression operator; e.g.:
# Property access.
Get-ADUser -Filter "sAMAccountName -eq `"$($user.SamAccountName)`""
# String concatenation
Get-ADUser -Filter "sAMAccountName -eq `"$('admin_' + $SamAc)`""
Background
Any argument you pass to -Filter is coerced to a string first, before it is passed to the Get-ADUser cmdlet, because the -Filter parameter is of type [string] - as it is for all provider cmdlets that support this parameter; verify with Get-ADUser -?
With -Filter in general, it is up to the cmdlet (the underlying PowerShell provider) to interpret that string, using a domain-specific (query) language that often has little in common with PowerShell.
In the case of Get-ADUser, that domain-specific language (query language) is documented in Get-Help about_ActiveDirectory_Filter.
Note: As of this writing, no newer version of this legacy topic exists; this GitHub issue requests one.
With Get-AdUser, the language supported by -Filter is certainly modeled on PowerShell, but it has many limitations and some behavioral differences that one must be aware of, notably:
As Santiago Squarzon points out, these limitations and difference stem from the fact that the language is translated into an LDAP filter behind the scenes, it is therefore constrained by its features and behaviors. (Note that you can use the -LDAPFilter parameter in lieu of -Filter to directly pass an LDAP filter).
Only a limited subset of PowerShell operators are supported, and some exhibit different behavior; here's a non-exhaustive list:
-like / -notlike only support * in wildcard expressions (not also ? and character sets/ranges ([...])
'*' by itself represents any nonempty value (unlike in PowerShell's wildcard expressions, where it also matches an empty one).
Instead of -eq "" or -eq $null to test fields for being empty, use
-notlike '*'.
Certain AD fields, e.g., DistinguishedName, only support '*' by itself, not as part of a larger pattern; that is, they only support an emptiness test.
There is no support for regex matching.
-lt / -le and -gt / -ge only perform lexical comparison.
Referencing a nonexistent / misspelled property name causes the Get-ADUser command to quietly return $null.
As stated, only stand-alone variable references are supported (e.g, $SamAc), not also expressions (e.g., $SamAc.Name or $("admin_" + $SamAc))
While you can use a script block ({ ... }) to pass what becomes a string to -Filter, and while this syntax can be convenient for embedding quotes, it is problematic for two reasons:
It may mislead you to think that you're passing a piece of PowerShell code; notably, you may be tempted to use unsupported operators and expressions rather than simple variable references.
It creates unnecessary work (though that is unlikely to matter in practice), because you're forcing PowerShell to parse the filter as PowerShell code first, only to have the result converted back to a string when the argument is bound to -Filter.
This one bit me when I first started to work with the ActiveDirectory module, and it was a pain to figure out.
The -Filter parameter for the ActiveDirectory module cmdlets is actually looking for a string. When you do {sAMAccountName -eq "$SamAc"} as the value, it is actually looking for "sAMAccountName -eq ""`$SamAc"""
Basically, Powershell parses the parameter and turns its value into a string, and will not interpolate the variable. Try building the string before hand, and it should work.
Something like this:
$SamAc = Read-Host 'What is your username?'
$filter = "sAmAccountname -eq ""$SamAc"""
$User = Get-ADUser -Filter $filter
I have to comment on this because it really aggravated me to sort this out.
Joseph Alcorn has the right idea. The filter parameter takes a string and then evaluates that in order to process the filter. What trips people up with this is that you are given the option to use curly brackets instead {}, and this doesn't work as you'd expect if you were using Where... it still has to be treated like a string.
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
I recommend sticking to quotes to make it more clear/readable for yourself and others and to avoid potential syntax errors, or stick to Where{} in the pipeline. When doing so, I find it best to use double-quotes on the outside & single-quotes on the inside so you still get intellisense detection on the variable.
Simply remove the quotes around your variable:
$SamAc = Read-Host 'What is your username?'
$User = Get-ADUser -Filter {sAMAccountName -eq $SamAc}
This should work just fine.
if (($ADUser = Get-ADUser -filter "SamAccountName -eq '$(Read-Host Username)'") -ne $null) {$ADUser.SamAccountName} else {"Not Found"}
Little addendum if anyone like me got here and was still tearing their hair out:
-properties *
Would be quite a common this to have in this query. Doesn't work, I'm sure someone smarter than me can figure it out
-properties mail,cn,wtf
etc does work as expected
It took me quite a bit to just use
Do not quote the variable reference ("$SamAc").
TXH so much
Okay, I got mine to finally work using the following syntax and using the following example from up above:
Previously:
$User = Get-ADUser -Filter "sAMAccountName -eq '$SamAc'"
Working Version:
$user = Get-aduser -Filter "sAMAccountName -eq '$($SamAc)'"
I had to add $($ ) to $SamAc before PowerShell could access the variable string value.