$Using:var in Start-Job within Invoke-Command

$Using:var in Start-Job within Invoke-Command

我正在使用 Invoke-Command,在 -ScriptBlock 中我正在使用 Start-Job。我必须在 Start-Job 中使用 $Using:var,但会话正在查找本地会话中声明的变量(在 Invoke-Command 之前声明)。这是我正在做的一个非常简短的例子:

Invoke-Command -ComputerName $computer -ScriptBlock {
    $sourcePath = 'C:\Source'
    $destPath = 'C:\dest.zip'
    $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
    $includeBaseDirectory = $false
    Start-Job -Name "compress_archive" -ScriptBlock {
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::CreateFromDirectory("$using:sourcePath","$using:destPathTemp",$using:compressionLevel,$using:includeBaseDirectory)
    }
}

Invoke-Command : The value of the using variable '$using:sourcePath' cannot be retrieved because it has not been set in the local session.
At line:1 char:1
+ Invoke-Command -ComputerName vode-fbtest -ScriptBlock {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Invoke-Command], RuntimeException
    + FullyQualifiedErrorId : UsingVariableIsUndefined,Microsoft.PowerShell.Commands.InvokeCommandCommand

如果我在 Start-Job -ScriptBlock {} 中调用变量时省略 $using,那么我会收到 Cannot find an overload for "CreateFromDirectory" and the argument count: "4". 错误,因为变量未在该范围内定义。

有没有一种方法可以在 远程 会话中使用 $using 作为变量而不是本地会话,或者我可以指定的另一个范围可以从中获取变量远程会话?我可以在 Invoke-Command 之前在本地声明这些变量来解决这个问题,但是由于变量包含动态值(所有这些都在 foreach ($obj in $objects) 中,因此需要大量工作在远程计算机上检索到,所以如果我无法完成这项工作,我将需要重组整个脚本。

我在 Windows Server 2012 R2(源主机和调用命令的 -ComputerName 主机)上使用 PS v5.1,如果有任何区别的话。

查看 this answer 我发现您可以将变量公开给较低级别​​的脚本块,但我需要从远程会话中实际声明变量。该值需要来自远程会话所在的计算机 运行。您能否在远程会话中以一种使其可用于顶级脚本块中的脚本块的方式声明变量?

PetSerAl,像以前无数次一样,在对问题的简短评论中提供了关键指针:

您需要:

  • 使用 [scriptblock]::Create() 创建要从字符串

    Start-Job 动态传递给 的脚本块
  • Invoke-Command 脚本块 中进行 [scriptblock]::Create() 调用 ,因为只有这样才能确保声明的变量 其中 是在 [scriptblock]::Create() 创建的脚本块中通过 $using: 范围说明符引用的。

    • 相比之下,如果您使用 脚本块文字 { ... }Start-Job,就像您的尝试一样,$using:引用 不是 引用 Invoke-Command 脚本块的范围,而是 Invoke-Command 的调用者的范围,即对进行整体 Invoke-Command 调用的代码可见的变量。
    • 理想情况下,$using:... 引用的扩展将足够智能以处理 嵌套 范围,如本例所示,但 PowerShell Core 的情况并非如此7.0.0-preview.3.

警告:正如 PetSerAl 指出的那样,如果您将 Invoke-Command 命令范围的临时会话 (通过使用 -ComputerName) 隐含 - 而不是使用 New-PSSession 之前创建并通过 -Session 传递给 Invoke-Command 的寿命更长的 session -当 Invoke-Command 调用 returns 时,后台作业 终止 ,然后(可能)有机会 完成 。虽然您可以通过管道将 Start-Job 调用传递给 ... | Receive-Job -Wait -AutoRemove,但只有在您开始 多个 作业时才值得这样做。

因此:

Invoke-Command -ComputerName $computer -ScriptBlock {

    # Inside this remotely executing script block, define the variables
    # that the script block passed to Start-Job below will reference:
    $sourcePath = 'C:\Source'
    $destPath = 'C:\dest.zip'
    $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
    $includeBaseDirectory = $false

    # Define the Start-Job script block as *literal* (here-)*string*, so as 
    # to defer interpretation of the $using: references, and then
    # construct a script block from it using [scriptblock]::Create(), which
    # ties the $using: references to *this* scope.
    $jobSb = [scriptblock]::Create(
@'
        Add-Type -AssemblyName System.IO.Compression.FileSystem
        [System.IO.Compression.ZipFile]::CreateFromDirectory("$using:sourcePath","$using:destPathTemp",$using:compressionLevel,$using:includeBaseDirectory)
'@
    )

    Start-Job -Name "compress_archive" -ScriptBlock $jobSb

}