Get Powershell Command's Output When Invoked Through Code

Get Powershell command's output when invoked through code

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

How to catch full invoke text from powershell class in C#

If you want the exact text that powershell produces you then you can use Out-String in the powershell command:

ps.Commands.AddScript("ls | Out-String");

enter image description here


You can also read the values by accessing the Properties of the PSObject:

foreach (PSObject obj in results)
{
var name = obj.Properties["Name"]?.Value.ToString()
var mode = obj.Properties["Mode"]?.Value.ToString();
var length = obj.Properties["Length"]?.Value.ToString();
var lastMod = (DateTime?)obj.Properties["LastWriteTime"]?.Value;

Console.WriteLine(string.Format("{0} {1} {2} {3}", mode, lastMod, length, name));
}

Note, as mentioned in mklement0's answer, you don't need to use Runspace to execute this powershell. Consider using Get-ChildItem rather than ls.

Capturing output from powershell commands run from C#

I think the answer is here:

while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
// do something with line
}

Powershell Capture Invoke-Command Output

That is the kind of behavior you will get with Invoke-Command by design.

Invoke-Command do not return the objects from the remote session. Rather, it return a representation of the object that did go through several processes.

First, it is serialized on the remote environment, then deserialized back on the local environment.

That is for everything that get transmitted. There are some primitive types, serialization-wise, that get deserialized into a "live" object directly, such as:

  • Byte, SByte, Byte[]
  • Int16, Int32, Int64, UInt16, UInt32, Uint64
  • Decimal, Single, Double
  • TimeSpan, DateTime, ProgressRecord
  • Char, String, XmlDocument, SecureString
  • Boolean,Guid, Uri, Version

Then you have types that are not deserialized with full fidelity, but behave as primitive types for most practical purposes.

This include Enums, which are deserialized into an underlying integer.
Similarly, deserializer will preserve contents of lists, but might change the actual type of the container. (eg: List deserialized to ArrayList, Dictionaries deserialized into Hashtables, etc...)

Finally, you also have some objects that get rehydrated into their live counterpart. For instance, IP Address object get serialized, then deserialized into a Deserialized.System.Net.IPAddress and converted again to its original type through "rehydration", which is the process that dictate how the deserialized type should be converted again.

There is some built-in rehydration for some of PowerShell types… :

  • PSPrimitiveDictionary
  • SwitchParameter
  • PSListModifier
  • PSCredential

as well as for some types from base class libraries:

  • IPAddress, MailAddress
  • CultureInfo
  • X509Certificate2, X500DistinguishedName
  • DirectorySecurity, FileSecurity, RegistrySecurity

So, to do what you seek, you will need to return objects that are serializable. You will need to dig into the COM object and return the properties values that you want. You could use Get-Member to determine the available properties and from there, return what you want.

You could also use ConvertTo-Json on the remote object to return a json representation of it and convert it back to a PSObject locally. You won't get an accurate representation either, type-wise, but you might get a better view of the properties / values. Don't forget to set the -Depth parameter to an higher number if needed since the default is 4 layers deep.

Reference

Microsoft Dev-blogs - How objects are sent to and from remote address.



Related Topics



Leave a reply



Submit