Errors from thread jobs written to the error stream by Receive-Job
show the PositionMessage
for the originating error.
Consider, for example
$job =
Start-ThreadJob {
1/0 # error line
}
$job | Wait-Job | Out-Null
$job | Receive-Job
which 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 Cmdlet
like Receive-Job
does. However, writing the error with Cmdlet.WriteError()
shows the position of the Cmdlet
not 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-JobFancy
outputs
Line |
22 | $job | Receive-JobFancy
| ~~~~~~~~~~~~~~~~
| Attempted to divide by zero.
Showing all the errors in $job
with the same receiving command's position makes diagnostics more difficult. Get-Error
does reveal the ErrorRecord
with the original error line at $.Exception.ErrorRecord
. But using Get-Error
to see all errors from a job is awkward. I'd prefer to mimick the behavior of Receive-Job
so that the original error is shown.
How can I write an error from a job so that it shows the original position message like Receive-Job
does?
Errors from thread jobs written to the error stream by Receive-Job
show the PositionMessage
for the originating error.
Consider, for example
$job =
Start-ThreadJob {
1/0 # error line
}
$job | Wait-Job | Out-Null
$job | Receive-Job
which 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 Cmdlet
like Receive-Job
does. However, writing the error with Cmdlet.WriteError()
shows the position of the Cmdlet
not 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-JobFancy
outputs
Line |
22 | $job | Receive-JobFancy
| ~~~~~~~~~~~~~~~~
| Attempted to divide by zero.
Showing all the errors in $job
with the same receiving command's position makes diagnostics more difficult. Get-Error
does reveal the ErrorRecord
with the original error line at $.Exception.ErrorRecord
. But using Get-Error
to see all errors from a job is awkward. I'd prefer to mimick the behavior of Receive-Job
so that the original error is shown.
How can I write an error from a job so that it shows the original position message like Receive-Job
does?
1 Answer
Reset to default 3First, a notable mention, using your original cmdlet, you can still see where in the scriptblock the error originated by looking at the ScriptStackTrace
property:
try {
Start-ThreadJob { 1 / 0 } |
Wait-Job |
Receive-JobFancy -ErrorAction Stop
}
catch {
$_.ScriptStackTrace # at <ScriptBlock>, <No file>: line 1
}
Now, to answer the question, they use a property you have no access to, see ReceiveJob.cs#L815-L816
. So if you use reflection you get the same result (wouldn't advise it...):
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);
}
}
}
If you want to create a setter delegate you can also do:
typeof(ErrorRecord).GetProperty(
"PreserveInvocationInfoOnce",
BindingFlags.NonPublic | BindingFlags.Instance)?
.GetSetMethod(true)?
.CreateDelegate<Action<ErrorRecord, bool>>();
See also in ErrorPackage.cs#L1576-L1577
:
// 2005/07/14-913791 "write-error output is confusing and misleading"
internal bool PreserveInvocationInfoOnce { get; set; }
Not sure what 913791
is, but if it is a GitHub issue you might find more details about this property.