Execute PowerShell scripts in C#
Tags: csharp
Published: 2023-02-12 12:59:41

## Intro
This time, I will try executing PowerShell scripts in C#.
There are some way to do this.

For example, I can use Process class.

#### PsAccessor.cs
```csharp
using System.Diagnostics;

namespace AccessPowerShellSample;

public class PsAccessor
{
    public async Task<string?> ExecuteProcess()
    {
        using(var app = new Process())
        {
            app.StartInfo.FileName = "powershell.exe";
            app.StartInfo.Arguments = "echo HelloWorld!!";
            app.EnableRaisingEvents = true;
            app.StartInfo.RedirectStandardOutput = true;
            app.StartInfo.RedirectStandardError = true;
            // Must not set true to execute PowerShell command
            app.StartInfo.UseShellExecute = false;
            app.Start();
            using(var o = app.StandardOutput)
            {
                return await o.ReadToEndAsync();
            }
        }
    }
}
```

I also can "PowerShell" class.

So I will try it in this time.

## Use PowerShell class.
To use "PowerShell", I have to add some NuGet packages.

* System.Management.Automation
* Microsoft.PowerShell.Commands.Diagnostics
* Microsoft.PowerShell.ConsoleHost
* Microsoft.PowerShell.Commands.Utility
* Microsoft.PowerShell.Commands.Management
* Microsoft.WSMan.Management

#### PsAccessor.cs
```csharp
using System.Management.Automation;

namespace AccessPowerShellSample;

public class PsAccessor
{
    public string? GenerateHmacSha512(string inputData, string key)
    {
        using(var ps = PowerShell.Create())
        {
            ps.AddCommand("echo");
            ps.AddArgument("Hello World!");
            var results = ps.Invoke<string>();
            return results.FirstOrDefault();
        }
    }
}
```

## AddScript
How about more complicated case?

For example, I will try generating a hash value by HMAC SHA-512.

#### hashValueGenerator.ps1
```ps1
param(
    [Parameter()]
    [String] $secret_key,
    [String] $message
)
$oHMACSHA512 = New-Object System.Security.Cryptography.HMACSHA512
$oHMACSHA512.key = [Text.Encoding]::ASCII.GetBytes($secret_key)
$signature_rawout = $oHMACSHA512.ComputeHash([Text.Encoding]::ASCII.GetBytes($message))
$signature = ""
$signature_rawout | ForEach-Object { $i = [Convert]::ToString($_,16); if ($i.length -eq 1) { $i ='0' + $i }; $signature += $i }
[Convert]::ToBase64String($signature_rawout)
```

* [PowerShell/HMAC-SHAを使用する方法 - Windowsと暮らす](https://win.just4fun.biz/?PowerShell/HMAC-SHA%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95)

To execute it, I can use "Process" like below.

```cs
public async Task<string?> ExecuteProcess(string key, string message)
{
    using(var app = new Process())
    {
        app.StartInfo.FileName = "powershell.exe";
        app.StartInfo.Arguments = $"$oHMACSHA512 = New-Object System.Security.Cryptography.HMACSHA512;$oHMACSHA512.key = [Text.Encoding]::ASCII.GetBytes('{key}');$signature_rawout = $oHMACSHA512.ComputeHash([Text.Encoding]::ASCII.GetBytes('{message}'));$signature = '';$signature_rawout | ForEach-Object {{ $i = [Convert]::ToString($_,16); if ($i.length -eq 1) {{ $i ='0' + $i }}; $signature += $i }};[Convert]::ToBase64String($signature_rawout)";
        app.EnableRaisingEvents = true;
        app.StartInfo.RedirectStandardOutput = true;
        app.StartInfo.RedirectStandardError = true;
        // Must not set true to execute PowerShell command
        app.StartInfo.UseShellExecute = false;
        app.Start();
        using(var o = app.StandardOutput)
        {
            return await o.ReadToEndAsync();
        }
    }
}
```

Although I can get result, but it's very hard to debug.

I also can use PowerShell script files.

```cs
    public string? GenerateHmacSha512(string key, string message)
    {
        using(var ps = PowerShell.Create())
        {
            ps.AddScript(File.ReadAllText("./hashValueGenerator.ps1"));
            ps.AddParameter("secret_key", key);
            ps.AddParameter("message", message);
            var results = ps.Invoke<string>();
            return results.FirstOrDefault();
        }
    }
```

I can use "AddCommand" to do this.
But I will get error because PowerShell execution policy.

```
Unhandled exception. System.Management.Automation.PSSecurityException: File C:\Users\example\OneDrive\Documents\workspace\AccessPowerShellSample\hashValueGenerator.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at https://go.microsoft.com/fwlink/?LinkID=135170.
...
```
So I think I should use "AddScript".

## Resources
* [PowerShell Class - Microsoft Learn](https://learn.microsoft.com/ja-jp/dotnet/api/system.management.automation.powershell?view=powershellsdk-7.2.0)
* [about_Execution_Policies - Microsoft Learn](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.3)
* [Add Credential support to PowerShell functions - Microsoft Learn](https://learn.microsoft.com/en-us/powershell/scripting/learn/deep-dives/add-credentials-to-powershell-functions?view=powershell-7.3)
* [PowerShellをC#から実行する - 銀の光と碧い空](https://tech.tanaka733.net/entry/2013/12/10/powershell-from-csharp)
* [C# 上で PowerShell スクリプトを実行 - ++C++; // 未確認飛行 C](https://ufcpp.net/study/powershell/interop.html)