从事件处理程序报告 Powershell 进度

本文关键字:Powershell 进度 报告 程序 事件处理 | 更新日期: 2023-09-27 17:57:14

我用 C# 编写了一个 cmdlet,它充当繁重/长时间运行的同步操作的包装器。 该方法(其他人的代码)通过事件处理程序报告此长时间运行的操作期间的进度百分比,我想将这些连接到 powershell 的标准 WriteProgress 方法以获取漂亮的打印进度条。 但是,我收到以下错误消息:

The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread.

这是我的代码:

overrride void ProcessRecord()
{
    LongRunningOperation op = new LongRunningOperation();
    op.ProgressChanged += ProgressUpdate;
    op.Execute();
    op.ProgressChanged -= ProgressUpdate;
}
void ProgressUpdate(object sender, ProgressChangeEventArgs e)
{
   ProgressRecord progress = new ProgressRecord(activityId: 1, activity: "Moving data", statusDescription: "Current operation");
   progress.PercentComplete = e.ProgressPercentage;
   WriteProgress(progress);
}

有人能发现我做错了什么吗?

更新:看起来事件处理程序是从与ProcessRecord()不同的线程触发的。 如何将我需要的信息返回到与ProcessRecord()相同的线程中?

从事件处理程序报告 Powershell 进度

需要

手动将事件处理程序封送回 PowerShell 管道线程ProgressChanged。这可以通过应用生产者-使用者模式来完成,其中ProgressChanged事件处理程序将是生产者,PowerShell 管道线程中的事件循环将是使用者。在支持.NET Framework 4.0中引入BlockingCollection<T>,可以轻松实现它:

overrride void ProcessRecord() {
    Task longRunningOperation;
    using(BlockingCollection<ProgressRecord> queue = new BlockingCollection<ProgressRecord>()) {
        //offload LongRunningOperation to different thread to keep control on PowerShell pipeline thread
        longRunningOperation=Task.Run(() => {
            try {
                //replace EventHandler<ProgressChangeEventArgs> with ProgressChanged type
                EventHandler<ProgressChangeEventArgs> handler =
                    //implemented as anonymous method to capture queue local variable
                    (object sender, ProgressChangeEventArgs e) => {
                        ProgressRecord progress = new ProgressRecord(activityId: 1, activity: "Moving data", statusDescription: "Current operation");
                        progress.PercentComplete = e.ProgressPercentage;
                        //queue ProgressRecord for processing in PowerShell pipeline thread
                        queue.Add(progress);
                    }
                LongRunningOperation op = new LongRunningOperation();
                op.ProgressChanged += handler;
                op.Execute();
                op.ProgressChanged -= handler;
            } finally {
                queue.CompleteAdding();
            }
        });
        //event loop
        for(;;) {
            ProgressRecord progress;
            if(!queue.TryTake(out progress, Timeout.Infinite)) {
                break;
            }
            WriteProgress(progress);
        }
    }
    //get any exception from LongRunningOperation
    longRunningOperation.GetAwaiter().GetResult();
}