I am using the below code to try and pull out a list of last logon times for a group of users contained in a CSV file. I have a test domain at home and the script below works fine. However, when I try it on my work prod AD I get the following error.
Get-ADUser: Variable: 'dname' found in expression: $dname is not defined.
This is the script I have put together.
$list = Import-Csv -Path "C:\data\PowerShell\UserInfo.csv"
ForEach ($user in $list) {
$dname = $user.Displayname
Get-ADUser -Filter {displayName -eq $dname} -Properties lastLogon | Select-Object Name, SamAccountName, @{Name="LastLogon"; Expression={[DateTime]::FromFileTime($_.lastLogon)}} | Export-CSV -Path "C:\data\PowerShell\user_last_login.csv" -append -NoTypeInformation
}
tl;dr
For the reasons explained in the next section, your Get-ADUser
proxy command[1] doesn't see your local variables inside a -Filter
argument. Therefore, use string interpolation instead, so as to directly incorporate the variable value into the -Filter
argument (note the need to use embedded double-quoting (`"
)around the expanded value):
Get-ADUser -Filter "displayName -eq `"$dname`"" -Properties lastLogon
As an aside:
-Filter {displayName -eq $dname}
does work if Get-ADUser
is the regular (non-proxy) cmdlet from the ActiveDirectory
module that directly talks to an AD server, the script-block syntax ({ ... }
) - while convenient - is conceptually problematic and can lead to misconceptions, so -Filter 'displayName -eq $name'
may be preferable - see this answer for background information.Your symptom implies that your Active Directory commands are provided via implicit remoting, where a local proxy module is used to relay calls to a remote machine behind the scenes using PowerShell's remoting feature (the proxy modules are typically created with the Export-PSSession
cmdlet; this article provides an introduction to implicit remoting).
In a nutshell, the proxy module contains proxy commands of the same name as their remote counterparts, implemented as PowerShell functions that relay the calls to their remote counterparts.[1]
While this proxying mostly works as intended, it has side effects:
References to local variables, such as in your -Filter
argument cannot work, because the remote command that ultimately executes the operation knows nothing about the local caller's variables.
Note that AD module's -Filter
parameter is unusual in that it expects a string, inside of which (stand-alone only) references to PowerShell variables are recognized; while it is common to see script blocks ({ ... }
) as -Filter
arguments, they are actually converted to strings during parameter binding; while using script blocks is syntactically convenient, it obscures the real behavior (it can falsely suggest that the argument is a piece of PowerShell code, which it isn't) and can lead to conceptual confusion - see this answer for background information.
In cases where actual script blocks are passed to remoting commands, notably in the context of Invoke-Command
, the values of local variables can be embedded in them via the $using:
scope.
The following side effect applies to script modules in general (script modules are those whose exported commands are implemented as PowerShell functions rather than as binary cmdlets, as is the case in implicit remoting):
$ErrorActionPreference = 'Stop'
.The following side effect applies not only to PowerShell's remoting in general, but also to background jobs and "mini shells" (in-session PowerShell CLI calls with script blocks):
Redirecting, capturing, or suppressing output streams other than the success (1
) and error output stream (2
) is inconsistently supported with partial inability to redirect and/or capture streams, and partial inability to effectively suppress output streams.
For details, see GitHub issue #9585.
[1] Given that Get-ADUser
is normally a binary cmdlet, an easy way to test if a given Get-ADUser
command is a proxy command is to use (Get-Command Get-ADUser).CommandType -eq 'Function'
.