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.
# 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
Both tests should succeed.
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.
PowerShell Core 7.0.0-preview.5
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:
Most helpful comment
You were on the right track, @vexx32, all that was missing was
true
:This uses this
internal
PSObject.ASPSObject()
overload, which gives the resultingPSObject
instance a wrapper-local ETS type-name list not tied to the original object.Select-Object -ExpandProperty
apparently uses the same overload - obscurely.