Reputation: 429
I want to execute Powershell
script in C#
application.
For this I'm using Microsoft.PowerShell.5.ReferenceAssemblies
package (because of net48
requirements).
I'd like to pipe objects from .Net
to script but have no success.
static void Main(string[] args)
{
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
var ps = PowerShell.Create();
ps.Runspace = runspace;
var pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(@"example.ps1");
pipeline.Input.Write("1");
pipeline.Input.Write("2");
pipeline.Input.Flush();
var res = pipeline.Invoke();
foreach (var xx in res) {
Console.WriteLine(xx);
}
}
Example.ps1:
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline=$true)]
$p
)
Begin {
Write-Output "Start"
}
Process {
Write-Output "Process $_"
}
End {
Write-Output "End"
}
Actual output:
Start
Process
End
Expected output:
Start
Process 1
Process 2
End
Upvotes: 1
Views: 375
Reputation: 10083
After a load of empirical testing, I've found the following works for me:
pipeline.Input.Write("1");
pipeline.Input.Write("2");
pipeline.Commands.Add(".\\example.ps1");
That is, use pipeline.Commands.Add
, not pipeline.Commands.AddScript
. Even though your command is the name of a script file, it's a command not a script!
Also, note the pipeline.Input.Write
comes before the pipeline.Commands.Add
, and I've removed the pipeline.Input.Flush()
- these all seemed to break things in different ways.
The full code becomes:
static void Main(string[] args)
{
var runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
var ps = PowerShell.Create();
ps.Runspace = runspace;
var pipeline = runspace.CreatePipeline();
pipeline.Input.Write("1");
pipeline.Input.Write("2");
pipeline.Commands.Add(".\\example.ps1");
var res = pipeline.Invoke();
foreach (var xx in res) {
Console.WriteLine(xx);
}
}
and the application writes this out to the console:
Start
Process 1
Process 2
End
The corollary is, it looks like AddScript
doesn't consume pipeline input.
For completeness, here's some of the other (broken) tests I did along the way:
// fails - original code
//pipeline.Commands.AddScript(".\\example.ps1");
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
//pipeline.Input.Flush();
// Start
// Process
// End
// test - works for a command, but requires in-script parameters
//pipeline.Commands.AddScript("1, 2 | write-output");
// 1
// 2
// test - works for a script, but requires in-script parameters
//pipeline.Commands.AddScript("1, 2 | .\\example.ps1");
// Start
// Process 1
// Process 2
// End
// test - works for a command with pipeline input
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
//pipeline.Commands.Add("write-output");
// 1
// 2
// test - fails with AddScript and mandatory parameters on script
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
//pipeline.Commands.AddScript("write-output");
// System.Management.Automation.ParameterBindingException: 'Cannot process command because of one
// or more missing mandatory parameters: InputObject.'
// test - fails with AddScript and optional parameters on script
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
//pipeline.Commands.AddScript(".\\example.ps1");
// Start
// Process
// End
// fails - flush kills pipelne
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
//pipeline.Input.Flush();
//pipeline.Commands.Add(".\\example.ps1");
// Start
// Process
// End
// fails - pipeline written after script
//pipeline.Commands.AddScript(".\\example.ps1");
//pipeline.Input.Write("1");
//pipeline.Input.Write("2");
// Start
// Process
// End
Update
You can turn a string containing a PowerShell script into a "pipeline-able" command as follows (note the second parameter in new Command
- i.e. isScript: true
):
pipeline.Input.Write("1");
pipeline.Input.Write("2");
var scriptText = File.ReadAllText(".\\example.ps1");
pipeline.Commands.Add(
new Command(scriptText, true)
);
// Start
// Process 1
// Process 2
// End
Upvotes: 1