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
)
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.