-
Notifications
You must be signed in to change notification settings - Fork 8.2k
Description
Currently and historically, [scriptblock] instances are deserialized as [string] rather than as type [scriptblock]:
This leads to surprising behavior:
# FAILS, because the script block deserializes to a string parameter
PS> pwsh -nop -c { param($sb) & $sb } -args { 'hi' }
&: The term ' 'hi' ' is not recognized as the name of a cmdlet, function, script file, or operable program.# FAILS, because the background job sees the script block as a string after deserialization.
PS> $sb = { 'hi' }; (Measure-Command $using:sb &) | Receive-Job -Wait -AutoRemoveJob
Measure-Command: Cannot bind parameter 'Expression'. Cannot convert the " 'hi' " value of type "System.String" to type "System.Management.Automation.ScriptBlock".The serialization part already contains all the required information - e.g. the above script block serializes as XML text <SBK> 'hi' </SBK>, but, surprisingly, during deserialization [scriptblock]::Create() is not called on this string.
Note: This simple plain-text serialization of the script block's content means that the $using:-scope feature for embedding values from the caller's scope doesn't work - references such as $using:foo are serialized as-is and would fail on execution of the recreated script block in the caller's scope.
The historical design rationale for this behavior was given by @BrucePay in #4218 (comment):
Historically this is by design. Serializing scriptblocks with fidelity resulted in too many places where there was automatic code execution so to facilitate secure restricted runspaces, scriptblocks are always deserialized to strings.
To safely avoid this problem and to correct the surprising behavior, it sounds like we'd need to simply make sure that PowerShell-internally no accidental execution (not sure when and why that would happen; simply parsing strings into script blocks seems to be allowed even in restricted runspaces).
From the user's perspective, a script block should deserialize as such, and it is their responsibility, as always, to decide if and when to invoke it.
For old code that worked around the limitation, this change should have little to no impact, given how [scriptblock] instances stringify:
# The following should be equivalent
[scriptblock]::create(" 'foo' ") # convert from string
[scriptblock]::create({ 'foo' }) # redundant conversion from script block, which old code may end up doingHypothetically, old code that tries to access [string] properties on a deserialized script block could break - hopefully, this scenario falls into bucket 3.