Search code examples
c#powershellrunspace

Adding command to CreatePipeline() vs Using Commands.Add()


I'm having some trouble understanding the difference between the following code snippets:

using System.Management.Automation;
using System.Management.Automation.Runspaces;

Console.WriteLine("#------ TidyScript ------#");

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

Pipeline pipeline = runspace.CreatePipeline("C:\\\"Program Files\"\\\"tidy 5.8.0\"\\bin\\tidy.exe teste.html > teste_tidy.html ");


var results = pipeline.Invoke();

foreach (var result in results)
{
  Console.WriteLine(result.ToString());
}

The above snippet executes smoothly and outputs my desired result, which is a corrected version of an badly formed html file. However, the following code:

using System.Management.Automation;
using System.Management.Automation.Runspaces;

Console.WriteLine("#------ TidyScript ------#");

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

Pipeline pipeline = runspace.CreatePipeline();

Command myCommand = new Command("C:\\\"Program Files\"\\\"tidy 5.8.0\"\\bin\\tidy.exe teste.html > teste_tidy.html ");
pipeline.Commands.Add(myCommand);

var results = pipeline.Invoke();

foreach (var result in results)
{
  Console.WriteLine(result.ToString());
}

Gives out an unhandled exception:

Unhandled exception. System.Management.Automation.CommandNotFoundException: The term 'C:"Program Files""tidy 5.8.0"\bin\tidy.exe teste.html > teste_tidy.html ' is not recognized as a name of a cmdlet, function, script file, or executable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

What is the difference between adding the previous command directly onto CreatePipeline() and adding after by using pipeline.Commands.Add(myCommand)? Another use case I tried was the ls command. On the first example, I used CreatePipeline("ls ..") which worked perfectly, but the latter example's output was a CommandNotFoundException as well.


Solution

    • runspace.CreatePipeline() expects a piece of PowerShell source code as its argument.

    • For equivalent functionality via a pipeline's .Commands collection, call its .AddScript() method.

      • There's no need for a instantiating Command,which itself represents only a single, specific command, and to which parameter values (arguments) would have to be added via its .Parameters collection.

      • Whatever you pass to a Command constructor is interpreted as the name or path of a command only - not a complete PowerShell statement. Mistakenly passing a complete statement - a command with arguments - results in the error you saw.


    As an aside, I suggest simplifying your .CreatePipeline() call as follows:

    Pipeline pipeline = runspace.CreatePipeline(
      @"& ""C:\Program Files\tidy 5.8.0\bin\tidy.exe"" teste.html > teste_tidy.html"
    );
    

    Taking a step back:

    Unless you need to manage runspace creation explicitly, you can take advantage of the higher-level PowerShell API, which allocates a default runspace and allows you to simplify your code to:

    using System.Management.Automation;
    
    Console.WriteLine("#------ TidyScript ------#");
    
    using (var ps = PowerShell.Create()) {
    
      var results = ps.AddScript(
        @"& ""C:\Program Files\tidy 5.8.0\bin\tidy.exe"" teste.html > teste_tidy.html"
      ).Invoke();
    
      foreach (var result in results)
      {
        Console.WriteLine(result.ToString());
      }
    }