122

I need to execute a PowerShell script from within C#. The script needs commandline arguments.

This is what I have done so far:

RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();

Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();

RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);

Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add(scriptFile);

// Execute PowerShell script
results = pipeline.Invoke();

scriptFile contains something like "C:\Program Files\MyProgram\Whatever.ps1".

The script uses a commandline argument such as "-key Value" whereas Value can be something like a path that also might contain spaces.

I don't get this to work. Does anyone know how to pass commandline arguments to a PowerShell script from within C# and make sure that spaces are no problem?

Guss
  • 27,350
  • 15
  • 95
  • 119
Mephisztoe
  • 3,104
  • 7
  • 33
  • 48
  • 1
    Just in order to clarify for future users, accepted answer resolve the problem for people having issues with spaces even without the use parameters. Using : `Command myCommand = new Command(scriptfile);` and then `pipeline.Commands.Add(myCommand);` solve the escaping issue. – scharette May 14 '18 at 16:20

8 Answers8

122

Try creating scriptfile as a separate command:

Command myCommand = new Command(scriptfile);

then you can add parameters with

CommandParameter testParam = new CommandParameter("key","value");
myCommand.Parameters.Add(testParam);

and finally

pipeline.Commands.Add(myCommand);

Here is the complete, edited code:

RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();

Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();

Pipeline pipeline = runspace.CreatePipeline();

//Here's how you add a new script with arguments
Command myCommand = new Command(scriptfile);
CommandParameter testParam = new CommandParameter("key","value");
myCommand.Parameters.Add(testParam);

pipeline.Commands.Add(myCommand);

// Execute PowerShell script
results = pipeline.Invoke();
nardnob
  • 890
  • 14
  • 23
Kosi2801
  • 20,918
  • 12
  • 37
  • 43
  • I still seem to have the problem that if value is something like c:\program files\myprogram, the key is set to c:\program. :( – Mephisztoe Feb 09 '09 at 10:48
  • 2
    Never mind. Sometimes it helps when you know how to correctly split strings. ;-) Thanks again, your solution helped me in resolving my problem! – Mephisztoe Feb 09 '09 at 12:37
  • @Tronex - you should be defining the key as being a parameter for your script. PowerShell has some great builtin tools for working with paths. Maybe ask another question about that. @Kosi2801 has the correct answer for adding parameters. – Steven Murawski Feb 09 '09 at 12:39
  • Typing my reply overlapped yours.. I'm glad you got it resolved! – Steven Murawski Feb 09 '09 at 12:39
  • 3
    scriptInvoker variable isn't being used. – niaher Jan 15 '14 at 08:07
  • 1
    How to capture the powershell output in c# back.In my case pipeline.Invoke() return null value if there is any write-host in the ps script. – Asif Iqbal Mar 19 '20 at 13:29
  • ftfy @niaher :) – nardnob Dec 23 '20 at 23:55
  • To capture the powershell output in c# see https://stackoverflow.com/questions/10106217/how-would-i-return-an-object-or-multiple-values-from-powershell-to-executing-c-s – Christian Storb Feb 16 '21 at 08:21
52

I have another solution. I just want to test if executing a PowerShell script succeeds, because perhaps somebody might change the policy. As the argument, I just specify the path of the script to be executed.

ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = @"powershell.exe";
startInfo.Arguments = @"& 'c:\Scripts\test.ps1'";
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardError = true;
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
Process process = new Process();
process.StartInfo = startInfo;
process.Start();

string output = process.StandardOutput.ReadToEnd();
Assert.IsTrue(output.Contains("StringToBeVerifiedInAUnitTest"));

string errors = process.StandardError.ReadToEnd();
Assert.IsTrue(string.IsNullOrEmpty(errors));

With the contents of the script being:

$someVariable = "StringToBeVerifiedInAUnitTest"
$someVariable
wonea
  • 4,425
  • 17
  • 82
  • 137
Jowen
  • 4,975
  • 1
  • 42
  • 40
11

I had trouble passing parameters to the Commands.AddScript method.

C:\Foo1.PS1 Hello World Hunger
C:\Foo2.PS1 Hello World

scriptFile = "C:\Foo1.PS1"

parameters = "parm1 parm2 parm3" ... variable length of params

I Resolved this by passing null as the name and the param as value into a collection of CommandParameters

Here is my function:

private static void RunPowershellScript(string scriptFile, string scriptParameters)
{
    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
    runspace.Open();
    RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
    Pipeline pipeline = runspace.CreatePipeline();
    Command scriptCommand = new Command(scriptFile);
    Collection<CommandParameter> commandParameters = new Collection<CommandParameter>();
    foreach (string scriptParameter in scriptParameters.Split(' '))
    {
        CommandParameter commandParm = new CommandParameter(null, scriptParameter);
        commandParameters.Add(commandParm);
        scriptCommand.Parameters.Add(commandParm);
    }
    pipeline.Commands.Add(scriptCommand);
    Collection<PSObject> psObjects;
    psObjects = pipeline.Invoke();
}
Liam
  • 25,247
  • 27
  • 110
  • 174
