133

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
  • 30,470
  • 17
  • 104
  • 128
Mephisztoe
  • 3,276
  • 7
  • 34
  • 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

9 Answers9

128

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
  • 909
  • 13
  • 23
Kosi2801
  • 22,222
  • 13
  • 38
  • 45
  • 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
  • 1
    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
57

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,783
  • 17
  • 86
  • 139
Jowen
  • 5,203
  • 1
  • 43
  • 41
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
  • 27,717
  • 28
  • 128
  • 190
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
7

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,920
  • 2
  • 24
  • 34
Andrew Chaa
  • 6,120
  • 2
  • 45
  • 33
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,783
  • 17
  • 86
  • 139
Pedro Luz
  • 973
  • 5
  • 14
6

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,279
  • 2
  • 31
  • 23
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,783
  • 17
  • 86
  • 139
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
  • 454
  • 3
  • 9
0

If your code cannot find the System.Management.Automation.Runspaces namespace you need to add a dependency to System.Management.Automation.dll. This DLL gets shipped with PowerShell and is by default located in the directory: C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0

To add a reference open your project and right-click "references > add reference" and select the "Browse" button to go to the above mentioned location and select the needed .dll file. Click "add" and the reference will show up in the browse tab with a checked checkbox next to it.

After adding the referenceSystem.Management.Automation.Runspaces You can run the in other mentioned code to add parameters and execute the PowerShell script. I find it very convenient to use a tuple to store the "key", "value" pairs.

/// <summary>
/// Run a powershell script with a list of arguments
/// </summary>
/// <param name="commandFile">The .ps1 script to execute</param>
/// <param name="arguments">The arguments you want to pass to the script as parameters</param>
private void ExecutePowerShellCommand(string commandFile, List<Tuple<string, string>> arguments)
{
    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
    runspace.Open();

    Pipeline pipeline = runspace.CreatePipeline();

    //commandFile is the PowerShell script you want to execute, e.g. "FooBar.ps1"
    Command cmd = new Command(commandFile);

    // Loop through all the tuples containing the "key", "value" pairs and add them as a command parameter
    foreach (var parameter in arguments)
        cmd.Parameters.Add(new CommandParameter(parameter.Item1, parameter.Item2));

    pipeline.Commands.Add(cmd);

    // Execute the PowerShell script
    var result = pipeline.Invoke();

}

and the calling code:

string commandFile = @"C:\data\test.ps1";

List<Tuple<string, string>> arguments = new List<Tuple<string, string>>();
arguments.Add(new Tuple<string, string>("filePath", @"C:\path\to\some\file"));
arguments.Add(new Tuple<string, string>("fileName", "FooBar.txt"));

ExecutePowerShellCommand(commandFile, arguments);
Remy
  • 4,843
  • 5
  • 30
  • 60