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:
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");
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
Generating Random Numbers Without Repeating.C#
What Would Be the Fastest Way to Concatenate Three Files in C#
How to Sync the Scrolling of Two Multiline Textboxes
Combine Two Linq Lambda Expressions
How to Disable Aero Snap in an Application
How to Inject an Attribute Using a Postsharp Attribute
Monitor Process Start in the System
How to Convert a Simple .Net Console Project a into Portable Exe with Mono and Mkbundle
Is This a Covariance Bug in C# 4
Take Screenshot of Multiple Desktops of All Visible Applications and Forms
Oracle Parameters with in Statement
Capturing Process Output via Outputdatareceived Event
Equality Comparison Between Multiple Variables
Looking for C# Equivalent of Scanf
Does "Foreach" Cause Repeated Linq Execution
Unity Eventmanager with Delegate Instead of Unityevent
How to Use Xpath Function in a Xpathexpression Instance Programatically