PowerShell / PowerShell/PowerShell
Provide a public way of preserving ErrorRecord.InvocationInfo when writing errors from jobs
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 55.5k
- Forks
- 8.5k
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 88
Description
Summary of the new feature / enhancement
As an author of compiled Cmdlets that manage jobs I would like the ability to show errors from jobs that accurately show the position in user code where that error originated.
The problem is set out in detail this stackoverflow question:
Errors from thread jobs written to the error stream by
Receive-Jobshow thePositionMessagefor the originating error.Consider, for example
$job = Start-ThreadJob { 1/0 # error line } $job | Wait-Job | Out-Null $job | Receive-Jobwhich outputs
RuntimeException: Line | 2 | 1/0 # error line | ~~~ | Attempted to divide by zero.Note that the original error line is shown. The line number output is incorrect (which is a different matter altogether) but at least the impugned code shown in the position message is correct.
I also would like to write errors from a thread job from my own compiled
CmdletlikeReceive-Jobdoes. However, writing the error withCmdlet.WriteError()shows the position of theCmdletnot the position of the original error:Add-Type @' using System.Management.Automation; [Cmdlet(VerbsCommunications.Receive,"JobFancy")] public class ReceiveJobFancy : PSCmdlet { [Parameter(Mandatory = true,ValueFromPipeline = true)] public Job Job {get; set;} protected override void ProcessRecord() { foreach (var e in Job.Error) WriteError(e); } } '@ -PassThru | % Assembly | Import-Module $job = Start-ThreadJob { 1/0 # error line } $job | Wait-Job | Out-Null $job | Receive-JobFancyoutputs
Line | 22 | $job | Receive-JobFancy | ~~~~~~~~~~~~~~~~ | Attempted to divide by zero.Showing all the errors in
$jobwith the same receiving command's position makes diagnostics more difficult.Get-Errordoes reveal theErrorRecordwith the original error line at$.Exception.ErrorRecord. But usingGet-Errorto see all errors from a job is awkward. I'd prefer to mimick the behavior ofReceive-Jobso that the original error is shown.
Santiago Squarzon determined in his answer to that question that Receive-Job accomplishes this with the line
e.PreserveInvocationInfoOnce = true;
PreserveInvocationInfoOnce is, unfortunately, internal:
internal bool PreserveInvocationInfoOnce { get; set; }
But setting PreserveInvocationInfoOnce indeed achieves the desired result.
demo code
Adapted from this answer
Add-Type @'
using System.Management.Automation;
using System.Reflection;
[Cmdlet(VerbsCommunications.Receive, "JobFancy")]
public class ReceiveJobFancy : PSCmdlet
{
private PropertyInfo _propertyInfo;
[Parameter(Mandatory = true, ValueFromPipeline = true)]
public Job Job { get; set; }
protected override void BeginProcessing()
{
_propertyInfo = typeof(ErrorRecord).GetProperty(
"PreserveInvocationInfoOnce",
BindingFlags.NonPublic | BindingFlags.Instance);
}
protected override void ProcessRecord()
{
foreach (var e in Job.Error)
{
_propertyInfo.SetValue(e, true);
WriteError(e);
}
}
}
'@ -PassThru |
% Assembly |
Import-Module
$job =
Start-ThreadJob {
1/0 # error line
}
$job | Wait-Job | Out-Null
$job | Receive-JobFancy
outputs
RuntimeException:
Line |
2 | 1/0 # error line
| ~~~
| Attempted to divide by zero.
In other words, this seems to already be possible with a private API. I don't see a way of preserving the position message of the ErrorRecord the goal without accessing this private API, though.
Enabling community workarounds to issues affecting Receive-Job, ForEach-Object -Parallel etc
For what its worth, this relatively small change to the PowerShell project itself, would enable community implementations of commands that write errors from jobs to the pipeline. Receive-Job and ForEach-Object -Parallel are the obvious such commands. I have reimplemented each of those to workaround a variety of their shortcomings. Those commands lie at the intersection of a substantial variety of concerns, so it's no surprise that their original implementations resulted in some shortcomings. (#24575, #21332, #5433, #21549, #24901, and #24016 are just a few of issues I have personally worked around in my own implementations.) Those workarounds, however, each ultimate rely on using the non-public API PreserveInvocationInfoOnce to avert obscuring the location of errors in user code arising within jobs. I'm reluctant to publish or promote workarounds that rely on private APIs and I have seen that other command authors are reluctant as well. Providing a public API as requested here, would enable many of those workarounds to be implemented and validated in community modules.
Proposed technical implementation details
The obvious solution to this is to change PreserveInvocationInfoOnce from internal to public.
Another option would be to provide an overload of Cmdlet.WriteError that accepts an argument corresponding to PreserveInvocationInfoOnce:
public void WriteError (System.Management.Automation.ErrorRecord errorRecord, bool preserveInvocationInfo);
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading ErrorPackage.cs at PreserveInvocationInfoOnce and ReceiveJob.cs at the existing use described in the issue. Compare the proposed public property with a Cmdlet.WriteError overload, then inspect nearby error-handling behavior. Done means compiled cmdlets can preserve a job error's original invocation position without private reflection, with the existing Receive-Job behavior retained.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, powershell
- Domain
- backend, cli
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100