月曜日, 8月 18, 2025
月曜日, 8月 18, 2025
- Advertisment -
ホームニューステックニュースWindows標準だけでGUIを作成 ― PowerShell+.NET Framework

Windows標準だけでGUIを作成 ― PowerShell+.NET Framework


はじめに

Windows環境では、Visual Studioなど特別な開発環境を用意しなくても、標準搭載されている PowerShell 5.x.NET Framework 4.x を使って簡単にGUIアプリケーションを作ることができる。
例えば、業務ツールにちょっとした入力フォームやボタンを追加したい場合、PowerShellでWinFormsやWPFを利用すれば数行のコードで実現可能。
さらに、同梱されている csc.exe(C#コンパイラ) を使えば、C#のコードを書く必要はあるが EXE化 することも可能。

本記事では、

  1. PowerShell + WinForms
  2. PowerShell + WPF
  3. C#をcsc.exeでコンパイル(WinForms版)
  4. 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-TypePowerShellプロセス内でC#コードをその場コンパイルして読み込むコマンドである。
外部EXEを作らずに .NET の機能を直接呼べるため、配布はps1だけで済み、処理の重い部分だけC#に寄せて高速化といった使い方もできる。

  • Add-Type -TypeDefinition '' -Language CSharp で型を生成(プロセス内にロード)。
  • 必要に応じて -ReferencedAssemblies で参照DLLを追加(WinFormsなら System.Windows.Forms / System.Drawing など)。
  • 生成された型は [名前空間.クラス名]::メソッド() の形で PowerShell から呼べる。
  • 同じ型名を再定義(Add-Type)するとエラーになるため、試行錯誤時は名前空間/クラス名を変えるか、セッションを再起動する。

https://learn.microsoft.com/powershell/module/microsoft.powershell.utility/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



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -