Skip to content

Consider deserializing serialized script blocks as such (remoting, background jobs) #11698

@mklement0

Description

@mklement0

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 doing

Hypothetically, old code that tries to access [string] properties on a deserialized script block could break - hopefully, this scenario falls into bucket 3.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Resolution-By DesignThe reported behavior is by design.WG-RemotingPSRP issues with any transport layer

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions