Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reporting Powershell progress from event handler

I've written a cmdlet in C# that acts as a wrapper for a heavy/long-running synchronous operation. The method (someone else' code) reports percentage progress during this long-running operation via event handlers, and I'd like to hook these up to powershell's standard WriteProgress method to get the pretty-printed progress bar. However, I'm getting the following error message:

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.

Here's my code:

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);
}

Anyone able to spot what I'm doing wrong?

Update: Looks like the event handler is being triggered from a different thread than ProcessRecord(). How can I get the information I need back into the same thread as ProcessRecord()?

like image 589
Benjin Avatar asked Mar 01 '26 20:03

Benjin


1 Answers

You need to manually marshal ProgressChanged event handler back to PowerShell pipeline thread. It can be done by applying producer–consumer pattern where ProgressChanged event handler will be producer and event loop in PowerShell pipeline thread will be consumer. It can be easy implemented with support of BlockingCollection<T> introduced in .NET Framework 4.0:

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();
}
like image 135
user4003407 Avatar answered Mar 04 '26 09:03

user4003407



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!