问题描述
为什么在本地使用 Invoke-Command
时 PowerShell 不允许使用 using
范围?根据 文档,using
修饰符只能用于远程命令.引用:
从 PowerShell 3.0 开始,您可以使用 Using scope 修饰符来标识远程命令中的局部变量.
在本地运行 Invoke-Command
时可以演示此行为:
$myServerName = 'www.google.com'调用命令 { ping $using:myServerName }
抛出以下错误:
无法检索 Using 变量.Using 变量只能与脚本工作流中的 Invoke-Command、Start-Job 或 InlineScript 一起使用.当它与 Invoke-Command 一起使用时,仅当在远程计算机上调用脚本块时,使用变量才有效.
错误说明using
修饰符的远程使用只能远程有效,使用Invoke-Command
.那么,如果我们尝试使用 Start-Job
运行相同的东西,会发生什么?
$myServerName = 'www.google.com'$j = 开始作业 { ping $using:myServerName }while( $j.State -eq 'Running' ){ Start-Sleep -s 1 }接收作业 $j
这不会引发错误,我得到了我期望的输出:
用 32 字节数据 Ping www.google.com [172.217.6.132]:来自 172.217.6.132 的回复:bytes=32 time=20ms TTL=56来自 172.217.6.132 的回复:bytes=32 time=19ms TTL=56来自 172.217.6.132 的回复:bytes=32 time=19ms TTL=56来自 172.217.6.132 的回复:bytes=32 time=19ms TTL=56
为什么文档声明 using
范围修饰符只能在可以在本地上下文中明确使用的情况下远程工作?同样,如果它在本地 Start-Job
的上下文中工作,是什么阻止它与本地 Invoke-Command
一起工作?
对于在运行空间外执行的任何内容,即不在调用者的运行空间中直接执行(线程),您需要 $using:
范围为了嵌入变量值来自调用者的范围,以便运行空间外的代码可以访问它.(相反,所有其他上下文既不需要也不支持 $using:
)
具体来说,这包括以下上下文:
远程执行的命令,从
Invoke-Command
的-ComputerName
参数.Runspace-local 使用
Invoke-Command
- 这就是 没有-ComputerName
- 既不需要也不支持$using:
引用(它在调用者范围的 子范围 中运行,或者,使用-NoNewScope
,直接在调用者的范围).- 运行空间本地使用
Invoke-Command
很少有必要,因为&
、调用(执行)运算符(在子作用域中执行)和.
,(dot-)source operator(直接在调用者范围内执行),是更简洁、更有效的替代方法.
- 运行空间本地使用
请注意,如果您使用
-ComputerName
参数来定位本地 计算机,该命令仍被视为远程执行,即,它通过 PowerShell 的远程处理基础设施,并适用与真正远程执行相同的规则.
后台作业,从
开始工作
线程作业,通过
Start-ThreadJob
.- 在 PowerShell [Core] v7+ 中,这还包括传递给
的脚本块ForEach-Object
带有-Parallel
开关.
- 在 PowerShell [Core] v7+ 中,这还包括传递给
远程执行的命令和后台作业运行进程外,并且对于跨越这些进程边界的值,它们经历基于 XML 的序列化和反序列化,这通常涉及类型保真度损失 - 包括输入和输出.
有关背景信息,请参阅此答案.
请注意,这不仅适用于通过
$using:
嵌入的值,还适用于通过-ArgumentList 作为 arguments 传递的值
(-Args
) 参数给Invoke-Command [-ComputerName]
和Start-Job
.
线程作业,相比之下,因为它们运行在不同的运行空间(线程)中在同一个进程,接收$using:代码>变量值作为其原始的、活动的对象
,并类似地返回这些对象.
警告是可能需要跨运行空间(线程)的显式同步,如果它们都访问给定的可变引用类型实例 - 即
ForEach-Object -Parallel
最有可能发生.一般来说,线程作业在大多数情况下是后台作业的更好替代方案,因为它们具有明显更好的性能、更低的资源使用和类型保真度.
>
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:
This behavior can be demonstrated when running Invoke-Command
locally:
$myServerName = 'www.google.com'
Invoke-Command { ping $using:myServerName }
Which throws the following error:
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
?
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 from the caller's scope, so that the out-of-runspace code can access it. (Conversely, all other contexts neither require nor support $using:
)
Specifically, this includes the following contexts:
Remotely executed commands, started with
Invoke-Command
's-ComputerName
parameter.Runspace-local use of
Invoke-Command
- which is what happens without-ComputerName
- 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.
- Runspace-local use of
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.
- In PowerShell [Core] v7+, this also includes script blocks passed to
Remotely executed commands and background jobs run out of process, 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 toInvoke-Command [-ComputerName]
andStart-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.
这篇关于为什么 `using` 作用域可以在本地使用 Start-Job 而不是 Invoke-Command?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!