Powershell: Get-Error unexpectedly modifies the ETS type names of the ErrorRecord instances it processes in place

Created on 19 Nov 2019  路  13Comments  路  Source: PowerShell/PowerShell

The handy new Get-Error cmdlet implements its pretty-printing by replacing the System.Management.Automation.ErrorRecord type name in the ETS type-name array with PSExtendedError.

Unfortunately, it does this on the _originals_ in the $Error collection, resulting in inadvertent modification that makes these instances print differently with implicit output (e.g., $Error[0]) afterwards.

Steps to reproduce

# Provoke a an error.
try { 1 / 0 } catch {}

$Error[0].pstypenames | Should -Be System.Management.Automation.ErrorRecord, System.Object

# Run Get-Error, which accidentally modifies .pstypenames of the most recent error.
$null = Get-Error 

# Repeating the test shows the modified .pstypenames
$Error[0].pstypenames | Should -Be System.Management.Automation.ErrorRecord, System.Object

Expected behavior

Both tests should succeed.

Actual behavior

The 2nd test fails:

 Expected @('System.Management.Automation.ErrorRecord', 'System.Object'), but got @('PSExtendedError', 'System.Object').

That is, $Error[0]'s .pstypename property was accidentally modified.

Environment data

PowerShell Core 7.0.0-preview.5
Area-Cmdlets-Utility Issue-Question Resolution-Fixed

Most helpful comment

You were on the right track, @vexx32, all that was missing was true:

                PSObject obj = PSObject.AsPSObject(errorRecord, true);

This uses this internal PSObject.ASPSObject() overload, which gives the resulting PSObject instance a wrapper-local ETS type-name list not tied to the original object.

Select-Object -ExpandProperty apparently uses the same overload - obscurely.

All 13 comments

This is an unfortunate workaround to how OutOfBand formatting works. Exceptions and ErrorRecords are OutOfBand and take precedence for formatting. This is why you have to use -force with format-list to see the entire contents, but it's also how exceptions/errorrecords can show up in the middle of pipeline output formatting (well, for non-terminating ones). Not sure how to fix this without this side effect since there isn't a generic way to copy a .NET object.

@SteveL-MSFT since this modification to PSTypeNames only needs to go as deep as the ETS, I think you can just call PSObject.Copy() to clone the wrapper and modify that instead of the original PSObject stored in $error.

@vexx32 tried that, didn't work. PSObject.Copy() relies on the base object to implement ICloneable. Note that I was aware of this side effect which is why the formatter puts back the original types. However, if you assign the output to $null, the formatter doesn't even get exercised.

馃 Can you then do var newWrapper = PSObject.AsPSObject(errorRecordPSObject.BaseObject); perhaps? Bit of a run-around I'm sure but hey 馃榿

@vexx32 not sure how that buys us anything? It'll still be a ref to the original

@SteveL-MSFT, note that problem also occurs if you don't assign the output to $null:

try { 1/0 } catch {}; $e = $Error[0]; Get-Error; '----'; $e.pstypenames

An additional problem is that PSExtendedError is apparently invariably inserted _every time_ Get-Error processes a given record:

try { 1/0 } catch {}; $e = $Error[0]; Get-Error; Get-Error; '----'; $e.pstypenames

Note how the output at the end now has _two_ PSExtendedError lines.

@mklement0 duplicate typenames should already be fixed in master (coming in Preview.6). I don't think we can solve the other problem unless we rewrite out OutOfBand formatting works

There is a solution, @SteveL-MSFT, by harnessing obscure behavior in Select-Object -ExpandProperty:

Here's a PoC in PowerShell:

try { 1/0 } catch {}; $e = $Error[0]; $eClone = [pscustomobject] @{ e = $e } | select -ExpandProperty e; $null = Get-Error; $eClone.pstypenames

Note how $eClone retained its separate .pstypenames value.

You were on the right track, @vexx32, all that was missing was true:

                PSObject obj = PSObject.AsPSObject(errorRecord, true);

This uses this internal PSObject.ASPSObject() overload, which gives the resulting PSObject instance a wrapper-local ETS type-name list not tied to the original object.

Select-Object -ExpandProperty apparently uses the same overload - obscurely.

Missed it by _that_ much. Nicely done @mklement0 馃榿

Excellent, I'll try this out.

:tada:This issue was addressed in #11125, which has now been successfully released as v7.0.0-rc.2.:tada:

Handy links:

Was this page helpful?
0 / 5 - 0 ratings