5

Thanks to the PowerShell expression mode, PowerShell has some nice ways to de-serialize objects, like:

My general expectation is that the result of an given expression should be the same as using one of above listed de-serialize commands on same serialized version of that expression (for background, see the ConvertTo-Expression answer on the 'Save hash table in PowerShell object notation (PSON)' question).
In other words:

<Expression> <=> Invoke-Command {<Expression>} <=> &([ScriptBlock]::Create('<Expression>'))
<Expression> <=> Invoke-Expression '<Expression>'

Examples:

Get-ChildItem <=> &{Get-ChildItem}
Get-ChildItem <=> Invoke-Command {Get-ChildItem}
Get-ChildItem <=> Invoke-Expression 'Get-ChildItem'

1, 2, 3 <=> &{1, 2, 3}
1, 2, 3 <=> Invoke-Command {1, 2, 3}
1, 2, 3 <=> Invoke-Expression '1, 2, 3'

This indeed appears true for mainly every expression, but due to the fact that PowerShell unrolls (enumerates) the output by default, this definition deviates in the case an expression contain an array with a single item:

,1 <≠> Invoke-Command {,1}
,1 <≠> Invoke-Expression ',1'
,"Test" <≠> Invoke-Command {,"Test"}
,"Test" <≠> Invoke-Expression ',"Test"'
@("Test") <≠> Invoke-Command {@("Test")}
@("Test") <≠> Invoke-Expression '@("Test")'
,@("Test") <≠> Invoke-Command {,@("Test")}
,@("Test") <≠> Invoke-Expression ',@("Test")'

Is there a way to prevent that expressions get unrolled when the are invoked (de-serialized) in anyway?

I am considering to request for a -NoEnumerate parameter (similar to the Write-Output cmdlet) for the Invoke-Expression on the PowerShell GitHub, but that will still leave the issue/question for the call operator and dot sourcing that do not support parameters...

iRon
  • 20,463
  • 10
  • 53
  • 79
  • I know powershell flattens expressions in some cases. I don't think serializing is the right word. That's for returning objects from a remote computer. A serialized object wouldn't have methods. – js2010 Jun 30 '19 at 13:41
  • The only way I can find: `$a = @(&{,1})` – js2010 Jun 30 '19 at 13:53
  • @js2010, with regards to ["serialization"](https://stackoverflow.com/questions/2170686/what-is-a-serialized-object-in-programming), all three commands listed commands: the `Invoke-Expression`, the call operator (`&([ScriptBlock]::Create(''))`) and dot sourcing are accepting plain text as input describing an object (which might -or not- come from a remote computer)... – iRon Jun 30 '19 at 14:11
  • @js2010, which regards to `$a = @(&{,1})`, this works for (single valued) arrays, but breaks the original definition for anything that is not an array (such as` valuedtypes` and `hashtables`) as e.g. `'Test' <≠> @(&{'Test'})` – iRon Jun 30 '19 at 14:18

1 Answers1

0

I found a workaround to prevent the default unrolling of (serialized) expressions:
Wrap the expression in a HashTable:

<Expression> <=> (Invoke-Command {@{e=<Expression>}})['e']
<Expression> <=> (Invoke-Expression '@{e=<Expression>}')['e']

Examples:

Get-ChildItem <=> (Invoke-Command {@{e=Get-ChildItem}})['e']
Get-ChildItem <=> (Invoke-Expression '@{e=Get-ChildItem}')['e']

1, 2, 3 <=> (Invoke-Command {@{e=1, 2, 3}})['e']
1, 2, 3 <=> (Invoke-Expression '@{e=1, 2, 3}')['e']

,1 <=> (Invoke-Command {@{e=,1}})['e']
,1 <=> (Invoke-Expression '@{e=,1}')['e']

I have further implemented this in a ConvertFrom-Expression cmdlet, which the following features:

  • -NoEnumerate switch to prevent arrays with a single item to unroll
  • -NoNewScope switch similar to Invoke-Command
  • Multiple ScriptBlock and/or String items via the pipeline or the -Expression argument

ConvertFrom-Expression Examples:

PS C:>'2*3', {3*4}, '"Test"' | ConvertFrom-Expression
6
12
Test

 

PS C:> (ConvertFrom-Expression ',"Test"' -NoEnumerate) -Is [Array]
True
iRon
  • 20,463
  • 10
  • 53
  • 79