PaaP: Windows PowerShell as a Platform – Part 1

In addition to being a scripting language, Windows PowerShell is also used as a platform in many applications. This is possible because the Windows PowerShell engine can be hosted inside an application. This blog post and the next will deal with the various APIs available for hosting Windows PowerShell in a C# application.

The most important type for hosting Windows PowerShell is the System.Management.Automation.PowerShell class. This class provides methods that create a pipeline of commands and then execute those commands in a runspace.

AddCommand

Let’s take a very simple example. Suppose you want to get the list of running processes on the machine. The way to run this command is as follows.

Step 1 – Create a PowerShell object

PowerShell ps = PowerShell.Create();

Step 2 – Add the command that you want to execute

ps.AddCommand(“Get-Process”);

Step 3 – Invoke the command

ps.Invoke();

You can also do all of those in a single step like this:

PowerShell.Create().AddCommand(“Get-Process”).Invoke();

AddParameter

The previous example executes a single command without any parameters. Let’s say I want to get a list of all the PowerShell processes running on the machine. I can use the AddParameter method to add parameters to the command.

PowerShell.Create().AddCommand(“Get-Process”)
                   .AddParameter(“Name”, “PowerShell”)
                   .Invoke();

To add more parameters, you can call AddParameter again like this:

PowerShell.Create().AddCommand(“Get-Process”)
                   .AddParameter(“Name”, “PowerShell”)
                   .AddParameter(“Id”, “12768”)
                   .Invoke();

Or, if you have a dictionary of parameter names and values, you can use the AddParameters method to add all the parameters.

IDictionary parameters = new Dictionary<String, String>();
parameters.Add("Name", "PowerShell");

parameters.Add("Id", "12768");
PowerShell.Create().AddCommand(“Get-Process”)
		   .AddParameters(parameters)
   		   .Invoke()

AddStatement

Now that we have executed a single command and its parameters, let’s execute a bunch of commands. The PowerShell API provides a way for us to simulate batching with the use of AddStatement method, which adds an additional statement to the end of the pipeline. To get the list of running PowerShell processes, and then get the list of running services, do the following:

PowerShell ps = PowerShell.Create();
ps.AddCommand(“Get-Process”).AddParameter(“Name”, “PowerShell”);
ps.AddStatement().AddCommand(“Get-Service”);
ps.Invoke();

AddScript

The AddCommand method only run commands. If I want to run a script, I can use the AddScript method. Let’s assume I have a simple script, a.ps1, that gets the number of running PowerShell processes on my machine.

------------D:\PshTemp\a.ps1----------
$a = Get-Process -Name PowerShell
$a.count
------------D:\PshTemp\a.ps1----------
PowerShell ps = PowerShell.Create();
ps.AddScript(“D:\PshTemp\a.ps1”).Invoke();

The AddScript API also provides an option to run the script in local scope. In this example, the script runs in local scope because we pass a value of true to the useLocalScope parameter.

PowerShell ps = PowerShell.Create();
ps.AddScript(@“D:\PshTemp\a.ps1”, true).Invoke();

Handling Errors, Verbose, Warning, Debug and Progress Messages

The PowerShell API also gives us access to the errors, verbose messages, etc. that are generated when the command runs. You can access the PowerShell.Streams.Error property to get the list of all errors and the PowerShell.Streams.Verbose property to get the verbose messages. There are properties for accessing the Progress, Debug and Warning messages as well.

PowerShell ps = PowerShell.Create()
                          .AddCommand("Get-Process")
                          .AddParameter("Name", "Non-ExistentProcess");
ps.Invoke();

ps.Streams.Error has the list of errors that were generated when running this command. In our case, we get one error for looking for a non-existent process.

Get-Process : Cannot find a process with the name "Non-ExistentProcess". 
Verify the process name and call the cmdlet again. 
+ CategoryInfo : ObjectNotFound: (Non-ExistentProcess:String) [Get-Process], 
ProcessCommandException 
+ FullyQualifiedErrorId : 
NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand

The PowerShell API is very powerful. It enables us to execute the commands in synchronous/asynchronous mode, to create a constrained runspace which exposes a limited set of commands, to execute commands in a nested pipeline, etc.

In all of the examples in this post, we created a default runspace that includes all of the built-in Windows PowerShell commands. This is not efficient in terms of the amount of memory consumed. In a lot of scenarios, people want to use a runspace with only a specific set of commands/language elements. In the next blog post, I’ll explain how to create a more limited and efficient runspace.

Thanks,

Indhu Sivaramakrishnan [MSFT]

Windows PowerShell Developer

Microsoft Corporation