Friday, October 30, 2009

Executing a PowerShell Pipeline from your own program

In Executing PowerShell code from within your own program I showed how to execute PowerShell code, but the interface was rather rude as the value had to be converted to string and added to the script code involved. If you have lots of objects to proces, this method is inefficient – a pipeline would be much better as the script source only has to be parsed once and the script would be able to access the objects as real objects.

I have now taken it a step further and created a piece of code that can execute a pipeline. If you have lots of objects – ‘lots’ depends on the amount of memory you have available – this may not be the most efficient method. All input and output objects will be represented as List<PSObject> giving some overhead. An alternative would be to use a steppable pipeline but for now, I’ll stick to the easy method.

Here’s the code

public class PSHost
{
private PSDataStreams _InvokePipeline(string Script,
List<PSObject> inputPipeline,
out Collection<PSObject> outputPipeline)
{

PowerShell PS = PowerShell.Create();
PS.AddScript(Script);

outputPipeline=PS.Invoke<PSObject>(inputPipeline);
return PS.Streams;
}
private string _ConvertStreamsToString(PSDataStreams streams)
{
string s = "";
foreach (DebugRecord result in PS.Streams.Debug)
{
s += "DEBUG: " + result.ToString();
}
foreach (VerboseRecord result in PS.Streams.Verbose)
{
s += "VERBOSE: " + result.ToString();
}
foreach (WarningRecord result in PS.Streams.Warning)
{
s += "WARNING: " + result.ToString();
}
foreach (ErrorRecord result in PS.Streams.Error)
{
s += "ERROR: " + result.ToString();
}
return s;

}

public string PSExecutePipeline(string Script,
List<PSObject> inputPipeline,
out Collection<PSObject> outputPipeline)
{

PSDataStreams ds = _InvokePipeline(Script, inputPipeline, out outputPipeline);
return _ConvertStreamsToString(ds);
}
}











The script must include $input to pick up the values – if any. The streams collection is returned as a string, so you can check for warning/error/verbose/debug/progress output. Parsing errors will throw an exception; you would use a try-catch to handle that. Naturally, you can change the interface to your own requirements.


Note that output objects may be real objects with properties or just simple value types. A property value can be retrieved with p.property[“propertyname”].value and a simple with p.BaseObject.



Example 1 – summing numbers




// Input pipeline
List<PSObject> numbers = new List<PSObject>();

// Add values/objects
numbers.Add(new PSObject(1));
numbers.Add(new PSObject(2));
numbers.Add(new PSObject(3));
numbers.Add(new PSObject(4));
numbers.Add(new PSObject(5));

// Output pipeline
System.Collections.ObjectModel.Collection<PSObject> outputPipeline;

PSHost psh = new PSHost();
string s = psh.PSExecutePipeline("$input | measure-object -sum", numbers, out outputPipeline);
if (string.IsNullOrEmpty(s))
{
foreach (PSObject p in outputPipeline)
{
System.Diagnostics.Debug.WriteLine("Sum is " + p.Properties["Sum"].Value.ToString());
}
}
else
{
System.Diagnostics.Debug.WriteLine("PowerShell failed: " + s);
}









Example 2 demonstrates how to return a value type




// Re-uses the variable from example 1
List<PSObject> empty = new List<PSObject>();
s = psh.PSExecutePipeline("100", empty, out outputPipeline);
if (string.IsNullOrEmpty(s))
{
foreach (PSObject p in outputPipeline)
{
if (p.BaseObject.GetType().IsValueType)
{
System.Diagnostics.Debug.WriteLine("Number is " + p.BaseObject.ToString());
}
else
{
System.Diagnostics.Debug.WriteLine("Number is " + p.Properties["Value"].Value.ToString());
}
}
}
else
{
System.Diagnostics.Debug.WriteLine("PowerShell failed: " + s);
}





Debug output can be seen with DbgView.



Have fun

No comments: