Windowsで社内向けの小さい確認ツールや、CSVを選んで中身を見る程度のGUIがほしいことがあります。
このくらいの用途なら、Visual Studioでプロジェクトを作らなくても、Windowsに入っているPowerShell 5.1と.NET Frameworkだけで作れます。
この記事では、次の4パターンを扱います。
- PowerShell + WinFormsで最小GUIを作る
- PowerShell + WPFでXAMLベースのGUIを作る
- Windows標準の
csc.exeでC#コードをEXE化する - PowerShellの
Add-TypeでC#をインプロセス実行する
前提
想定環境は次の通りです。
| 項目 | 内容 |
|---|---|
| OS | Windows 10 / 11 |
| PowerShell | Windows PowerShell 5.1 |
| .NET | .NET Framework 4.x |
| 追加インストール | なし |
PowerShell 7ではなく、Windowsに標準で入っているWindows PowerShell 5.1を前提にしています。
確認する場合は次のコマンドを使います。
$PSVersionTable.PSVersion
[System.Environment]::Version
どれを使うか
先に使い分けです。
| 方法 | 向いている用途 | 配布 |
|---|---|---|
| PowerShell + WinForms | ボタン、入力欄、一覧などの簡易ツール | .ps1 |
| PowerShell + WPF | レイアウトを少し整えたいツール | .ps1 |
C# + csc.exe
|
ダブルクリックで起動するEXEにしたい | .exe |
PowerShell + Add-Type
|
一部だけC#で書きたい、.NET APIを直接使いたい | .ps1 |
個人的には、まずWinFormsで十分かを見ます。
画面が複雑になってきたらWPF、利用者にPowerShellを意識させたくないならEXE化、処理の一部だけC#に寄せたいならAdd-Type、という判断でよいと思います。
1. PowerShell + WinForms
一番小さい例です。
Add-Type -AssemblyName System.Windows.Forms
$form = [System.Windows.Forms.Form]::new()
$form.Text = 'WinForms'
$form.Width = 300
$form.Height = 150
$button = [System.Windows.Forms.Button]::new()
$button.Text = 'Click'
$button.Dock = 'Fill'
$button.Add_Click({
[System.Windows.Forms.MessageBox]::Show('Hello from PowerShell')
})
$form.Controls.Add($button)
$form.ShowDialog() | Out-Null
WinFormsは古い技術ですが、PowerShellから触るにはかなり手軽です。
業務用の小さいGUIなら、次のような用途に向いています。
- ファイル選択
- 入力値の確認
- ボタン実行
- CSVや一覧の表示
- 処理結果のメッセージ表示
2. PowerShell + WPF
WPFを使うと、XAMLで画面を定義できます。
Add-Type -AssemblyName PresentationFramework
[xml]$xaml = @"
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WPF XAML" Width="300" Height="150">
<Grid>
<Button Name="Button" Content="Click" Width="100" Height="32" />
</Grid>
</Window>
"@
$reader = [System.Xml.XmlNodeReader]::new($xaml)
$window = [Windows.Markup.XamlReader]::Load($reader)
$button = $window.FindName('Button')
$button.Add_Click({
[System.Windows.MessageBox]::Show('Hello from WPF')
})
$window.ShowDialog() | Out-Null
WinFormsよりコード量は増えますが、レイアウトをXAML側に分けられるのが利点です。
ただし、PowerShellだけでWPFを本格的に書き続けるとつらくなります。
画面が大きくなるなら、素直にC#のWPFプロジェクトにした方が保守しやすいです。
3. CSVを選んで一覧表示する
実務寄りの例として、CSVを選択してDataGridViewに表示します。
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Data
function Convert-CsvToDataTable {
param(
[Parameter(Mandatory)]
[string]$Path
)
$rows = @(Import-Csv -Path $Path)
$table = [System.Data.DataTable]::new()
if ($rows.Count -eq 0) {
return $table
}
foreach ($name in $rows[0].PSObject.Properties.Name) {
[void]$table.Columns.Add($name)
}
foreach ($row in $rows) {
$dataRow = $table.NewRow()
foreach ($name in $row.PSObject.Properties.Name) {
$dataRow[$name] = $row.$name
}
[void]$table.Rows.Add($dataRow)
}
return $table
}
$form = [System.Windows.Forms.Form]::new()
$form.Text = 'CSV Viewer'
$form.Width = 800
$form.Height = 600
$button = [System.Windows.Forms.Button]::new()
$button.Text = 'CSVを開く'
$button.Dock = 'Top'
$grid = [System.Windows.Forms.DataGridView]::new()
$grid.Dock = 'Fill'
$grid.AutoSizeColumnsMode = 'Fill'
$button.Add_Click({
$dialog = [System.Windows.Forms.OpenFileDialog]::new()
$dialog.Filter = 'CSV Files (*.csv)|*.csv|All Files (*.*)|*.*'
if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$grid.DataSource = Convert-CsvToDataTable -Path $dialog.FileName
}
})
$form.Controls.Add($grid)
$form.Controls.Add($button)
$form.ShowDialog() | Out-Null
CSVのパースは自前で,分割せず、Import-Csvを使う方が安全です。
引用符付きの値やカンマを含む値を考えると、PowerShell標準のCSVパーサに任せる方がよいです。
4. csc.exeでEXE化する
Windowsには.NET Framework付属のC#コンパイラcsc.exeがあります。
これを使うと、Visual Studioなしで小さいEXEを作れます。
$framework = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319'
if (!(Test-Path $framework)) {
$framework = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319'
}
$csc = Join-Path $framework 'csc.exe'
$source = @'
using System;
using System.Windows.Forms;
namespace DemoWinForms
{
internal static class Program
{
[STAThread]
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var button = new Button { Text = "Click", Dock = DockStyle.Fill };
button.Click += (sender, e) => MessageBox.Show("Hello from C#");
var form = new Form { Text = "WinForms EXE", Width = 300, Height = 150 };
form.Controls.Add(button);
Application.Run(form);
}
}
}
'@
$cs = Join-Path $env:TEMP 'demo_winforms.cs'
$exe = Join-Path $PSScriptRoot 'demo_winforms.exe'
$source | Set-Content -Path $cs -Encoding UTF8
& $csc /nologo /target:winexe `
/r:System.Windows.Forms.dll `
/r:System.Drawing.dll `
/out:$exe `
$cs
/target:winexeにすると、起動時にコンソールウィンドウを出さないEXEになります。
この方法は、利用者に.ps1を実行させたくない場合に便利です。
ただし、大きいアプリをこれで作るより、普通にC#プロジェクトを作った方がよいです。
5. Add-TypeでC#をPowerShell内に読み込む
Add-Typeを使うと、PowerShellの中でC#コードをコンパイルして、そのまま呼び出せます。
Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing -TypeDefinition @'
using System;
using System.Windows.Forms;
namespace Demo
{
public static class UI
{
public static void Show()
{
var form = new Form { Text = "Add-Type", Width = 300, Height = 150 };
var button = new Button { Text = "Click", Dock = DockStyle.Fill };
button.Click += (sender, e) => MessageBox.Show("Hello from Add-Type");
form.Controls.Add(button);
form.ShowDialog();
}
}
}
'@ -Language CSharp
[Demo.UI]::Show()
Add-Typeは、次のようなときに使えます。
- PowerShellだけでは書きにくい処理をC#で書く
- .NETの型をまとめて扱う
- GUIはPowerShell、重い処理だけC#に寄せる
注意点として、同じPowerShellセッション内で同じ名前空間・同じクラス名を再定義するとエラーになります。
試行錯誤するときは、PowerShellを起動し直すか、クラス名を変えると楽です。
6. GUIスクリプトでハマりやすい点
実行ポリシー
.ps1を実行するとき、環境によっては実行ポリシーで止まります。
一時的に実行するなら、次のようにプロセス単位で緩める方法があります。
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
恒久的に変更する必要があるかは、環境の運用ルールに合わせて判断してください。
STAThread
クリップボードや一部のダイアログ、WPFまわりではSTAが必要になることがあります。
PowerShellから別プロセスで起動する場合は、-STAを付けて実行できます。
powershell.exe -STA -File .\tool.ps1
C#でEXE化する場合は、Mainに[STAThread]を付けます。
[STAThread]
private static void Main()
{
// ...
}
PowerShell 5.1前提にする
この記事の内容は、Windows PowerShell 5.1と.NET Frameworkを前提にしています。
PowerShell 7は.NETの実行基盤が違うため、WinForms/WPFやAdd-Typeまわりの挙動が変わることがあります。
「Windows標準だけ」で配る小ツールなら、まずはPowerShell 5.1前提で書く方が説明しやすいです。
本格アプリにしない
PowerShell GUIは便利ですが、本格的なアプリ開発環境ではありません。
次のような状態になったら、C#のWPF/WinForms/WinUIなどに移した方がよいです。
- 画面数が増えた
- 状態管理が複雑になった
- 非同期処理が多い
- エラー処理やログが重要になった
- 複数人で保守する
まとめ
Windows標準だけでも、PowerShellからWinForms/WPFを呼べば簡易GUIツールは作れます。
使い分けは次の感覚です。
| やりたいこと | 選択肢 |
|---|---|
| とにかく小さくGUIを作る | PowerShell + WinForms |
| XAMLで少し整えたい | PowerShell + WPF |
| ダブルクリックで配りたい |
csc.exeでEXE化 |
| 一部だけC#で書きたい | Add-Type |
業務用の小さい確認ツールや、自分用の作業補助ツールなら十分使えます。
一方で、長く保守するアプリに育ちそうなら、早めに通常のC#プロジェクトへ移すのが無難です。




