为什么 `using` 作用域可以在本地使用 Start-Job 而不是 Invoke-Command? [英] Why does the `using` scope work locally with Start-Job, but not Invoke-Command?

查看:67
本文介绍了为什么 `using` 作用域可以在本地使用 Start-Job 而不是 Invoke-Command?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么在本地使用 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 一起工作?

解决方案

注意:此答案不涵盖 PowerShell 工作流,因为它们是不再支持的过时技术PowerShell [核心] v6+ - 参见 这篇博文.

对于在运行空间外执行的任何内容,即不在调用者的运行空间中直接执行(线程),您需要 $using: 范围为了嵌入变量[1]来自调用者的范围,以便运行空间外的代码可以访问它.(相反,所有其他上下文既不需要也不支持 $using:)

注意:在撰写本文时,链接的帮助主题缺少此答案总结的重要方面.已经打开了几个 GitHub 问题来解决这个问题.

具体来说,这包括以下上下文:

  • 远程执行的命令,从 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 开关.
<小时>

远程执行的命令和后台作业运行进程外[2],并且对于跨越这些进程边界的值,它们经历基于 XML 的序列化和反序列化,这通常涉及类型保真度损失 - 包括输入和输出.

  • 有关背景信息,请参阅此答案.

  • 请注意,这不仅适用于通过 $using: 嵌入的值,还适用于通过 -ArgumentList 作为 arguments 传递的值 (-Args) 参数给 Invoke-Command [-ComputerName]Start-Job.

线程作业,相比之下,因为它们运行在不同的运行空间(线程)中在同一个进程接收$using:变量值作为其原始的、活动的对象,并类似地返回这些对象.

  • 警告可能需要跨运行空​​间(线程)的显式同步,如果它们都访问给定的可变引用类型实例 - 即ForEach-Object -Parallel 最有可能发生.

  • 一般来说,线程作业在大多数情况下是后台作业的更好替代方案,因为它们具有明显更好的性能、更低的资源使用和类型保真度.

<小时>

[1] 请注意,这意味着运行空间外的代码永远不能修改调用者范围内的变量.但是,在线程作业的情况下(但不是在远程处理期间,也不是在后台作业中),如果变量 value 恰好是一个引用类型的实例(例如,集合类型),如果多个线程执行修改,则可以在另一个线程中修改该实例,这需要跨线程同步修改.>

[2] 与远程命令不同,后台作业在同一台计算机上运行,但在(隐藏的)PowerShell 子进程中运行.

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?

解决方案

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

Note: As of this writing, the linked help topic is missing important aspects of what this answer summarizes. Several GitHub issues have been opened to get this rectified.

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

这篇关于为什么 `using` 作用域可以在本地使用 Start-Job 而不是 Invoke-Command?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