Why does the `using` scope work locally with Start-Job, but not Invoke-Command?
Asked Answered
S

3

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?

Simsar answered 31/1, 2020 at 19:46 Comment(1)
As of this writing, there are several open issues on GitHub to request improvements to the linked help topic, the main one being github.com/MicrosoftDocs/PowerShell-Docs/issues/5068Cantwell
C
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.

Cantwell answered 31/1, 2020 at 23:58 Comment(0)
C
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

Canker answered 31/1, 2020 at 20:9 Comment(3)
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.Simsar
@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.Canker
@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.Cantwell
C
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.

Cantwell answered 31/1, 2020 at 23:58 Comment(0)
C
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.

Comparator answered 31/1, 2020 at 20:21 Comment(1)
More importantly, you don't need Invoke-Command at all for local invocations.Cantwell

© 2022 - 2024 — McMap. All rights reserved.