I want to pass a ScriptBlock as an argument into a Start-Job ScriptBlock. I understand that [ScriptBlock]
is serialized to a string for safety when passed into a job. So, I need to deserialize it. I've tried using [ScriptBlock]::Create()
but it doesn't seem to process the $($args[0].cheese)
correctly.
This is a simplied version of what I'm doing.
function Format-Text {
[CmdletBinding()]
param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject,
[Parameter(Mandatory)][ScriptBlock]$Formatter)
Write-Host ($Formatter.Invoke($InputObject))
}
$formatter = {"My favourite cheese is: $($args[0].cheese)"}
$testObject = [PSCustomObject]@{cheese = 'cheddar'}
Format-Text -InputObject $testObject -Formatter $formatter
# stuff before this is just to demonstrate that it works outside of Start-Job
$job = Start-Job -ArgumentList $testObject,$formatter.ToString() -ScriptBlock {
param ([String]$Obj,
[String]$Fmtr)
function Format-Text {
[CmdletBinding()]
param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject,
[Parameter(Mandatory)][ScriptBlock]$Formatter)
Write-Host ($Formatter.Invoke($InputObject))
}
$sb = [ScriptBlock]::Create($Fmtr)
Format-Text -InputObject $Obj -Formatter $sb
}
# clean up
do { Start-Sleep -Milliseconds 500 } until ($job.State -ne 'Running')
Receive-Job $job; Remove-Job $job
The output is:
My favourite cheese is: cheddar
My favourite cheese is:
How can I deserialize the string
such that the $($args[0].cheese)
works?
The example above is pared down to the bone, the real script is 00s of lines and many functions. I don't want to rewrite the function if I can avoid it because it's used in many other locations.
I'm running the built-in PowerShell 5.1.
I want to pass a ScriptBlock as an argument into a Start-Job ScriptBlock. I understand that [ScriptBlock]
is serialized to a string for safety when passed into a job. So, I need to deserialize it. I've tried using [ScriptBlock]::Create()
but it doesn't seem to process the $($args[0].cheese)
correctly.
This is a simplied version of what I'm doing.
function Format-Text {
[CmdletBinding()]
param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject,
[Parameter(Mandatory)][ScriptBlock]$Formatter)
Write-Host ($Formatter.Invoke($InputObject))
}
$formatter = {"My favourite cheese is: $($args[0].cheese)"}
$testObject = [PSCustomObject]@{cheese = 'cheddar'}
Format-Text -InputObject $testObject -Formatter $formatter
# stuff before this is just to demonstrate that it works outside of Start-Job
$job = Start-Job -ArgumentList $testObject,$formatter.ToString() -ScriptBlock {
param ([String]$Obj,
[String]$Fmtr)
function Format-Text {
[CmdletBinding()]
param([Parameter(Mandatory, ValueFromPipeline)][Object]$InputObject,
[Parameter(Mandatory)][ScriptBlock]$Formatter)
Write-Host ($Formatter.Invoke($InputObject))
}
$sb = [ScriptBlock]::Create($Fmtr)
Format-Text -InputObject $Obj -Formatter $sb
}
# clean up
do { Start-Sleep -Milliseconds 500 } until ($job.State -ne 'Running')
Receive-Job $job; Remove-Job $job
The output is:
My favourite cheese is: cheddar
My favourite cheese is:
How can I deserialize the string
such that the $($args[0].cheese)
works?
The example above is pared down to the bone, the real script is 00s of lines and many functions. I don't want to rewrite the function if I can avoid it because it's used in many other locations.
I'm running the built-in PowerShell 5.1.
Share Improve this question asked Feb 15 at 16:17 user2871239user2871239 1,5722 gold badges12 silver badges31 bronze badges 2- This is not Deserializtion. A Scriptblock is a bunch of powershell commands. I think you want to pass a powershell object into the function. Not a scriptblock. – jdweng Commented Feb 15 at 16:46
- 1 @jdweng, using a background job of necessity involves (serialization and) deserialization. The explicit stated goal, which is perfectly appropriate for the use case, is to pass a script block to the background job, which requires manual deserialization of the string that PowerShell's own deserialization turns the script block into (for dubious reasons). Thus, it's not clear what your comment is trying to tell us; please consider deleting it. – mklement0 Commented Feb 15 at 17:56
1 Answer
Reset to default 1Recreating your script block from a string using
[scriptblock]::Create()
works as intended.As an aside: the alleged security considerations that resulted in choosing to deserialize script blocks as strings have never been spelled out. Personally, I don't see any risk associated with deserializing script blocks as such, given that construction of a script block does not result in its execution.
See GitHub issue #11698 for a discussion.
Your only problem is the accidental typing of your
-Object
parameter as[String]$Obj
, whereas it should be[object] $Obj
(or simply$Obj
).A
[string]
object obviously doesn't have a.cheese
property, and, by default, PowerShell quietly evaluates attempts to access non-existent properties as$null
, which in the context of string expansion (interpolation) evaluates to the empty string (which is what you saw).You could have caught this problem via
Set-StrictMode
-Version 2
or higher, which reports attempts to access non-existent properties as errors, though note the additional constraints imposed by these modes.