Execute PowerShell Script from C# with Commandline Arguments

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?

261278 次浏览

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();

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.

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();
}

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.

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

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.

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;
}

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.

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);