2

I have a PowerShell script which communicates with a REST server. This script only works in PowerShell 6. I want to call it from C#, because the C# program needs the info from the REST server, and I don't want to rewrite the REST code in C#.

So basically, I want to run a PowerShell script from C#. However, in C#, PowerShell.Create(); creates a PowerShell instance that uses PowerShell 5.
I already replaced pwsh.exe in the default folder, deleted PowerShell 5 everywhere etc. and when I shift+right click anywhere to use "Run PowerShell here" I get a PowerShell 6 window. But for some reason, C# sticks to using PowerShell 5, when using the PowerShell class.

This is the PowerShell code I want to reuse:

function Get-JSONWebToken {
    param (
      [Parameter(Mandatory=$True)][string] $BaseUri,
      [Parameter(Mandatory=$True)][string] $ApiToken
    )
    if ($PSVersionTable.PSVersion.Major -lt 6) {
        $version = $PSVersionTable.PSVersion
        Throw "Your PowerShell version is: $version. Please upgrade to PowerShell 6 or above"
    }

    $uri = "$BaseUri/auth/token"    
    $bodyJson = ConvertTo-Json @{token = $ApiToken} -Compress

    Write-Host "Authenticating ..."
    try {
        $response = Invoke-RestMethod `
            -Uri $uri `
            -Method Post `
            -ContentType "application/json" `
            -Body $bodyJson
        $jwtToken = $response.token
        $secureToken = ConvertTo-SecureString $jwtToken -AsPlainText -Force
        return $secureToken    
    }
    catch {
        #handle error
    }
}

So now I am trying to call PowerShell 6 manually, importing a module first and then using it. Here are my three attempts, which are all supposed to do the same thing: call Get-JSONWebToken (in rest-api.psm1) and retrieve the output correctly.

C# version 1, using PowerShell class:

         ps = PowerShell.Create();
         //module import...
         PSCommand cmd = ps.Commands.AddCommand("Get-JSONWebToken");
         cmd.AddParameter("baseUri", baseUri);
         cmd.AddParameter("apiToken", apiToken);
         ps.Invoke();

This always runs on PowerShell 5 for some reason so it can't be used.

C# version 2, using a Process instead

         Process ps6 = new Process();
         ps6.StartInfo = new ProcessStartInfo {
             FileName = "C:/Program Files/PowerShell/6/pwsh.exe",
             Arguments = "-Command {\n" +
                           "Import-Module " + modulePath + ";\n" +
                           "Get-JSONWebToken " + apiToken + ";\n" +
                         "}",
             UseShellExecute = false,
             RedirectStandardOutput = true,
             RedirectStandardError = true,
             CreateNoWindow = false
         };
         ps6.Start()

This runs on PowerShell 6, but only outputs the arguments I passed, and not the output of Get-JSONWebToken.

C# version 3: Calling PS6 from PS5 from C#

         PSCommand cmd = ps.Commands.AddCommand("C:/Program Files/PowerShell/6/pwsh.exe");
         ScriptBlock sb = ScriptBlock.Create("Import-Module " + modulePath + "; Get-JSONWebToken " + apiToken + ";");
         cmd.AddParameter("Command", sb);
         ps.Invoke();

This doesn't work at all:

Result: Usage: pwsh[.exe] [[-File] <filePath> [args]]
Result:                   [-Command { - | <script-block> [-args <arg-array>]
Result:                                 | <string> [<CommandParameters>] } ]
Result:                   [-ConfigurationName <string>] [-CustomPipeName <string>]
...
...

PowerShell version:

        $pinfo = New-Object System.Diagnostics.ProcessStartInfo
        $pinfo.FileName = $Ps6Path
        $pinfo.RedirectStandardError = $true
        $pinfo.RedirectStandardOutput = $true
        $pinfo.CreateNoWindow = $false
        $pinfo.Arguments = "-Command {Import-Module <myPath>\rest-api.psm1; Get-JSONWebToken 123inputStringExample;}"

        $p = New-Object System.Diagnostics.Process
        $p.StartInfo = $pinfo
        $p.Start() | Out-Null
        $p.WaitForExit()
        $stdout = $p.StandardOutput.ReadToEnd()
        $stderr = $p.StandardError.ReadToEnd()
        Write-Host "stdout: $stdout"
        Write-Host "stderr: $stderr"
        Write-Host "exit code: " + $p.ExitCode

This also only outputs the arguments I passed when called either from C# or from PS6 or PS5

Finni
  • 429
  • 5
  • 17
  • 3
    Rewriting powershell scripts in any .NET language is trivially easy, as PS1 scripts can directly call into .NET Framework code. Besides that, what about trying out the [PowerShell class](https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.powershell?view=pscore-6.2.0)? – MindSwipe Nov 20 '19 at 12:45
  • 1
    @MindSwipe See second paragraph: *"PowerShell.Create(); creates a PowerShell instance that uses PowerShell 5."*. Regarding the rewriting: I am using `Invoke-RestMethod` to which I haven't found an equally easy to use pendant in C# yet, hence the interest in calling the PS script instead. – Finni Nov 20 '19 at 12:49
  • Are we looking at different versions of the page or at different pages entirely? The Create method documentation states this: *Create() Constructs an empty PowerShell instance; a script or command must be added before invoking this instance* – MindSwipe Nov 20 '19 at 12:53
  • Can you add your PowerShell script to the question? Simply googling the Invoke-RestMethod shows me that it is quite similar to sending a WebRequest via a HttpClient – MindSwipe Nov 20 '19 at 12:55
  • @MindSwipe all these code passages are different attempts to achieve the same thing, I edited my question it to clarify that. And what about the PowerShell constructor? I found no (trivial) way to specify the PowerShell version when creating an instance, if that's why you refer to the docs. – Finni Nov 20 '19 at 13:12
  • Have you tried installing the `Microsoft.PowerShell.SDK` nuget package and referencing that? The stuff in `System.Management.Automation` only goes up to Powershell 5.1. Powershell 6 is Core and has some restrictions. – Palle Due Nov 20 '19 at 13:36
  • Are you using .NET Core or .NET Framework? And which version of? – MindSwipe Nov 20 '19 at 14:05
  • @MindSwipe I am using .NET Framework, v. 4.7 – Finni Nov 21 '19 at 10:18
  • I tried to rewrite the PowerShell code in C#, but that didn't work out too well either... [see here](https://stackoverflow.com/questions/58974020/c-sharp-equivalent-for-powershell-invoke-restmethod-token) – Finni Nov 21 '19 at 11:36

1 Answers1

0

This doesn't technically solve the problem, but I did as @MindSwipe suggested and rewrote the code in C# entirely. It wasn't trivially easy but it's a nice and elegant solution in the end.

If you have an idea on how to solve this question properly, please post it here as I'm still interested in how to call stuff on PowerShell 6 from C#.

Finni
  • 429
  • 5
  • 17