Note:
- The following doesn't just apply to the child-process-based jobs created by
Start-Job
, but analogously also to the generally preferable thread-based jobs created with Start-ThreadJob
[1] and the thread-based parallelism available in PowerShell (Core) 7+ with ForEach-Object
-Parallel
, as well as to PowerShell remoting via Invoke-Command
- in short: it applies to any scenario in which PowerShell executes out-of-runspace (in a different runspace).
PowerShell's background jobs run in an out-of-process runspace (a hidden child process) and therefore share no state with the caller.
Therefore, definitions created by the caller in-session - such as functions and class
definitions - are not visible to background jobs.
Short of providing these definitions via script files (*.ps1
) or modules that the job would have to dot-source (.
) or import, you'll have to duplicate the definitions of interest in the job's script block.
Note: Start-Job
(as well as well as Start-ThreadJob
) optionally supports passing a script block to its -InitializationScript
parameter, which allows initialization that precedes execution of the script block passed to -ScriptBlock
. While this can be a helpful separation for maintenance purposes, it is currently (Windows PowerShell and PowerShell (Core) v7.3.6) severely hampered by a bug: $using:
references aren't supported in -InitializationScript
script blocks, which makes it impossible to reference values and definitions from the caller's scope.
- See GitHub issue #4530; the fact that the bug report was created in 2017 suggests that fixing the bug has low priority.
While you can reduce the duplication in the case of functions by referencing their bodies via the $using:scope
- see this answer and the last snippet of sample code below - there is no analog for classes.
However, you can avoid having to duplicate the class
definition in the context of a job via a helper script block, as follows:
First, a simplified, self-contained example that shows the class
redefinition only:
# Wrap the class definition in a helper script block ({ ... }).
$classDefScriptBlock = {
class Test {
[String]$ComputerName
[String]TestMrParameter() {
if ($this.ComputerName -eq 'jhon'){throw "Bad thing happened"}
return $this.ComputerName
}
}
}
# Define the class in the current scope by dot-sourcing the script block.
. $classDefScriptBlock
Start-ThreadJob -ScriptBlock {
# Recreate the script block and dot-source it in the job's scope.
. ([scriptblock]::Create("$using:classDefScriptBlock"))
# Now you can use the [Test] class:
"The class name is: " + [Test].FullName
} |
Receive-Job -Wait -AutoRemoveJob
The above outputs The class name is: Test
, as intended, which implies that the class
was successfully (re)defined in the context of the job.
Finally, a self-contained example that shows the both class
and function
redefinitions:
# Wrap the class definition in a helper script block ({ ... }).
$classDefScriptBlock = {
class Test {
[String]$ComputerName
[String]TestMrParameter() {
if ($this.ComputerName -eq 'jhon'){throw "Bad thing happened"}
return $this.ComputerName
}
}
}
# Define the class in the current scope by dot-sourcing the script block.
. $classDefScriptBlock
# Define a simplified function that uses the [Test] class.
# Unlike with classes, no extra effort is required on the caller's
# side in order to be able to redefine the function in the job's context.
function TestMrParameter2 {
param($name)
"Return value from class method: " + ([Test] @{ ComputerName = $name}).TestMrParameter()
}
Start-ThreadJob -ScriptBlock {
param (
[string]$name
)
# Recreate the script block and dot-source it in the job's scope.
. ([scriptblock]::Create("$using:classDefScriptBlock"))
# Recreate the TestMrParameter2 function via a $using: reference
# and namespace variable notation.
$function:TestMrParameter2 = "$using:function:TestMrParameter2"
# Now you can call the TestMrParameter2 function.
TestMrParameter2 -Name $name
} -ArgumentList 'computer1' |
Receive-Job -Wait -AutoRemoveJob
The above outputs Return value from class method: computer1
, as intended, which implies that both the class
and the function
were successfully (re)defined in the context of the job.
[1] Start-ThreadJob
is a lightweight, much faster thread-based alternative to the child-process-based regular background jobs created with Start-Job
.
It comes with PowerShell (Core) 7+ and in Windows PowerShell can be installed on demand with, e.g., Install-Module ThreadJob -Scope CurrentUser
.
In most cases, thread jobs are the better choice, both for performance and type fidelity - see the bottom section of this answer for why.