3

Wanted to know if there is a reason that Powershell needs a special library (in System.Management.Automation NS) to be invoked from C# code? I have this code:

        Process p = new Process();
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = true;
        p.StartInfo.RedirectStandardError = true;

        string trackerPath = "C:\\Users\\bernam\\Downloads\\test_scripts\\test_scripts\\Mindaugas_Scripts\\test.ps1";
        p.StartInfo.FileName = "Powershell.exe";
        p.StartInfo.Arguments = " -file " + trackerPath;

        Console.WriteLine(p.StartInfo.FileName + p.StartInfo.Arguments);

        p.Start();

        string output = p.StandardOutput.ReadToEnd();

And it does not seem to be working - output is not returned back. However all these scenarios work fine (produce desired output):

  • p.StartInfo.FileName = "Powershell.exe"; p.StartInfo.Arguments = "get-service"; - works fine.
  • Invoking the PS script from the command line works fine as well (invocation from CMD):

    >Powershell.exe -file "C:\Users\<shortened>\test.ps1"
    1
    2
    

The powershell code inside the script:

1..10 | % { Write-Host $_  ; sleep -m 500}

I know that there is a recommendation to use PowerShell class in System.Management.Automation namespace. What is interesting to me - is why? And is it possible to use PS without that Class? Maybe my code is simply wrong?

Mindaugas Bernatavičius
  • 3,757
  • 4
  • 31
  • 58
  • 1
    after p.Start, you are reading to end, but it may not have finished starting powershell when you read to end.. – BugFinder Jul 24 '17 at 13:48
  • Seems like this user uses an external BAT file to kickoff PS - http://www.nootn.com.au/2013/01/run-powershell-from-net-program-without.html – Mindaugas Bernatavičius Jul 24 '17 at 13:48
  • @thepip3r - the path prints correctly to the console, this is the result: `Powershell.exe -file C:\Users\bernam\Downloads\test_scripts\test_scripts\Mindaugas_Scripts\test.ps1` – Mindaugas Bernatavičius Jul 24 '17 at 13:52
  • 2
    Any reason not just to use the API (`System.Management.Automation`) directly in C# rather than kicking off a `powershell.exe` process? – Mathias R. Jessen Jul 24 '17 at 13:57
  • @BugFinder - adding `Task.Delay(5000).Wait();` did not help. Also it would thrown an exception if the process was not started: System.InvalidOpertionException – Mindaugas Bernatavičius Jul 24 '17 at 13:57
  • @MathiasR.Jessen - Well knowledge gathering mainly. Not sure what is so special about PS that I can't make it work w/o the managing .dll. Coming from the Linux world, I have never seen exceptions to external process invocation, so this seems to be an interesting case. But then again, maybe there is something I overlook. – Mindaugas Bernatavičius Jul 24 '17 at 14:01
  • 1
    @MindaugasBernatavičius The simple answer to your overall question is that there is no reason why you cannot start the process independently without the additional DLL. The DLL is going to allow you to interact with the PowerShell process at a granular level. If you just start a process you basically have a blackbox that returns a value. With the DLL you could, for example, queue up many commands in a pipeline via your C# code. These commands would be inserted via logic in your C# code so therefor they could be completely different per use case. – Ty Savercool Jul 24 '17 at 14:08
  • @TySavercool I agree, there are many reasons to use the that DLL. Type-safety as pointed out by Pavel (although I'm not sure what that means in this context, I think he meant "typed" - instead of everything being a string, the underlying DLL would give me abstractions of particular type). However there are reasons not to use it - `.net core` as pointed out byt CodedBeard and I would add - interface uniformity / modularity as a 2nd (ie. I know I will move from PS soon in that code piece and having used types from that DLL would require more code-rewrite than the use of vanilla "process" type). – Mindaugas Bernatavičius Jul 24 '17 at 14:37
  • @MindaugasBernatavičius I really meaned type-safe https://stackoverflow.com/questions/2437469/what-is-type-safe-in-net and it is like you said - you are not working with strings but actual Types. – Pavel Pája Halbich Jul 24 '17 at 14:59

2 Answers2

4

Yes this is certainly possible. Try replacing Write-Host with Write-Output in the script you are calling. Write-Host does not write to the standard streams, it writes to a console host. If you are not running a console host (cmd/powershell console), the output will just disappear. In general it is best to avoid using Write-Host all together. The reason most people recommend using the System.Management.Automation method, is it simplifies many interactions that you may wish to use with powershell, rather than trying to parse the returns from powershell.exe, there are however valid reasons for calling the exe directly, for example if you are using .net core, which doesn't currently fully support System.Management.Automation.

CodedBeard
  • 862
  • 8
  • 19
  • Write-Host worked as well, but thanks a lot for the suggestion. I will read up about where the output goes for each cmd'let I use. The cause of the issue was in the STDERR all along, but I did not see it, since I was not printing it out :) ... I did not specify `" -executionpolicy bypass"` and it was giving me the execution disabled error :) – Mindaugas Bernatavičius Jul 24 '17 at 14:27
3

You should invoke PS scripts from System.Management.Automation NS, because then you can work with results and exceptions having type-safe environment.

EDIT also, you can use asynchronous execution or you can execute PS scripts on a Remote server. Generally, you have much more possibilities using that library.

You can take a look at my example below.

string script = "";                    // PS script content
List<ScriptParameter> ExecParamList;   // parameters

var result = new ExecPSResult();       // Class with list of outputs and errors
using (var powerShellInstance = PowerShell.Create())
{
    powerShellInstance.AddScript(script);

    foreach (var execParamModel in ExecParamList)
    {
        powerShellInstance.AddParameter(execParamModel.ParamName, 
                                        execParamModel.ParamValue ?? "$null");
    }

    var psOutput = powerShellInstance.Invoke();

    result.Errors =
        powerShellInstance.Streams.Error.Select(e => 
            ExecException.MakeFromException(e.Exception)  // just make models for exeptions
        ).ToList();

    result.OutputItems =
        psOutput.Where(
            outputItem =>
                outputItem != null && 
                outputItem.TypeNames[0] != "System.ServiceProcess.ServiceController")
            .Select(e => new ExecOutput
            {
                ObjectTypeFullName = e.BaseObject.GetType().FullName,
                ObjectValue = e.BaseObject   //This is typeof(Object)
            }).ToList();
}
return result;
Community
  • 1
  • 1
Pavel Pája Halbich
  • 1,529
  • 2
  • 17
  • 22