[備忘録] PowerShellでカスタムプログレスバーを作成してみた

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/68e80dec-4cb8-4aa2-b89c-f4864ac8edd1.png)

前回の記事では、PowerShellの標準コマンド `Write-Progress` を使用してプログレスバーを表示する方法を紹介しました。今回は、Windows Formsを使用してより柔軟でカスタマイズ可能なプログレスバーを作成してみたいと思います。

https://qiita.com/Tadataka_Takahashi/items/79c8ff3a240b2007e821

## 標準のプログレスバーの制約

PowerShellの `Write-Progress` コマンドは手軽に使える反面、以下のような制約があります：

- デザインのカスタマイズが困難
- 複数のプログレスバーを同時に表示するのが複雑
- 詳細な制御が難しい

## Windows Formsを使用したカスタムプログレスバー

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/fcea61b3-114b-41ab-95c8-5b0d10229287.png)

Windows Formsを使用することで、これらの制約を解決できます。

### 基本的な実装

```powershell
Add-Type -AssemblyName System.Windows.Forms

# フォームの作成
$form = New-Object Windows.Forms.Form
$form.Text = "カスタムプログレスバー"
$form.Size = New-Object Drawing.Size(400, 100)
$form.StartPosition = "CenterScreen"

# プログレスバーの作成
$progressBar = New-Object Windows.Forms.ProgressBar
$progressBar.Minimum = 0
$progressBar.Maximum = 100
$progressBar.Value = 0
$progressBar.Dock = "Fill"

# フォームにプログレスバーを追加
$form.Controls.Add($progressBar)
$form.Show()

# プログレス処理のシミュレーション
for ($i = 1; $i -le 100; $i++) {
    $progressBar.Value = $i
    [System.Windows.Forms.Application]::DoEvents()
    Start-Sleep -Milliseconds 50
}

$form.Close()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9168c62b-86e3-4e14-ad54-2a7549c15d76.png)


### より詳細なカスタマイズ例

```powershell
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# フォームの詳細設定
$form = New-Object Windows.Forms.Form
$form.Text = "高度なプログレスバー"
$form.Size = New-Object Drawing.Size(500, 150)
$form.StartPosition = "CenterScreen"
$form.BackColor = [System.Drawing.Color]::LightGray

# ラベルの追加
$label = New-Object Windows.Forms.Label
$label.Text = "処理中..."
$label.Location = New-Object Drawing.Point(20, 20)
$label.Size = New-Object Drawing.Size(460, 30)
$label.Font = New-Object Drawing.Font("Arial", 12, [System.Drawing.FontStyle]::Bold)
$form.Controls.Add($label)

# プログレスバーの詳細設定
$progressBar = New-Object Windows.Forms.ProgressBar
$progressBar.Location = New-Object Drawing.Point(20, 60)
$progressBar.Size = New-Object Drawing.Size(460, 30)
$progressBar.Minimum = 0
$progressBar.Maximum = 100
$progressBar.Value = 0
$progressBar.Style = "Continuous"
$form.Controls.Add($progressBar)

# パーセンテージラベル
$percentLabel = New-Object Windows.Forms.Label
$percentLabel.Location = New-Object Drawing.Point(20, 95)
$percentLabel.Size = New-Object Drawing.Size(100, 20)
$percentLabel.Text = "0%"
$form.Controls.Add($percentLabel)

$form.Show()

# 処理のシミュレーション
for ($i = 1; $i -le 100; $i++) {
    $progressBar.Value = $i
    $percentLabel.Text = "$i%"
    $label.Text = "処理中... ステップ $i/100"
    [System.Windows.Forms.Application]::DoEvents()
    Start-Sleep -Milliseconds 100
}

$label.Text = "完了！"
Start-Sleep -Seconds 2
$form.Close()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/416fc28d-aafa-42ec-8804-b7e60cb3f3d0.png)


## 実際の処理での応用例

ファイル処理などの実際の作業でプログレスバーを使用する例：

```powershell
function Show-FileProcessProgress {
    param(
        [string[]]$FilePaths
    )
    
    Add-Type -AssemblyName System.Windows.Forms
    Add-Type -AssemblyName System.Drawing
    
    $form = New-Object Windows.Forms.Form
    $form.Text = "ファイル処理進捗"
    $form.Size = New-Object Drawing.Size(500, 200)
    $form.StartPosition = "CenterScreen"
    
    # 現在のファイル名表示
    $fileLabel = New-Object Windows.Forms.Label
    $fileLabel.Location = New-Object Drawing.Point(20, 20)
    $fileLabel.Size = New-Object Drawing.Size(460, 30)
    $fileLabel.Text = "準備中..."
    $form.Controls.Add($fileLabel)
    
    # プログレスバー
    $progressBar = New-Object Windows.Forms.ProgressBar
    $progressBar.Location = New-Object Drawing.Point(20, 60)
    $progressBar.Size = New-Object Drawing.Size(460, 30)
    $progressBar.Maximum = $FilePaths.Count
    $progressBar.Value = 0
    $form.Controls.Add($progressBar)
    
    # 進捗情報
    $infoLabel = New-Object Windows.Forms.Label
    $infoLabel.Location = New-Object Drawing.Point(20, 100)
    $infoLabel.Size = New-Object Drawing.Size(460, 60)
    $infoLabel.Text = ""
    $form.Controls.Add($infoLabel)
    
    $form.Show()
    
    for ($i = 0; $i -lt $FilePaths.Count; $i++) {
        $currentFile = $FilePaths[$i]
        $fileName = Split-Path $currentFile -Leaf
        
        $fileLabel.Text = "処理中: $fileName"
        $progressBar.Value = $i + 1
        $infoLabel.Text = "進捗: $($i + 1)/$($FilePaths.Count) ファイル`n完了率: $([math]::Round((($i + 1) / $FilePaths.Count) * 100, 1))%"
        
        [System.Windows.Forms.Application]::DoEvents()
        
        # 実際のファイル処理をここに記述
        # 例: Copy-Item, Move-Item, 等
        Start-Sleep -Milliseconds 500  # 処理時間のシミュレーション
    }
    
    $fileLabel.Text = "処理完了！"
    $infoLabel.Text = "すべてのファイルの処理が完了しました。"
    Start-Sleep -Seconds 3
    $form.Close()
}

