Search code examples
c#powershellpowershell-sdk

Passing argument from C# to called PowerShell Script


I have the following function in my C# file:

private void RunScriptFile(string scriptPath, string computerName, PSCredential credential)
{
   RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
   Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
   runspace.Open();
   RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
   Pipeline pipeline = runspace.CreatePipeline();
   string scriptCommand = "Invoke-Command -ComputerName " + computerName + " -FilePath " + scriptPath + " -ArgumentList " +  credential;
   pipeline.Commands.AddScript(scriptCommand);
   Collection<PSObject> results = pipeline.Invoke();
   runspace.Close();
}

I am calling the following PowerShell script using Credentials passed in the above C# code; script.ps1

Param([PSCredential]$Credentials)
<code part using credentials>

After pipeline.Invoke(), C# code just gets closed without any action, no errors are thrown too.

Is there something I am doing wrong while making the call? The same call if invoked from PowerShell like below works fine:

Invoke-Command -ComputerName <computerName> -FilePath <scipt.ps1> -ArgumentList " +  credential;

Solution

  • If you use .AddScript() with a self-contained command, you must embed arguments in string form, as part of the PowerShell code snippet you're passing - and that won't work for a PSCredential instance.

    In order to pass arguments as specific .NET types in the context of a single command, you can instead use a PowerShell instance with the .AddCommand() and AddParameter() / AddArgument() methods.

    Applied to your scenario:

    private void RunScriptFile(string scriptPath, string computerName, PSCredential credential) {
      Collection<PSObject> results; 
      using (var ps = PowerShell.Create()) {
        ps.AddCommand("Invoke-Command")
          .AddParameter("ComputerName", computerName)
          .AddParameter("FilePath", scriptPath)
          .AddParameter("ArgumentList", new object[] { credential })
        results = ps.Invoke();
      }
    }
    

    This approach has the added advantage of not requiring any parsing of PowerShell code, which is faster and more robust.

    In general, use of the PowerShell class simplifies use of the PowerShell SDK and is sufficient in many cases (often there is no need to manage runspaces, pipelines, ... explicitly).


    However, as PetSerAl points out, PowerShell.AddScript() can accept typed arguments, if you reformulate the code snippet to declare (typed) parameters, via a param(...) block and then call .AddParameter() / .AddArgument():

    ps.AddScript(@"param([string] $ComputerName, [string] $FilePath, [pscredential] $Credential) Invoke-Command -ComputerName $ComputerName -FilePath $FilePath -ArgumentList $Credential")
      .AddArgument(computerName)
      .AddArgument(scriptPath)
      .AddArgument(credential)
    

    As you can see, however, that makes the solution more verbose.

    Declaring parameters makes the intent more obvious, but you can alternatively use the automatic, array-valued $args variable to access the arguments passed positionally:

    ps.AddScript(@"Invoke-Command -ComputerName $args[0] -FilePath $args[1] -ArgumentList $args[2]")
      .AddArgument(computerName)
      .AddArgument(scriptPath)
      .AddArgument(credential)
    

    See also:

    • This answer for guidance on what NuGet package to use in your C# application, depending on the host application and/or target PowerShell edition.

    • This answer for how to handle errors in the context of using the PowerShell SDK.