2

Why doesn't PowerShell allow the use of the using scope when using Invoke-Command locally? According to the documentation, the using modifier can only be used on remote commands. To quote:

Beginning in PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command.

This behavior can be demonstrated when running Invoke-Command locally:

$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName }

Which throws the following error:

A Using variable cannot be retrieved. A Using variable can be used only with Invoke-Command, Start-Job, or InlineScript in the script workflow. When it is used with Invoke-Command, the Using variable is valid only if the script block is invoked on a remote computer.

The error indicates that the remote use of the using modifier is only valid remotely, with Invoke-Command. So, if we try running the same thing using Start-Job, what happens?

$myServerName = 'www.google.com'
$j = Start-Job { ping $using:myServerName }
while( $j.State -eq 'Running' ){ Start-Sleep -s 1 }
Receive-Job $j

Which doesn't throw an error, and I get the output I expect:

 Pinging www.google.com [172.217.6.132] with 32 bytes of data:
 Reply from 172.217.6.132: bytes=32 time=20ms TTL=56
 Reply from 172.217.6.132: bytes=32 time=19ms TTL=56
 Reply from 172.217.6.132: bytes=32 time=19ms TTL=56
 Reply from 172.217.6.132: bytes=32 time=19ms TTL=56

Why does the documentation state that the using scope modifier only works remotely when it can be clearly used in local contexts as well? And similarly, if it works in the context of a local Start-Job, what stops it from working with a local Invoke-Command?

codewario
  • 19,553
  • 20
  • 90
  • 159
  • 1
    As of this writing, there are several open issues on GitHub to request improvements to the linked help topic, the main one being https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5068 – mklement0 Feb 01 '20 at 14:11

3 Answers3

2

This is true when using "using" because the definition of using states,

Beginning in PowerShell 3.0, you can use the Using scope modifier to identify a local variable in a remote command

Anytime you use the $using, you have to provide -ComputerName or -Session arguments whether the target server is localhost or remote.

Ex.

$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName }
### BIG ERROR.

$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName } -computername $env:COMPUTERNAME
### Ping response.

$myServerName = 'www.google.com'
Invoke-Command { ping $myServerName }
### Ping Reponse.

$using: is only supported in a few, specific contexts, which have one thing in common: code that is being run outside the current runspace - all other contexts neither require nor support it. (@mklement0)

[Invoke-Command, Start-Job, and InlineScript are known contexts which support the use of $using: to pass variables in current local session.]

Documentation on where you can use $using

Jawad
  • 11,028
  • 3
  • 24
  • 37
  • Okay, so it needs the `-ComputerName` or `-Session` arguments and it will work even locally. That makes sense, but doesn't explain why it works with a local `Start-Job` without specifying the `-ComputerName`. – codewario Jan 31 '20 at 20:22
  • @BendertheGreatest Updated the post. Limitation of `$using` exists in `Invoke-Command` only where it requires -ComputerName or -Session arguments. Start-Job or InlineScripts dont have this limitation. – Jawad Jan 31 '20 at 20:33
  • @BendertheGreatest, if you use `-ComputerName` to target the _local_ computer, the command still goes through PowerShell's remoting infrastructure, which has serious performance and type-fidelity implications. Doing so really only makes sense to simulate true remoting for _testing_. – mklement0 Feb 01 '20 at 00:20
1

Note: This answer doesn't cover PowerShell workflows, because they are obsolescent technology no longer supported in PowerShell [Core] v6+ - see this blog post.

  • For anything that executes out-of-runspace, i.e. doesn't execute directly in the caller's runspace (thread), you need the $using: scope in order to embed variable values[1] from the caller's scope, so that the out-of-runspace code can access it.

  • Conversely, all other contexts neither require nor support $using:.

    • This includes local Invoke-Command calls, such as yours, (due to the absence of a -ComputerName or a -Session argument); however, such calls are rarely necessary (see below).

Overview of out-of-runspace contexts:

  • Remotely executed commands, started with Invoke-Command's -ComputerName parameter.

    • Runspace-local use of Invoke-Command - which is what happens without -ComputerName or -Session - neither requires nor supports $using: references (it runs in a child scope of the caller's scope, or, with -NoNewScope, directly in the caller's scope).

      • Runspace-local use of Invoke-Command is rarely necessary, because the &, the call (execute) operator (execution in a child scope), and ., the (dot-)source operator (execution directly in the caller's scope), are more concise and efficient alternatives.
    • Note that if you use the -ComputerName parameter to target the local computer, the command is still treated as if it were a remote execution, i.e., it goes through PowerShell's remoting infrastructure, and the same rules as for true remote execution apply.

  • Background jobs, started with Start-Job

  • Thread jobs, started via Start-ThreadJob.

    • In PowerShell [Core] v7+, this also includes script blocks passed to
      ForEach-Object with the -Parallel switch.

Remotely executed commands and background jobs run out of process[2], and for values to cross these process boundaries they undergo XML-based serialization and deserialization, which typically involves loss of type fidelity - both on input and output.

  • See this answer for background information.

  • Note that this doesn't just apply to values embedded via $using:, but also to values passed as arguments via the -ArgumentList (-Args) parameter to Invoke-Command [-ComputerName] and Start-Job.

Thread jobs, by contrast, because they run in a different runspace (thread) in the same process, receive $using: variable values as their original, live objects and, similarly, return such objects.

  • The caveat is that explicit synchronization across runspaces (threads) may be needed, if they all access a given, mutable reference-type instance - which is most likely to happen with ForEach-Object -Parallel.

  • Generally, though, thread jobs are the better alternative to background jobs in most cases, due to their significantly better performance, lower resource use, and type fidelity.


[1] Note that this means that out-of-runspace code can never modify variables in the caller's scope. However, in the case of thread jobs (but not during remoting and not in background jobs), if the variable value happens to be an instance of a reference type (e.g., a collection type), it is possible to modify that instance in another thread, which requires synchronizing the modifications across threads, should multiple threads perform modifications.

[2] Unlike remote commands, background jobs run on the same computer, but in a (hidden) PowerShell child process.

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

You don't need the using if it's not remote:

invoke-command { ping $myservername } 

Note that you have to be admin to invoke on localhost.

js2010
  • 23,033
  • 6
  • 64
  • 66