Executing PowerShell scripts from C#
In today’s post, I will demonstrate how to execute PowerShell scripts from a C# application.
Requirements
- PowerShell 2.0
References
In the solution explorer, add a project reference to the System.Management.Automation assembly. Then, you have to add the following using statements to import the required types:
using System.Collections.ObjectModel; using System.Management.Automation;
Create and populate a PowerShell pipeline
The first step is to create an empty PowerShell pipeline by using the static method PowerShell.Create().
using (PowerShell powershell= PowerShell.Create())
{
...
}
Next, we can add both scripts or commands to execute.
- AddScript(): Add a piece of script to construct a command pipeline.
- AddCommand(): Add a cmdlet to construct a command pipeline.
If your script has parameters, you can easily add a string or an object for the parameter value with the AddParameter() method.
using (PowerShell powershell = PowerShell.Create())
{
...
powershell.AddScript("param($param1) $d = get-date; $s = 'test string value'; " + "$d; $s; $param1; get-service");
powershell.AddParameter("param1", "parameter 1 value!");
...
}
Execution
There are two ways we can call PowerShell to execute it: synchronously and asynchronously.
Synchronously execution
For synchronous execution, we call PowerShell.Invoke(). The caller waits until the script or commands have finished executing completely before returning from the call to Invoke().
using (PowerShell powershell = PowerShell.Create())
{
...
powershell.Invoke();
...
}
The return object from Invoke() is a collection of PSObject instances that were written to the output stream during execution. Inside the PSObject is a member called BaseObject, which contains an object reference to the base type you are working with.
Besides the standard output stream, there are also dedicated streams for warnings, errors, debug, progress, and verbose logging. If any cmdlets leverage those streams, or you call them directly (write-error, write-debug, etc.), then those items will appear in the streams collections.
using (PowerShell powershell = PowerShell.Create())
{
...
Collection<PSObject> psOutput = powershell.Invoke();
foreach (PSObject output in psOutput)
{
if (output != null)
{
Console.WriteLine(outputItem.BaseObject.GetType().FullName);
}
}
...
if (powershell.Streams.Error.Count > 0)
{
// error records were written to the error stream.
// do something with the items found.
}
}
Asynchronous execution
For asynchronous execution, we call PowerShell.BeginInvoke(). BeginInvoke() immediately returns to the caller an IAsyncResult object (used to monitor the status of the execution pipeline) and the script execution begins in the background.
using (PowerShell powershell = PowerShell.Create())
{
...
powershell.AddScript("start-sleep -s 7; get-service");
IAsyncResult result = PowerShellInstance.BeginInvoke();
while (result.IsCompleted == false)
{
Console.WriteLine("Waiting for pipeline to finish...");
Thread.Sleep(1000);
}
Console.WriteLine("Finished!");
...
}
By wrapping the PowerShell instance in a using block and don't wait for execution to complete, the pipeline will close itself, and will abort script execution, when it reaches the closing brace of the using block. To avoid this and wait remove the using block and manually calling Dispose() the instance.
Execution Policy
When you invoke cmdlets through the PowerShell class in C#, the execution policy behavior is subject to the policy restrictions of the machine. In order to get around this is, you can set the execution policy for the scope of the application process by runing Set-ExecutionPolicy and specify the scope to be Process. This should allow you to invoke secondary scripts without altering the machine/user policies.