Search code examples
powershellforeachpowershell-remotingget-aduser

Variable not defined when trying to list a group of AD user from a CSV using Get-ADUser -Filter {displayName


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
    }

Solution

  • 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:

    • While -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.
      However, in the case at hand (implicit remoting, see below) using string interpolation is the only solution.

    Background information: The limitations of implicit remoting:

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

      • Script modules do not see a caller's preference variables (except if the call happens to be made from the global scope, which in scripting is typically not the case), such as $ErrorActionPreference = 'Stop'.
      • For a detailed discussion, see GitHub issue #4568
    • 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'.