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

梦想与她 提交于 2020-07-19 04:52:32

问题


I am using Invoke-Command, and within the -ScriptBlock I am using Start-Job. I have to use $Using:var within Start-Job but the session is looking for the declared variables in the local session (declared before Invoke-Command). Here's a very brief example of what I'm doing:

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

If I omit $using when calling variables in the Start-Job -ScriptBlock {} then I get a Cannot find an overload for "CreateFromDirectory" and the argument count: "4". error because the variables are not defined in that scope.

Is there a way to use $using for variables within the remote session rather than the local one, or possibly another scope I can specify that would source variables from the remote session? I could declare these variables locally before the Invoke-Command to fix this but that would require a significant bit of work due to the variables containing dynamic values (all of this is in a foreach ($obj in $objects), the data for which is retrieved on the remote computer so I would need to restructure the whole script if I can't make this work).

I'm using PS v5.1 on Windows Server 2012 R2 (both source host and -ComputerName host on which the command is invoked) if that makes any difference.

Looking at this answer I see that you can expose variables to lower level script blocks but I need to actually declare the variable from within the remote session. The value needs to come from the computer on which the remote session is running. Can you declare the variable from within the remote session in a fashion that makes it available to script blocks within the top-level script block?


回答1:


PetSerAl, as countless times before, has provided the crucial pointer in a terse comment on the question:

You need to:

  • use [scriptblock]::Create() to create the script block to pass to Start-Job dynamically, from a string

  • make the [scriptblock]::Create() call inside the Invoke-Command script block, because only that ensures that the variables declared in there are the ones referenced in the [scriptblock]::Create()-created script block via the $using: scope specifier.

    • By contrast, if you use a script-block literal, { ... } with Start-Job, as in your attempt, the $using: references do not refer to the Invoke-Command script block's scope, but to the scope of the caller of Invoke-Command, i.e. to the variables visible to the code that makes the overall Invoke-Command call.
    • Ideally, the expansion of $using:... references would be smart enough to handle nested scopes, as in this case, but that is not the case as of PowerShell Core 7.0.0-preview.3.

Caveat: As PetSerAl points out, if you use Invoke-Command with a command-scoped ad-hoc session (implied by using -ComputerName) - rather than a longer-lived session created prior with New-PSSession and passed to Invoke-Command with -Session - the background job gets terminated when the Invoke-Command call returns, before it (likely) has a chance to finish. While you could pipe the Start-Job call to ... | Receive-Job -Wait -AutoRemove, that would only be worth it if you started multiple jobs.

Therefore:

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

}


来源:https://stackoverflow.com/questions/57700435/usingvar-in-start-job-within-invoke-command

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!