前提
こちら で紹介されているように PowerShell で Add-Type
コマンドレットを介して C# のコードが実行できます。
問題点
上記の記事でもふれられているように C# コードを修正して Add-Type
を再実行すると
Add-Type : 型を追加できません。型名 'Example' は既に存在しています。
なるエラーがでます。
対策
ワーク アラウンドとしては
- 別プロセスの
powershell.exe
を起動してAdd-Type
しなおす。 -
Start-Job
をつかう。ただし、Console.Write()
などのコンソールへの直接的出力はすてられるという制限あり。
別プロセスの powershell.exe
を起動して Add-Type
しなおす
別プロセスの powershell.exe
を起動して Add-Type
しなおすのはめんどうなので、多少なりともの自動化として、ラッパー関数をかいてみました。ここ のサンプル コードもエラーなしで実行できるように culture にもきをくばってみました。
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"
}
使用例
上記の記事よりおかりしまして、
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()
などのコンソールへの直接的出力はすてられるという制限があります。
$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;
からのぶんしかでてきません。
補足
コードを実行させたいだけなら
C:\Windows\Microsoft.NET\Framework\v4.0.30319\csc.exe .\example.cs
とすれば .\example.exe
が生成されるのでした (2020 年 7 月現在) 。
ただ、このばあい ここ のサンプル コードは Main()
の最初に
System.Threading.Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");
を追加することでしか正常に実行させられませんでした。 PS のコマンドラインで
[System.Threading.Thread]::CurrentThread.CurrentCulture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US')
.\example.exe
とするのは効果がありませんでした。