はじめに
Windows環境では、Visual Studioなど特別な開発環境を用意しなくても、標準搭載されている PowerShell 5.x と .NET Framework 4.x を使って簡単にGUIアプリケーションを作ることができる。
例えば、業務ツールにちょっとした入力フォームやボタンを追加したい場合、PowerShellでWinFormsやWPFを利用すれば数行のコードで実現可能。
さらに、同梱されている csc.exe(C#コンパイラ) を使えば、C#のコードを書く必要はあるが EXE化 することも可能。
本記事では、
- PowerShell + WinForms
- PowerShell + WPF
- C#をcsc.exeでコンパイル(WinForms版)
- C#をPowerShellでインプロセス実行(Add-Type)
の順で、「Windows標準だけ」で完結するGUI作成方法を紹介する。
No. | 方法 | 概要 |
---|---|---|
1 | PowerShell + WinForms | PowerShellでWinFormsのUIを作成。実装・学習コスト低。デザイン自由度は低。 |
2 | PowerShell + WPF | PowerShellでWPFのUIを作成。高機能レイアウト。XAMLなしでも可。 |
3 | C# csc.exeコンパイル | C#のコードを標準ツールでEXE化。1,2より処理速度に期待できる。ユーザーはダブルクリックで簡単に実行可能。 |
4 | C#をPowerShellでインプロセス実行 | C#のコードをPowerShellのプロセス内で実行。1,2より処理速度に期待できる。処理の一部だけC#化することも可能。 |
最小コード例
1. PowerShell + WinForms
-
Add-Type -AssemblyName System.Windows.Forms
が必須。 -
.Add_Click({ ... })
のようにイベントを追加する。 -
Controls.Add()
に追加する順で描画される。
Add-Type -AssemblyName System.Windows.Forms
$form = [Windows.Forms.Form]::new()
$form.Text='WinForms'; $form.Width=300; $form.Height=150
$btn = [Windows.Forms.Button]::new()
$btn.Text='WinForms'; $btn.Dock='Fill'
$btn.Add_Click({ [System.Windows.Forms.MessageBox]::Show('Hello!') })
$form.Controls.Add($btn)
$form.ShowDialog() | Out-Null
2. PowerShell + WPF
-
Add-Type -AssemblyName PresentationFramework
が必須。 - XAMLは別ファイルにすることも可能。
-
.Add_Click({ ... })
のようにイベントを追加する。
Add-Type -AssemblyName PresentationFramework
[xml]$xaml = @"
/ /schemas.microsoft.com/winfx/2006/xaml/presentation"
Title="WPF XAML" Width="300" Height="150">
Wpf" Width="80" Height="30" />
"@
$win = [Windows.Markup.XamlReader]::Load((New-Object System.Xml.XmlNodeReader $xaml))
$btn = $win.FindName("btn")
$btn.Add_Click({ [System.Windows.MessageBox]::Show('Hello!') })
$win.ShowDialog() | Out-Null
3. C#をcsc.exeでコンパイル
- Windows標準の C# コンパイラ
csc.exe
を使用。 - 以下の例では
$src
にC#のコードを記述しているが、別ファイルにすることも可能。
$fx = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319'
if (!(Test-Path $fx)) { $fx = Join-Path $env:WINDIR 'Microsoft.NET\Framework\v4.0.30319' }
$csc = Join-Path $fx 'csc.exe'
$src = @'
using System;
using System.Windows.Forms;
namespace DemoWinForms
{
internal static class Program
{
private static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var btn = new Button { Text = "WinForms", Dock = DockStyle.Fill };
btn.Click += (s, e) => MessageBox.Show("Hello!");
var form = new Form { Text = "WinForms", Width = 300, Height = 150 };
form.Controls.Add(btn);
Application.Run(form);
}
}
}
'@
$cs = Join-Path $env:TEMP 'demo_winforms.cs'
$exe = Join-Path $PSScriptRoot 'demo_winforms.exe'
$src | Set-Content $cs -Encoding UTF8
& $csc /nologo /target:winexe `
/r:System.Windows.Forms.dll /r:System.Drawing.dll `
/out:$exe $cs
4. C# をインプロセス実行(Add-Type)
Add-Type
は PowerShellプロセス内でC#コードをその場コンパイルして読み込むコマンドである。
外部EXEを作らずに .NET の機能を直接呼べるため、配布はps1だけで済み、処理の重い部分だけC#に寄せて高速化といった使い方もできる。
-
Add-Type -TypeDefinition '
で型を生成(プロセス内にロード)。' -Language CSharp - 必要に応じて
-ReferencedAssemblies
で参照DLLを追加(WinFormsならSystem.Windows.Forms
/System.Drawing
など)。 - 生成された型は
[名前空間.クラス名]::メソッド()
の形で PowerShell から呼べる。 - 同じ型名を再定義(Add-Type)するとエラーになるため、試行錯誤時は名前空間/クラス名を変えるか、セッションを再起動する。
Add-Type -ReferencedAssemblies System.Windows.Forms,System.Drawing -TypeDefinition @'
using System;
using System.Windows.Forms;
namespace Demo
{
public static class UI
{
public static void ShowForm()
{
var form = new Form { Text = "WinForms (in-proc)", Width = 300, Height = 150 };
var btn = new Button { Text = "WinForms", Dock = DockStyle.Fill };
btn.Click += (s, e) => MessageBox.Show("Hello from C#!");
form.Controls.Add(btn);
form.ShowDialog();
}
}
}
'@ -Language CSharp
[Demo.UI]::ShowForm()
実践例
(1)CSVを読込んで一覧表示
PowerShell + WinFormsの場合と、PowerShell + WPFの場合で下記の機能を実装する。
- ボタンクリックでファイル選択ダイアログをオープン
- CSVファイルを選択すると一覧表示
PowerShell + WinFormsの場合
読み込んだCSVをDataTable
に変換し、WinFormsのDataGridView
にバインド。
Add-Type -AssemblyName System.Windows.Forms
class CsvHelper {
[string[]] ReadCsv($filePath) {
if (-Not (Test-Path $filePath)) {
throw "File not found: $filePath"
}
return Get-Content $filePath
}
[System.Data.DataTable] ConvertToDataTable($csvData) {
$dataTable = [System.Data.DataTable]::new()
if ($csvData.Length -gt 0) {
$headers = $csvData[0].Split(',')
foreach ($header in $headers) {
$dataTable.Columns.Add($header)
}
for ($i = 1; $i -lt $csvData.Length; $i++) {
$row = $dataTable.NewRow()
$values = $csvData[$i].Split(',')
for ($j = 0; $j -lt $values.Length; $j++) {
$row[$j] = $values[$j]
}
$dataTable.Rows.Add($row)
}
}
return $dataTable
}
}
$form = [System.Windows.Forms.Form]::new()
$form.Text = "CSV Viewer"
$form.Size = [System.Drawing.Size]::new(800, 600)
$dataGridView = [System.Windows.Forms.DataGridView]::new()
$dataGridView.Dock = 'Fill'
$dataGridView.AutoSizeColumnsMode = 'Fill'
$button = [System.Windows.Forms.Button]::new()
$button.Text = "Select CSV File"
$button.Dock = 'Top'
$button.Add_Click({
$openFileDialog = [System.Windows.Forms.OpenFileDialog]::new()
$openFileDialog.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*"
$openFileDialog.Title = "Select a CSV File"
if ($openFileDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
$csvHelper = [CsvHelper]::new()
$csvData = $csvHelper.ReadCsv($openFileDialog.FileName)
$dataTable = $csvHelper.ConvertToDataTable($csvData)
$dataGridView.DataSource = $dataTable
}
})
$form.Controls.Add($dataGridView)
$form.Controls.Add($button)
$form.Add_Shown({$form.Activate()})
[void]$form.ShowDialog()
PowerShell + WPFの場合
読み込んだCSVをDataTable
に変換し、WPFのDataGrid
にバインド。
Add-Type -AssemblyName PresentationFramework
class CsvHelper {
[string[]] ReadCsv($filePath) {
if (-Not (Test-Path $filePath)) {
throw "File not found: $filePath"
}
return Get-Content $filePath
}
[System.Data.DataTable] ConvertToDataTable($csvData) {
$dataTable = [System.Data.DataTable]::new()
if ($csvData.Length -gt 0) {
$headers = $csvData[0].Split(',')
foreach ($header in $headers) {
$dataTable.Columns.Add($header)
}
for ($i = 1; $i -lt $csvData.Length; $i++) {
$row = $dataTable.NewRow()
$values = $csvData[$i].Split(',')
for ($j = 0; $j -lt $values.Length; $j++) {
$row[$j] = $values[$j]
}
$dataTable.Rows.Add($row)
}
}
return $dataTable
}
}
[xml]$xaml = @"
/ /schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CsvViewer" Width="800" Height="600">
"/>
* "/>
0" Content="Select CSV File" Margin="0,0,0,0"/>
" Grid.Row=" 1" AutoGenerateColumns="True" />
"@
$reader = ([System.Xml.XmlNodeReader]::new($xaml))
$window = [Windows.Markup.XamlReader]::Load($reader)
$selectCsvButton = $window.FindName("selectCsvButton")
$dataGrid = $window.FindName("dataGrid")
$selectCsvButton.Add_Click({
$openFileDialog = [Microsoft.Win32.OpenFileDialog]::new()
$openFileDialog.Filter = "CSV Files (*.csv)|*.csv|All Files (*.*)|*.*"
$openFileDialog.Title = "Select a CSV File"
if ($openFileDialog.ShowDialog() -eq $true) {
$csvHelper = [CsvHelper]::new()
$csvData = $csvHelper.ReadCsv($openFileDialog.FileName)
$dataTable = $csvHelper.ConvertToDataTable($csvData)
$dataGrid.ItemsSource = $dataTable.DefaultView
}
})
$window.ShowDialog() | Out-Null
(2)PowerShellで一部の処理をC#化
- 下記のサンプルはベースはPowerShellで一部の重い処理だけC#化するサンプル。
- PowerShellで実装した処理をAction型でC#に渡し、C#内でコールバック実行することも可能。下記のサンプルではUI更新のPowerShell処理をC#に渡し、C#コード内で実行している。
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase
Add-Type -TypeDefinition @"
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Threading;
public class Processor
{
public Task StartProcessingAsync(Action updateStatus, Dispatcher dispatcher)
{
return Task.Run(() => {
try {
// 処理タスクの定義
var tasks = new[] {
new { TaskNo = 1, Duration = 1000 },
new { TaskNo = 2, Duration = 2000 },
new { TaskNo = 3, Duration = 1500 },
new { TaskNo = 4, Duration = 1000 }
};
// 処理開始の通知
// Task.Runはバックグラウンドスレッドで実行されるため、
// Dispatcherを使用してUIスレッドに通知する必要がある
dispatcher.Invoke(() => updateStatus(" Starting Processing..."));
for (int i = 0; i {
// 進捗の計算
double progress = (double)(i + 1) / tasks.Length * 100;
updateStatus(string.Format("Processing... ({0:0.0}%)", progress));
});
}
// 全てのタスクが完了したことを通知
dispatcher.Invoke(() => updateStatus("Processing complete!"));
}
catch (Exception ex)
{
dispatcher.Invoke(() => updateStatus("Error: " + ex.Message));
}
});
}
}
"@ -ReferencedAssemblies @("WindowsBase")
[xml]$xaml = @"
/ /schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Processing Demo" Height="200" Width="300">
100" Height="50" Margin="10">Start Process
" FontSize=" 16" Margin="10">Status: Ready
"@
$reader = New-Object System.Xml.XmlNodeReader $xaml
$window = [Windows.Markup.XamlReader]::Load($reader)
$processButton = $window.FindName("ProcessButton")
$statusText = $window.FindName("StatusText")
$processor = New-Object Processor
$processButton.Add_Click({
$processButton.IsEnabled = $false
$updateStatusAction = [Action[string]]{
param($status)
$statusText.Text = "Status: $status"
}
$task = $processor.StartProcessingAsync($updateStatusAction, $window.Dispatcher)
$task.ContinueWith({
$processButton.IsEnabled = $true
}, [System.Threading.Tasks.TaskScheduler]::FromCurrentSynchronizationContext())
})
$window.ShowDialog() | Out-Null
【補足】csc.exe / PowerShell Add-Type の C# & .NET バージョンについて
1. csc.exe
(C# コンパイラ)
- 公式: csc.exe (C# Compiler)
-
概要: Windows に同梱される C# コンパイラ。
Visual Studio / .NET Framework に付属するものは「.NET Framework のバージョンに対応した C#」が使われます。
.NET Framework | C# バージョン |
---|---|
2.0 / 3.0 | C# 2.0 |
3.5 | C# 3.0 |
4.0 | C# 4.0 |
4.5 / 4.6 | C# 5.0 |
4.6.2 / 4.7 | C# 6.0 |
4.7.2 / 4.8 | C# 7.0 / 7.3 |
csc -version
で手元のコンパイラのバージョンを確認可能。
2. PowerShell Add-Type
PowerShell | 実行基盤 | 利用される C# コンパイラ | C# バージョン目安 |
---|---|---|---|
5.1 | .NET Framework 4.x | csc.exe (Framework 付属) | 7.0 / 7.3 |
PowerShell 5.1 の Add-Type は「.NET Framework 4.8 + C# 7.3」まで。
それ以降の最新 C# 機能を使いたい場合は PowerShell 7 系を使う必要がある。
3. バージョン確認コマンド例
& (Get-Command csc.exe).Source -version
Add-Type -TypeDefinition "public class V { }"
[Microsoft.CSharp.CSharpCodeProvider].Assembly.GetName().Version
Views: 0