# 使用例
# $files = Get-ChildItem "C:\temp\*.txt"
# Show-FileProcessProgress -FilePaths $files.FullName
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/57e5dd84-bbc2-450e-8b5a-83b494921ff5.png)


## 複数プログレスバーの同時表示

```powershell
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

$form = New-Object Windows.Forms.Form
$form.Text = "複数プログレスバー"
$form.Size = New-Object Drawing.Size(500, 300)
$form.StartPosition = "CenterScreen"

# 1つ目のプログレスバー
$label1 = New-Object Windows.Forms.Label
$label1.Text = "タスク1"
$label1.Location = New-Object Drawing.Point(20, 20)
$label1.Size = New-Object Drawing.Size(460, 20)
$form.Controls.Add($label1)

$progressBar1 = New-Object Windows.Forms.ProgressBar
$progressBar1.Location = New-Object Drawing.Point(20, 45)
$progressBar1.Size = New-Object Drawing.Size(460, 25)
$progressBar1.Maximum = 50
$form.Controls.Add($progressBar1)

# 2つ目のプログレスバー
$label2 = New-Object Windows.Forms.Label
$label2.Text = "タスク2"
$label2.Location = New-Object Drawing.Point(20, 90)
$label2.Size = New-Object Drawing.Size(460, 20)
$form.Controls.Add($label2)

$progressBar2 = New-Object Windows.Forms.ProgressBar
$progressBar2.Location = New-Object Drawing.Point(20, 115)
$progressBar2.Size = New-Object Drawing.Size(460, 25)
$progressBar2.Maximum = 75
$form.Controls.Add($progressBar2)

# 全体プログレスバー
$labelTotal = New-Object Windows.Forms.Label
$labelTotal.Text = "全体進捗"
$labelTotal.Location = New-Object Drawing.Point(20, 160)
$labelTotal.Size = New-Object Drawing.Size(460, 20)
$form.Controls.Add($labelTotal)

$progressBarTotal = New-Object Windows.Forms.ProgressBar
$progressBarTotal.Location = New-Object Drawing.Point(20, 185)
$progressBarTotal.Size = New-Object Drawing.Size(460, 25)
$progressBarTotal.Maximum = 125  # 50 + 75
$form.Controls.Add($progressBarTotal)

$form.Show()

# 並行処理のシミュレーション
$task1Complete = $false
$task2Complete = $false

while (-not ($task1Complete -and $task2Complete)) {
    if (-not $task1Complete -and $progressBar1.Value -lt $progressBar1.Maximum) {
        $progressBar1.Value++
        $progressBarTotal.Value++
        if ($progressBar1.Value -eq $progressBar1.Maximum) {
            $task1Complete = $true
            $label1.Text = "タスク1 - 完了"
        }
    }
    
    if (-not $task2Complete -and $progressBar2.Value -lt $progressBar2.Maximum) {
        $progressBar2.Value++
        $progressBarTotal.Value++
        if ($progressBar2.Value -eq $progressBar2.Maximum) {
            $task2Complete = $true
            $label2.Text = "タスク2 - 完了"
        }
    }
    
    [System.Windows.Forms.Application]::DoEvents()
    Start-Sleep -Milliseconds 100
}

$labelTotal.Text = "全体進捗 - 完了"
Start-Sleep -Seconds 2
$form.Close()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/fe5e0532-7282-4e3b-a716-c8a9df21eef0.png)

## カスタムプログレスバーの利点

柔軟なデザインに対応
　色やサイズ、表示位置を自由にカスタマイズできます。

詳細な情報の表示が可能
　ラベルや補足情報などをあわせて表示することができます。

複数の進行状況を同時に管理
　複数の処理を並行して監視・表示することができます。

ユーザーとのインタラクションに対応
　ボタンなどのコントロール要素を追加して、操作性を高めることができます。

## 注意点

[System.Windows.Forms.Application]::DoEvents() を使用することで、UIの更新を確実に反映させることができます。

長時間の処理ではフォームが応答しなくなることがあるため、適切なタイミングでUIを更新する工夫が必要です。

また、エラーが発生した場合に備えて、適切なエラーハンドリングを実装しておくことも重要です。

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e36d47be-e12f-42e8-a23d-60ba9e554127.png)

Windows Formsを使用することで、PowerShellでも本格的なプログレスバーを作成できます。標準の `Write-Progress` では実現できない柔軟な表示や複雑な進捗管理が可能になり、ユーザーエクスペリエンスの向上に大きく貢献します。

長時間の処理を行うスクリプトでは、ぜひこのようなカスタムプログレスバーの実装を検討してみてください。

## 参考情報

https://learn.microsoft.com/en-us/powershell/scripting/samples/creating-a-custom-input-box?view=powershell-7.5

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.progressbar?view=windowsdesktop-8.0

https://learn.microsoft.com/ja-jp/dotnet/api/system.windows.forms.application.doevents?view=windowsdesktop-9.0