0

I want to develop a PowerShell application that is able to invoke certain commands depending on the previous command. Also, the next command must be able to take the result from the previous one and do some stuff with it. Therefore, I use the PowerShell.SDK from Microsoft.

Currently, I have the following approach with which the commands are executed one after another and waiting for each other. But do not care about the results.

public Task Execute(IJobExecutionContext context)
{
    var details = (IList<(string modulepath,string arguments)>)context.JobDetail.JobDataMap["Commands"];
    
    PowerShell ps = PowerShell.Create();
    foreach (var detail in details)
    {
        ps.Commands.AddCommand("Start-Process").AddParameter("FilePath", detail.modulepath).AddParameter("ArgumentList", detail.arguments).AddParameter("Wait");
    }
    
    ps.Invoke();
    return Task.FromResult(true);
}

Is there any easy why to get the Errors or ExitCode from the single commands? Or am I following the wrong way? Thanks for any suggest. What would be the correct and best way to do that?

Florian
  • 1,019
  • 6
  • 22
Christoph
  • 155
  • 1
  • 2
  • 10
  • 2
    Why are you invoking a PowerShell command to run a process rather than using the native C# `Process.Start` method? – gunr2171 Apr 12 '23 at 14:31
  • Powershell has [pipelines](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pipelines?view=powershell-7.3) for when you want to send output from one function to another. I am relatively new to powershell but to be very honest I think you should stick with that vs. having commands tracking internal state and behaving differently. Sounds like it would be hard to debug – Narish Apr 12 '23 at 14:31
  • You're currently building a pipeline (eg. `Start-Process file -ArgumentList ... -Wait |Start-Process file2 -ArgumentList ... -Wait | ...`), which is probably _not_ what you want – Mathias R. Jessen Apr 12 '23 at 14:31
  • Tangentially related, [System.CommandLine](https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial) is still in preview but is something you should probably have on your radar if you do a lot of cli apps – Narish Apr 12 '23 at 14:32
  • @gunr2171 I use this because there is not only Start-Process. That is just this example. There can also be other things in the future. – Christoph Apr 12 '23 at 14:36
  • @MathiasR.Jessen why isn't this what I need or want? I have a hand full of commands which I want to execute after each other. What would be the better approach? Thankful for any idea :) – Christoph Apr 12 '23 at 14:38
  • 1
    Pipelines are for I/O, but there's no exchange of data between the individual commands in your example. You simply want a list of individual statements, eg. `Start-Process ...; Start-Process ...; Start-Process ...`. To construct such a script programmatically, add a call to `AddStatement()` after the last parameter on each command invocation (eg. change `.AddParameter("Wait");` -> `.AddParameter("Wait").AddStatement();`) – Mathias R. Jessen Apr 12 '23 at 14:40
  • How can I handle the results of the previous commands? Something like stop on error? E.g. if there is an error I want to send a mail with the error code from the command that failed. – Christoph Apr 12 '23 at 14:44
  • Re error handling when using the PowerShell SDK: see [this answer](https://stackoverflow.com/a/69051991/45375). – mklement0 Apr 12 '23 at 15:32

1 Answers1

0

If you use the native process.Start method as gunr2171 suggested then this could be an example approach to grabbing the output:

var process = new Process
{
    StartInfo = new ProcessStartInfo()
    {
        FileName = @"C:\windows\system32\windowspowershell\v1.0\powershell.exe" //path to powershell
        Arguments = $"{command_argument_list_here}", //put commands & arguments here
        CreateNoWindow = true,
        RedirectStandardOutput = true,
        UseShellExecute = false  
    }
};

process.start();

// reading the standard output of your command
string outputLine = "";
while (!proccess.StandardOutput.EndOfStream)
{
    outputLine = await proccess.StandardOutput.ReadLineAsync();
    Console.WriteLine(outputLine);
}

or

string entireOutput = process.StandardOutput.ReadToEnd();

This isn't fully updated for your example above but gives an idea of how to read the output of single commands