twalker
  • 111
  • 1
  • 3
  • Just added:`using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))...using (Pipeline pipeline = runspace.CreatePipeline()) ` – Red Jul 01 '16 at 06:11
  • When I am passing multiple parameters, it occurs this error: An unhandled exception of type 'System.Management.Automation.ParseException' occurred in System.Management.Automation.dll – Muhammad Noman Aug 08 '18 at 08:17
6

Mine is a bit more smaller and simpler:

/// <summary>
/// Runs a PowerShell script taking it's path and parameters.
/// </summary>
/// <param name="scriptFullPath">The full file path for the .ps1 file.</param>
/// <param name="parameters">The parameters for the script, can be null.</param>
/// <returns>The output from the PowerShell execution.</returns>
public static ICollection<PSObject> RunScript(string scriptFullPath, ICollection<CommandParameter> parameters = null)
{
    var runspace = RunspaceFactory.CreateRunspace();
    runspace.Open();
    var pipeline = runspace.CreatePipeline();
    var cmd = new Command(scriptFullPath);
    if (parameters != null)
    {
        foreach (var p in parameters)
        {
            cmd.Parameters.Add(p);
        }
    }
    pipeline.Commands.Add(cmd);
    var results = pipeline.Invoke();
    pipeline.Dispose();
    runspace.Dispose();
    return results;
}
wonea
  • 4,425
  • 17
  • 82
  • 137
NotHere
  • 918
  • 5
  • 14
5

You can also just use the pipeline with the AddScript Method:

string cmdArg = ".\script.ps1 -foo bar"            
Collection<PSObject> psresults;
using (Pipeline pipeline = _runspace.CreatePipeline())
            {
                pipeline.Commands.AddScript(cmdArg);
                pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                psresults = pipeline.Invoke();
            }
return psresults;

It will take a string, and whatever parameters you pass it.

James Pogran
  • 4,089
  • 2
  • 29
  • 23
4

For me, the most flexible way to run PowerShell script from C# was using PowerShell.Create().AddScript()

First you'll need to install the Microsoft.PowerShell.SDK nuget package. Or if targeting .net framework you'll need Microsoft.PowerShell.5.ReferenceAssemblies

The snippet of the code is

using System.Management.Automation;

string scriptDirectory = Path.GetDirectoryName(
    ConfigurationManager.AppSettings["PathToTechOpsTooling"]);

var script =    
    "Set-Location " + scriptDirectory + Environment.NewLine +
    "Import-Module .\\script.psd1" + Environment.NewLine +
    "$data = Import-Csv -Path " + tempCsvFile + " -Encoding UTF8" + 
        Environment.NewLine +
    "New-Registration -server " + dbServer + " -DBName " + dbName + 
       " -Username \"" + user.Username + "\" + -Users $userData";

_powershell = PowerShell.Create().AddScript(script);
_powershell.Invoke<User>();
foreach (var errorRecord in _powershell.Streams.Error)
    Console.WriteLine(errorRecord);

You can check if there's any error by checking Streams.Error. It was really handy to check the collection. User is the type of object the PowerShell script returns.

Geordie
  • 1,496
  • 1
  • 18
  • 28
Andrew Chaa
  • 5,622
  • 2
  • 39
  • 31
3

Here is a way to add Parameters to the script if you used

pipeline.Commands.AddScript(Script);

This is with using an HashMap as paramaters the key being the name of the variable in the script and the value is the value of the variable.

pipeline.Commands.AddScript(script));
FillVariables(pipeline, scriptParameter);
Collection<PSObject> results = pipeline.Invoke();

And the fill variable method is:

private static void FillVariables(Pipeline pipeline, Hashtable scriptParameters)
{
  // Add additional variables to PowerShell
  if (scriptParameters != null)
  {
    foreach (DictionaryEntry entry in scriptParameters)
    {
      CommandParameter Param = new CommandParameter(entry.Key as String, entry.Value);
      pipeline.Commands[0].Parameters.Add(Param);
    }
  }
}

this way you can easily add multiple parameters to a script. I've also noticed that if you want to get a value from a variable in you script like so:

Object resultcollection = runspace.SessionStateProxy.GetVariable("results");

//results being the name of the v

you'll have to do it the way I showed because for some reason if you do it the way Kosi2801 suggests the script variables list doesn't get filled with your own variables.

wonea
  • 4,425
  • 17
  • 82
  • 137
Ruud
  • 65
  • 1
  • 7
0

Here's what it worked for me, including cases when the arguments contains spaces:

using (PowerShell PowerShellInst = PowerShell.Create())
        {

            PowerShell ps = PowerShell.Create();
            
            string param1= "my param";
            string param2= "another param";
            string scriptPath = <path to script>;

            ps.AddScript(File.ReadAllText(scriptPath));

            ps.AddArgument(param1);
            ps.AddArgument(param2);

            ps.Invoke();
         
        }

I find this approach very easy to understand and very clear.

J.Tribbiani
  • 399
  • 3
  • 9