Creating Your Own PowerShell Host

In this post I’m going to have a quick look at creating your own PowerShell host. I’ve been looking into hosting PowerShell in a Windows service using C# for a new project, this post will serve as a quick intro. Most of the information in this post comes from this MSDN link but I’m hoping to create a one pager intro for busy people.

PowerShell Runspaces
When you execute PowerShell commands it is actually the runspace that is doing the execution work and handling the execution pipeline. The host is mostly responsible for handling input and output streams to interact with the runspace. Examples of hosts are PowerShell.exe, PowerShell ISE or a custom console, WPF, WinForms or Windows service application. The different hosts interact with the PowerShell runspace behind the scenes. When you create your own host you are creating a runspace instance and passing it commands to execute.

Creating A Runspace
Creating the default runspace is pretty easy, just call:

PowerShell ps = PowerShell.Create();

Note: You will need a reference to System.Management.Automation for the code to work.

You can do a lot more to customise the runspace instance. You can set initial variables, configure PowerShell options, import modules and set startup scripts. You can even constrain the commands that each runspace is allowed to execute. Some of the modifications can be applied to the default runspace while others require creating an empty initial state and runspace explicitly. Example here.

Running commands
Once you have a runspace you can give it commands to execute. You do this using the AddCommand and AddParameter methods of the PowerShell object.

powershell.AddCommand("sort-object") .AddParameter("descending").AddParameter("property", "handlecount");

You can add multiple commands to the pipeline before executing them, the results of the commands will be piped together. To start a new statement and a new pipeline call the AddStatement method. You can add script snippets or script files to the pipeline using the AddScript method. You can execute commands synchronously by using the Invoke method or asynchronously using the BeginInvoke method. Example here.

Handling Output
If you invoked the command synchronously you can get the output from the result of the Invoke method.

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

If you called the command asynchronously you can also wait for it to complete execution or even better you can use events to handle the output as it occurs, example here.

Error Handling
Powershell has separate streams to handle different output, if you look at the PowerShell object’s Streams property you will see there are streams for Debug, Error, Information, Progress, Verbose and Warning. When non-terminating errors occur they will be written to the Error stream but the pipeline won’t raise exceptions. When terminating errors occur the pipeline will throw RuntimeExceptions and you can handle them in your code with a Try..Catch block.

Remote Connections
For commands that doesn’t have a ComputerName parameter to execute remotely you have to create a remote runspace, it is a bit like creating a remote session using Enter-PSSession. You create a WSManConnectionInfo object pointing to an endpoint on the remote machine and then you pass the WSManConnectionInfo object to the RunspaceFactory.CreateRunspace method. Example here.

Interacting With The Host
You can interact with the host from your Powershell scripts, although it is not really applicable to server automation it might be handy for user utilities. You can for instance manipulate variables and controls on the host from your PowerShell script. Example here.

Francois Delport

Published by

Francois Delport

I am a cloud and devops consultant, technology fan and previously a professional C# developer with a keen interest in system design and architecture. Currently I am involved in projects using Azure, the Microsoft stack and DevOps. I am based in Melbourne, Australia. Email: [email protected]

Leave a Reply

Your email address will not be published. Required fields are marked *