最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - In a compiled Cmdlet, how can I write an error from a thread job without burying the PositionMessage? - Stack Overflow

programmeradmin1浏览0评论

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?

Share Improve this question edited Feb 7 at 20:58 Santiago Squarzon 60.1k5 gold badges23 silver badges51 bronze badges asked Feb 7 at 20:36 alx9ralx9r 4,2416 gold badges32 silver badges56 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 3

First, 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.

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论