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?
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.
{ ... }
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.$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
}