Execute PowerShell Script from C# with Commandline Arguments
Asked Answered
A

9

134

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?

Addi answered 9/2, 2009 at 9:21 Comment(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.Dunne
C
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();
Cida answered 9/2, 2009 at 10:15 Comment(8)
I still seem to have the problem that if value is something like c:\program files\myprogram, the key is set to c:\program. :(Addi
Never mind. Sometimes it helps when you know how to correctly split strings. ;-) Thanks again, your solution helped me in resolving my problem!Addi
@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. @Cida has the correct answer for adding parameters.Clavicorn
Typing my reply overlapped yours.. I'm glad you got it resolved!Clavicorn
scriptInvoker variable isn't being used.Wildon
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.Helmick
ftfy @Wildon :)Retsina
To capture the powershell output in c# see #10106717Pickmeup
T
58

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
Thermel answered 23/12, 2010 at 16:43 Comment(2)
Hi. Do you have any idea why starting powershell as you described and executing all commands process (in our case) does not exit?Amenity
What library are you usingFarquhar
R
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();
}
Rasla answered 10/12, 2009 at 21:3 Comment(2)
Just added:using (Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))...using (Pipeline pipeline = runspace.CreatePipeline()) Bullnose
When I am passing multiple parameters, it occurs this error: An unhandled exception of type 'System.Management.Automation.ParseException' occurred in System.Management.Automation.dllGraces
L
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.

Lissie answered 9/7, 2014 at 13:48 Comment(0)
H
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.

Hypnology answered 9/2, 2009 at 22:48 Comment(0)
H
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;
}
Hydrography answered 11/9, 2015 at 15:43 Comment(0)
T
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.

Terminator answered 1/12, 2010 at 10:1 Comment(0)
B
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.

Buenabuenaventura answered 12/8, 2021 at 18:38 Comment(2)
Which .NET Version is the project and which packages need a reference for those classes?Yeargain
Referenced package for runtime PowerShell: System.Management.AutomationConvenience
E
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);
Equiangular answered 12/10, 2022 at 12:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.