bloginvoke

Using PowerShell from managed code

Hannes de Jager powershellc-sharp

PowerShell… the gracious gift to Sys-Admins from Jeffrey Snover and his team at Microsoft… It needs no introduction and is certainly one of those tools that, once you start using it, you can’t imagine life without it anymore.

But hey, why should Sys-Admins have all the fun with this mighty automation framework and developers be left out in the cold? After all Windows PowerShell provides a hosting mechanism with which the Windows PowerShell runtime can be embedded inside other applications. In fact, this is what the Exchange Management Console does in 2007 and 2010 and also System Center Virtual Machine Manager – It sits on top of Powershell (as dictated by the Microsoft’s Common Engineering Criteria for server products) and delegates all the earth shaking stuff to PowerShell.

Pulling the Powershell engine into your managed code not only allows your application to leverage all of its built-in super-powers and all the extended functionality available from providers like Exchange, MS SQL and SCVMM but it also allows you to leverage all the knowledge and investments made by your system administrator – his code becomes your toolbox!!

So lets see how this can be done:

Pick your favorite .NET language (I’ll be using C#) and add a reference to the System.Management.Automation assembly in your project:

using System;
using System.Collections.ObjectModel;
using System.Management.Automation;

On my machine this assembly was located at C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\

Now create an empty PowerShell pipeline by instantiating the System.Management.Automation.PowerShell class:

PowerShell powershell = PowerShell.Create();

Add some commands to the pipeline:

powershell.AddCommand("Get-Process");

or a more elaborate script:

powershell.AddScript("Get-Process | where { $_.CPU -gt 30 }");

Call the Invoke method to run the commands of the pipeline:

Collection<PSObject> result = powershell.Invoke();

and do something with the result:

foreach (PSObject process in result)
{
  Console.WriteLine("Name: {0}, ID: {1}",
    process.Members["ProcessName"].Value,
    process.Members["Id"].Value
  );
} 

Its that simple!

If you want to see if something went wrong:

var errors = powershell.Streams.Error;
if (errors.Count > 0)
{
  var e = errors[0].Exception;
  Console.WriteLine(
    "Hmmm, something went wrong. PowerShell gave me this: {0}", 
    e.Message
  );
}

And if you want to execute some more PowerShell with the same object:

powershell.Streams.ClearStreams();
powershell.Commands.Clear();
// ... Go again here...

And there we have the basics that enable us to build a little wrapper class for our reuse:

public class PowershellSession
{
    protected readonly PowerShell powershell;
 
    public PowershellSession()
    {
        powershell = PowerShell.Create();
    }
 
    public void RunScript(string scriptText, 
        Action<Collection<PSObject>> handler)
    {
        powershell.Streams.ClearStreams();
        powershell.Commands.Clear();
        powershell.AddScript(scriptText, true);
        var results = powershell.Invoke();
        ThrowIfError();
        if (handler != null)
        {
           handler(results);
        }
    }
 
    public string RunScript(string scriptText)
    {
        var result = new StringBuilder();
        RunScript(scriptText, results =>
        {
            foreach (var line in results)
            {
                if (line != null)
                {
                    result.AppendLine(line.ToString());
                }
            }
        });
        return result.ToString().TrimEnd();
    }
 
    private void ThrowIfError()
    {
        var errors = powershell.Streams.Error;
        if (errors.Count > 0)
        {
            var e = errors[0].Exception;
            powershell.Streams.ClearStreams();
            throw e;
        }
    }
}

Add some syntactic sugar through extension methods:

public static class PowershellExtentionMethods
{
    private static readonly PowershellSession powerShellSession 
       = new PowershellSession();
 
    public static string PowershellExec(this string commands)
    {
        return powerShellSession.RunScript(commands);
    }
 
    public static void PowershellExec(this string commands,
        Action<Collection<PSObject>> handler)
    {
        powerShellSession.RunScript(commands, handler);
    }
}

…and we have ourselves a framework for playing with PowerShell like so:

var outlookPid = 
    @"$id = Get-Process | where { $_.ProcessName -eq 'OUTLOOK' } | Select id
      $id.Id"
    .PowershellExec();
Console.WriteLine("Process ID for Outlook: {0}", outlookPid);
or how about something a little bit more involved:

Process explorerProcess = null;
"Get-Process | where { $_.ProcessName -eq 'explorer' }"
    .PowershellExec(result =>
        explorerProcess = (Process)result[0].BaseObject);
Console.WriteLine("Killing {0} process with PID {1}...", 
    explorerProcess.ProcessName,
    explorerProcess.Id);
explorerProcess.Kill();
Hannes de Jager
Software Builder