21

I have written a piece of code (in C#) to execute a Powershell script (specifically Azure PowerShell) using System.Management.Automation. The powershell script basically uploads a vhd in a container on Azure, which shows the upload progress and time elapsed etc when command is manually entered through azure Powershell. Through code everything works fine but i want to get the result/output of a command (i.e. upload progress, time elapsed), during command execution (i.e. pipeline.invoke();) here is the code:

 RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
 Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
 runspace.Open();
 RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
 Pipeline pipeline = runspace.CreatePipeline();

 Command myCommand = new Command(scriptPath);
 foreach (var argument in arguments)
 {
     myCommand.Parameters.Add(new CommandParameter(argument.Key, argument.Value));
 }
 pipeline.Commands.Add(myCommand);

 var results = pipeline.Invoke(); // i want to get results here (i.e. during command execution) 
 foreach (var psObject in results)
 {
     System.Diagnostics.Debug.Write(psObject.BaseObject.ToString());
 }

Please guide if it is possible to retrieve live output from Powershell.

6
  • Use the PowerShell class instead of Pipeline. Then invoke asynchronously and read from PowerShell.Streams.Progress Commented Dec 21, 2015 at 14:53
  • Can you please guide more using some code? Commented Dec 21, 2015 at 15:17
  • I've added an answer Commented Dec 21, 2015 at 15:40
  • thanks m giving it a try.. Commented Dec 21, 2015 at 15:49
  • To be clear, with the DataAdded eventhandler in place, you don't need to invoke it async. Invoke() will do fine Commented Dec 21, 2015 at 16:05

2 Answers 2

48

Unless you're targeting PowerShell 1.0, there's no need to set up your runspace and pipeline manually, create an instance of the PowerShell class instead:

PowerShell psinstance = PowerShell.Create();
psinstance.AddScript(scriptPath);
var results = psinstance.Invoke();

Way simpler.


Now, the PowerShell class exposes the various non-standard output streams (Verbose, Debug, Error etc.) - including the Progress Stream - via the Streams property so you can subscribe to it, like so:

psinstance.Streams.Progress.DataAdded += myProgressEventHandler;

And then in your event handler:

static void myProgressEventHandler(object sender, DataAddedEventArgs e)
{
    ProgressRecord newRecord = ((PSDataCollection<ProgressRecord>)sender)[e.Index];
    if (newRecord.PercentComplete != -1)
    {
        Console.Clear();
        Console.WriteLine("Progress updated: {0}", newRecord.PercentComplete);
    }
}

As an example, here is that event handler shown above in action, while running a sample script that writes progress information (sample script posted below) in a simple console application:

readProgress

Test-Progress.ps1

function Test-Progress
{
    param()

    Write-Progress -Activity 'Testing progress' -Status 'Starting' -PercentComplete 0
    Start-Sleep -Milliseconds 600
    1..10 |ForEach-Object{
        Write-Progress -Activity "Testing progress" -Status 'Progressing' -PercentComplete $(5 + 6.87 * $_)
        Start-Sleep -Milliseconds 400
    }
    Write-Progress -Activity 'Testing progress' -Status 'Ending' -PercentComplete 99
    Start-Sleep -Seconds 2
    Write-Progress -Activity 'Testing progress' -Status 'Done' -Completed
}

Test-Progress
Sign up to request clarification or add additional context in comments.

2 Comments

Hi, I am using the PowerShell class but the DataAdded event is never triggered for me. My question is why? My code looks like this: using (PowerShell powerShell = PowerShell.Create()) { powerShell.AddScript(scriptText); powerShell.Streams.Progress.DataAdded += ProgressDataAdded; Collection<PSObject> PSOutput = powerShell.Invoke(); }
@IonutEnache Please ask a new question and include the handler code and what you've tried so far :)
7

It may be a little late, but still.

@Mathias R. Jessen answer works great, but for cases with the output of ordinary strings (without Write-Progress and so on):

Initialize the collection first

PSDataCollection<PSObject> output = new PSDataCollection<PSObject>();
output.DataAdded += Output_DataAdded;

Perform

var res = psinstance.BeginInvoke<PSObject, PSObject>(null, output);
res.AsyncWaitHandle.WaitOne();

Get the result real-time

private static void Output_DataAdded(object sender, DataAddedEventArgs e)
{
    PSObject newRecord = ((PSDataCollection<PSObject>)sender)[e.Index];
    Console.WriteLine(newRecord);
}

Tested on Get-Process PS command.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.