LoginSignup
5

More than 3 years have passed since last update.

PowerShell で C# コードを実行

Last updated at Posted at 2020-07-04

前提

こちら で紹介されているように PowerShell で Add-Type コマンドレットを介して C# のコードが実行できます。

問題点

上記の記事でもふれられているように C# コードを修正して Add-Type を再実行すると

Console
Add-Type : 型を追加できません。型名 'Example' は既に存在しています。

なるエラーがでます。

対策

ワーク アラウンドとしては
* 別プロセスの powershell.exe を起動して Add-Type しなおす。
* Start-Job をつかう。ただし、Console.Write() などのコンソールへの直接的出力はすてられるという制限あり。

別プロセスの powershell.exe を起動して Add-Type しなおす

別プロセスの powershell.exe を起動して Add-Type しなおすのはめんどうなので、多少なりともの自動化として、ラッパー関数をかいてみました。ここ のサンプル コードもエラーなしで実行できるように culture にもきをくばってみました。

PowerShell
function Invoke-CSharp {
    [CmdletBinding(DefaultParameterSetName = 'Code')]
    param(
        [Parameter(Mandatory = $true, ParameterSetName = 'Code', Position = 0)]
        [String] $Code,

        [Parameter(Mandatory = $true, ParameterSetName = 'Path')]
        [ValidateScript({ Test-Path -Path $_ })]
        [String] $FilePath,

        [String] $ClassName,

        [String] $MethodName = 'Main',

        [String[]] $ArgumentList,

        [String] $CultureName = 'en-US'
    )

    if ($FilePath) {
        $Code = Get-Content -Path $FilePath -Raw
    }

    if (! $ClassName) {
        $ClassName = $Code -split '\r?\n' |
            Select-String -Pattern '^\s*public(?:\s\w+)*?\s+class\s+(\w+)' |
            Select-Object -First 1 |
            ForEach-Object -Process { $_.Matches.Groups[1].Value }
    }

    $ArgumentList = $ArgumentList |
        ForEach-Object -Process {
            if ($_ -is [String]) {
                '"' + $_.Replace('"', '`"') + '"'
            } else {
                [String] $_
            }
        }
    [String] $ArgumentList = $ArgumentList -join ', '

    $command = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes(@"
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo('$CultureName')
Add-Type -TypeDefinition @'
$Code
'@ -Language CSharp
[$ClassName]::$MethodName($ArgumentList)  # Qiita の記事の ``` のなかでも \[.*\]:.*\( をふつうにかくと非表示になり、 # をうしろにおくと復活します。なにかバギーです。
"@))

    Start-Process -FilePath powershell.exe -ArgumentList '-NoLogo', '-NoExit', "-EncodedCommand $command"
}

使用例

上記の記事よりおかりしまして、

PowerShell
Invoke-CSharp -Code @'
using System;
public static class TestClass1
{
    public static string TestMethod(string name, string greet)
    {
        string msg = name + "さん、" + greet;

        Console.WriteLine(msg);
        return msg;
    }
}
'@ -MethodName 'TestMethod' -ArgumentList '太郎', 'おはよう' -CultureName 'ja-JP'

自分の用途としては Visual Studio などいれられない、いれたくない環境でもサンプル コードをきがるに「写経」できることです。よって、 -FilePath パラメーターを多用します。

Start-Job をつかう

Console.Write() などのコンソールへの直接的出力はすてられるという制限があります。

PowerShell
$j = Start-Job -ScriptBlock {
    Add-Type -TypeDefinition @'
using System;
public static class TestClass1
{
    public static string TestMethod(string name, string greet)
    {
        string msg = name + "さん、" + greet;

        Console.WriteLine(msg);
        return msg;
    }
}
'@ -Language CSharp

    [TestClass1]::TestMethod('太郎', 'おはよう')
}

Wait-Job -Job $j | Out-Null
Receive-Job -Job $j

この C# コードを「ふつうに」実行すると「太郎さん、おはよう」が二行でます。一行目は Console.WriteLine(msg); から、二行目は return msg; からのです。しかし、この Start-Job をかませた実行では return msg; からのぶんしかでてきません。

補足

コードを実行させたいだけなら

Console
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe .\example.cs

とすれば .\example.exe が生成されるのでした (2020 年 7 月現在) 。

ただ、このばあい ここ のサンプル コードは Main() の最初に

C#
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");

を追加することでしか正常に実行させられませんでした。 PS のコマンドラインで

PowerShell
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US')
.\example.exe

とするのは効果がありませんでした。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5