In this article, we’ll learn how to execute a PowerShell script in C# using the ProcessStartInfo class from System.Diagnostics
namespace and PowerShell classes from the System.Management.Automation
namespace, which is available in PowerShell.SDK
.
Let’s see how to do that!
Use the ProcessStartInfo Class to Execute a PowerShell Script in C#
.NET provides users with a ProcessStartInfo
class that enables us to configure, start and stop a process. We can leverage this class to invoke PowerShell commands and scripts. To learn more about ProcessStartInfo
class, please check the article on How to execute CLI Applications From C#.
Let’s start by creating a simple .ps1
file with this content:
I am invoked using ProcessStartInfoClass!
Next, we will create a function to execute our script:
public string ExecuteScript(string pathToScript) { var scriptArguments = "-ExecutionPolicy Bypass -File \"" + pathToScript + "\""; var processStartInfo = new ProcessStartInfo("powershell.exe", scriptArguments); processStartInfo.RedirectStandardOutput = true; processStartInfo.RedirectStandardError = true; using var process = new Process(); process.StartInfo = processStartInfo; process.Start(); string output = process.StandardOutput.ReadToEnd(); string error = process.StandardError.ReadToEnd(); Console.Writeline(output); // I am invoked using ProcessStartInfoClass! }
When we create a new ProcessStartInfo
instance we have to provide two arguments – the file name and command line arguments. Thus, we set the ExecutionPolicy Bypass
, which ensures that we can run our scripts with no restrictions.
Then, we invoke the process using ProcessStartInfo
class. We do that by creating a new Process
instances and assigning the created processStartInfo
object to the StartInfo
property.
When we start a process using Process.Start
, the process has its own standard output and standard error streams where it will write output and error messages. By default, the calling process is not catching these streams. That is why we set RedirectStandardOutput
and RedirectStandardError
properties to true
. We instruct the Process
object to redirect the standard output and standard error streams so that we can capture and read them programmatically.
Lastly, we take the results from the output and error streams respectively and safely dispose of the process by including using
statement.
Now, let’s modify our code and see how simple we can execute PowerShell commands directly instead of running a script. All we need to do is change the setup of the ProcessStartInfo
class slightly:
public string ExecuteCommand(string command) { var processStartInfo = new ProcessStartInfo(); processStartInfo.FileName = "powershell.exe"; processStartInfo.Arguments = $"-Command \"{command}\""; processStartInfo.UseShellExecute = false; processStartInfo.RedirectStandardOutput = true; using var process = new Process(); process.StartInfo = processStartInfo; process.Start(); string output = process.StandardOutput.ReadToEnd(); Console.Writeline(output); }
We configure it in a similar manner except now the Arguments
include a command
variable that we will specify when invoking the function:
ExecuteCommand("I am invoked using echo command!"); // I am invoked using echo command!
To sum up, when we use the ProcessStartInfo
class to execute a PowerShell script, we actually start a PowerShell.exe
on our local system by calling process.Start
and passing it all the necessary arguments to execute our commands.
Use the PowerShell Class to Execute a PowerShell Script in C#
We can also use the PowerShell class from the PowerShell.SDK
to execute our commands and scripts in C#. Let’s start by referencing the Microsoft.PowerShell.SDK
, which gives us access to the System.Management.Automation
namespace:
using System.Management.Automation;
PowerShell.SDK
offers us a lot of flexibility for executing commands and scripts and enables us to create custom runspaces, which we will cover later.
The main difference between using the PowerShell
class and the ProcessStartInfo
class is that the PowerShell
class creates our PowerShell instance in our application, so we do not need to invoke an external PowerShell process to execute commands.
Let’s see how simple it is to execute a script.
This time we’ll create a simple echo.ps1
script:
'I am invoked by PowerShell class!'
Now let’s invoke this script using the PowerShell
class:
PowerShell ps = PowerShell.Create(); ps.AddScript(@".\echo.ps1").Invoke();
We simply pass the path to our script to invoke our echo.ps1
script.
Let’s execute a simple command directly this time, we’ll use Get-Date
to get the current date time:
using var ps = PowerShell.Create(); ps.AddCommand("Get-Date"); var processes = ps.Invoke(); Console.WriteLine(processes.First().ToString()); // 10/05/2023 19:12:34
Notice that we instantiate the PowerShell
class in the using statement to dispose of it properly after. We create a PowerShell
instance, add a command and invoke it.
To display the resulting objects to the console, we just use the First
method and convert the result to a string.
As we can see, as long as we know the command in PowerShell, we can easily transform it and use it in C#. Let us try one more example where we start a process which is the notepad:
using var ps = PowerShell.Create(); ps.AddCommand("Start-Process").AddArgument("notepad"); ps.Invoke();
Notice that the PowerShell
class allows method chaining. We use the AddArgument()
method after the AddCommand()
method.
By default, the PowerShell
class uses the default runspace to run the script and execute commands. The default runspace represents the default execution environment for our commands and scripts.
Let’s see how to use a custom runspace.
Custom Runspace
Alongside security reasons, we might use a custom runspace for performance gains. This is because we limit the runspace to a specified subset of commands that we actually need. That means that a runspace loads only the commands that we specify.
First, we need to create an InitialSessionState
object. This class allows us to define a set of elements that should be present when a SessionState
is created. The SessionStateVariableEntry
class allows us to create new session state variables.
A session state variable is a variable that persists for the entire session, similar to a global variable. We need it because it represents the set of allowed commands we can use in our runspace throughout the session. SessionState
refers to the current configuration of a Windows PowerShell
session or module.
Basically, the PowerShell
session refers to the time from starting a host application that opens PowerShell runspace up until it closes the runspace. We will apply all our restrictions to this InitialSessionState
object:
InitialSessionState iss = InitialSessionState.Create(); var entry = new SessionStateVariableEntry("AllowedCommands", new[] { "Get-Date" }, "List of allowed commands"); iss.Variables.Add(entry);
Here we allow only a single command to be used, the Get-Date
command.
Next, we need to import the Microsoft.PowerShell.Utility
module that contains this command and apply the command to the InitialSessionState
object:
var ms = new ModuleSpecification("Microsoft.PowerShell.Utility"); iss.ImportPSModule(new[] { ms }); var getDateCmdlet = new SessionStateCmdletEntry("Get-Date", typeof(Microsoft.PowerShell.Commands.GetDateCommand), ""); iss.Commands.Add(getDateCmdlet);
Now we can create and run our runspace to use the configured InitialSessionState
:
Runspace rs = RunspaceFactory.CreateRunspace(iss); rs.Open();
All that remains now is to apply our custom runspace to the PowerShell
instance and to try it out:
using var ps = PowerShell.Create(); ps.Runspace = rs; ps.AddCommand("Get-Date"); var processes= ps.Invoke(); Console.WriteLine(processes.First().ToString()); // 10/05/2023 20:15:34
As expected, we got the time and date printed on the console.
Lastly, let’s try to run the Start-Process
command instead, which is not in our custom runspace. To better understand what is happening, we will create a method that returns true
or false
depending on the execution result of our command:
public bool StartProcess(string processName) { try { using (var ps = PowerShell.Create()) { ps.Runspace = rs; ps.AddCommand("Start-Process").AddArgument(processName); ps.Invoke(); return true; } } catch (Exception) { return false; } }
Next, let’s print the result to the console:
var processStarted = StartProcess("notepad"); if (!processStarted) { Console.WriteLine("This is a custom runspace that can only run Get-Date command"); }
Our custom runspace does not recognize the command and throws an exception with the message. This shows we have successfully created a restricted runspace.
Conclusion
In this article, we reviewed two ways to execute a PowerShell script in C#. We first covered using the ProcessStartInfo
class. Next, we focused on the PowerShell
class from the PowerShell.SDK
and the difference between this class and the ProcessStartInfo
class. Lastly, we took a step further by looking at a custom runspace where we can restrict which commands can be executed.