Powershell: Variable expansion of quoted string with curly braces in Get-ADUser doesn't work in v7.0.2

Created on 6 Jul 2020  路  12Comments  路  Source: PowerShell/PowerShell

When I use Get-ADUser to query Active Directory using a string variable in the Filter string, Powershell fails to properly interpret the variable. This works in as expected in Powershell v5.1.

Steps to reproduce

$loginName = "egomez"
Get-ADUser -Filter {SamAccountName -eq $loginName}

Expected behavior

DistinguishedName : CN=Elizabeth Gomez,OU...
Enabled           : True
GivenName         : Elizabeth
Name              : Elizabeth Gomez
ObjectClass       : user
ObjectGUID        : ...
SamAccountName    : egomez
SID               : S-1-...
Surname           : Gomez
UserPrincipalName : egomez@...

Actual behavior

Get-ADUser: Variable: 'loginName' found in expression: $loginName is not defined

Environment data

Name                           Value
----                           -----
PSVersion                      7.0.2
PSEdition                      Core
GitCommitId                    7.0.2
OS                             Microsoft Windows 6.3.9600
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0.}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Screenshots:
image

image

Resolution-Answered

Most helpful comment

@indented-automation is likely to have a good handle on additional oddities there.

All 12 comments

Can you check the output from Get-Module ActiveDirectory | Format-List *? 馃檪

Generally speaking: using script blocks { ... } with the [string]-typed -Filter parameter is conceptually problematic - see this SO (Stack Overflow) answer.

I don't know if that's the case here, but if the Active Directory module is loaded via the Windows PowerShell compatibility feature, which uses _implicit remoting_, then the following from the linked SO answer applies (where "remote" refers to the hidden Windows PowerShell child process that knows nothing about the calling PowerShell [Core] session's variables):


Caveat: If you use Get-ADUser via an _implicitly remoting_ module, _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.

  • In that event you must use PowerShell's string interpolation ("...") or string concatenation from literals and variable references / expressions in order to "bake" any variable / expression values _into the string_, _up front_:
    Get-ADUser -Filter "sAMAccountName -eq `"$SamAc`""
  • Note that for _string_ operands embedded quoting then _is_ necessary.
  • Also, be sure to `-escape constants such as $true, $false, and $null inside the "..." string, so that PowerShell doesn't expand them up front.
  • Caveat: This technique may not work with all data types; at least the default stringification of a [datetime] instance (e.g., 01/15/2018 16:00:00 is _not_ recognized by the AD provider.

@tralston I believe @mklement0's comment answers your question. Let us know if you think otherwise.

@daxian-dbw @mklement0 Thank you for your responses. @vexx32, I ran the code you asked for and it was reallyyyyyyyyy long, so I didn't include it here. Is there a subset of that info you'd find useful?

I read the SO article, and I agree, there are anti-patterns associated with curly braces. But my question still remains: why did it change from 5.1 to 7.0.2? Do any you have a more recent version of PS that could test this out on a DC? Since I created this issue, I have thus modified my code to use quotes instead of curly braces, and it works fine now.

I guess I'm trying to understand if this is such a bad idea, why hasn't more recent versions of PS warned about this type of invocation? Why is the documentation for Get-ADUser still showing this (anti) pattern (see the end of this section)?

Maybe it's a breaking change in PS 7.0, or a breaking change in the ActiveDirectory module, or a completely unintentional side-effect. Let's call it what it is: a change/regression in the way the language is executed. If curly braces shouldn't be used at all for string interpolation (which they are in other constructs in PS, like naming variables), that's fine. Let's enforce it across the board. But if for this one function, it doesn't work how it's supposed to work everywhere else, then Get-ADUser needs to be fixed, or it should be updated to give an error on that type of construct. There shouldn't be one input with multiple types of output.

Sorry for the rant! I know there's a huge list of bugs and issues the coders are proposing every minute of the day, and it's a lot of work to assess which are truly bugs or just not writing proper code.

@tralston I believe it'll work just fine if you're on the latest version of Windows 10 and RSAT. The AD module had to be updated for Core, but afaik it's not coming to Windows 7 (which looks like where your second screenshot is from).

Why is the documentation for Get-ADUser still showing this (anti) pattern (see the end of this section)?

The original authors of the AD module seemed to think it was a good idea to pretend to support ScriptBlocks. It's largely brought nothing but confusion though. /cc @SteveL-MSFT I believe you worked on the Core port, any chance to get this pattern removed from the docs?

Let's call it what it is: a change/regression in the way the language is executed. If curly braces shouldn't be used at all for string interpolation (which they are in other constructs in PS, like naming variables), that's fine.

So basically, the parameter Filter is typed as a string. So behind the scenes, the AD module is doing something like this (though oversimplified):

$myFilter = { thing -eq $otherThing }
$asString = $myFilter.ToString()
$asString -replace '\$otherThing', $otherThing

Nothing in the language actually changed, it's just their hack that broke. There's no actual support for scriptblocks, it's processed exactly the same as if you passed Get-AdUser -Filter ' thing -eq $otherThing ' (which also shouldn't work but does because of their hack).

100000 thanks @SeeminglyScience for that explanation, and calling it "what it is". I seriously laughed out loud at this part:

The original authors of the AD module seemed to think it was a good idea to pretend to support ScriptBlocks

So thank you for brightening my day.

Unless @SteveL-MSFT has anything to add (i.e. updating docs to remove script blocks in AD modules), I'll mark this as resolved.

Thanks for the explanation, @SeeminglyScience, but note that what the AD module is doing is not just simple _text_ expansion of simple variable references in the -Filter argument:

At least with a [datetime]-typed PowerShell variable, AD seems to recognize it properly as a date, which - from what I'm told, I can't personally verify - does _not_ work with the up-front string-expansion approach.

Therefore, while the up-front string-expansion approach works with strings and numbers, it doesn't work with dates - and perhaps other data types?

In other words: while you _must_ use the up-front string-expansion approach whenever _implicit remoting_ is involved (because the AD module then doesn't see the caller's variables) - whether via the Windows PowerShell compatibility feature or via an explicitly created implicit-remoting module via Import-PSSession - it seemingly doesn't cover all use cases.

Or is the workaround for the [datetime] case (and possibly others - I personally don't have access to AD).

Yeah, the AD module's solution to the problem of how to make LDAP filters digestible for PS users just fundamentally involves misleading folks as to how it works. It's far too late to change it now, but I think if an effort like that were to be attempted again, it'd need a complete redesign in terms of the approach to avoid potential issues like this.

Thanks for the explanation, @SeeminglyScience, but note that what the AD module is doing is not just simple text expansion of simple variable references in the -Filter argument:

Yeah I did say it was over simplified 馃槈. I was mostly trying to illustrate how the AD module fakes scriptblock support FWIW.

That said, isn't it just file time? If so 'PropertyName -eq {0}' -f (Get-Date).ToFileTime() should work too.

Thanks, @SeeminglyScience (good point re oversimplification).

  • Can you - or someone with AD access - verify that .ToFileTime() indeed works (perhaps you already know)? I'd like to update my SO answer accordingly.

  • Is anyone aware of other data types that also need special handling with the up-front string-expansion approach?

@indented-automation is likely to have a good handle on additional oddities there.

Was this page helpful?
0 / 5 - 0 ratings