[備忘録] PowerShellでプログレスバーを実装してみた

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a54dfcce-b42f-4e10-b7d6-93866b6fc7ac.png)

PowerShellで長時間処理を実行する際、進捗状況がわからないとユーザーが不安になりがちです。`Write-Progress`コマンドレットを使用することで、視覚的なプログレスバーを簡単に実装できます。

## 動作確認した環境

Windows 11
Powershell 5.1

## 基本的なプログレスバー

最もシンプルなプログレスバーの実装例です。

```powershell
# 基本的なプログレスバー
for ($i = 1; $i -le 100; $i++) {
    Write-Progress -Activity "処理実行中" -Status "$i% 完了" -PercentComplete $i
    Start-Sleep -Milliseconds 50
}
Write-Progress -Activity "処理実行中" -Completed
```

Windows Powershell ISEでの出力例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/65b97acd-3c9c-48fd-abb7-16244fe98043.png)

Powershellのターミナルから出力例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/9d98e910-b4a0-4817-9b0d-5a2bb48908a9.png)

### パラメータ説明
- `Activity`: プログレスバーのタイトル
- `Status`: 現在の状況を示すテキスト
- `PercentComplete`: 進捗率（0-100）
- `Completed`: プログレスバーを非表示にする

## 実用的な例：ファイル処理

実際の処理でよくあるファイル一括処理の例です。

```powershell
function Process-Files {
    param([string]$Path)
    
    $files = Get-ChildItem -Path $Path -File
    $totalFiles = $files.Count
    
    for ($i = 0; $i -lt $totalFiles; $i++) {
        $file = $files[$i]
        $percentComplete = [math]::Round(($i / $totalFiles) * 100, 2)
        
        Write-Progress -Activity "ファイル処理中" `
                       -Status "処理中: $($file.Name)" `
                       -PercentComplete $percentComplete `
                       -CurrentOperation "($($i + 1)/$totalFiles)"
        
        # 実際の処理（例：ファイルサイズ取得）
        $fileSize = $file.Length
        Write-Host "$($file.Name): $fileSize bytes"
        
        Start-Sleep -Milliseconds 100
    }
    
    Write-Progress -Activity "ファイル処理中" -Completed
}

# 使用例
Process-Files -Path "C:\temp"
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/56ecb1a2-c117-46a2-b7b1-4e766956aae0.png)

## 階層プログレスバー

複数の処理段階がある場合の階層プログレスバーです。

```powershell
function Process-WithHierarchy {
    $mainTasks = @("初期化", "データ取得", "処理実行", "結果出力")
    
    for ($mainIndex = 0; $mainIndex -lt $mainTasks.Count; $mainIndex++) {
        $mainTask = $mainTasks[$mainIndex]
        $mainPercent = [math]::Round((($mainIndex + 1) / $mainTasks.Count) * 100)
        
        Write-Progress -Id 1 -Activity "メイン処理" `
                       -Status $mainTask `
                       -PercentComplete $mainPercent
        
        # サブタスク
        for ($subIndex = 1; $subIndex -le 10; $subIndex++) {
            $subPercent = ($subIndex / 10) * 100
            
            Write-Progress -Id 2 -ParentId 1 `
                           -Activity $mainTask `
                           -Status "ステップ $subIndex/10" `
                           -PercentComplete $subPercent
            
            Start-Sleep -Milliseconds 200
        }
        
        Write-Progress -Id 2 -Completed
    }
    
    Write-Progress -Id 1 -Completed
}

Process-WithHierarchy
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/df0a22a9-2ee5-430d-a86f-7c1c77f10358.png)


## エラーハンドリング付きプログレスバー

実際の運用では必須のエラーハンドリングを含む例です。

```powershell
function Process-WithErrorHandling {
    param([string[]]$Items)
    
    $totalItems = $Items.Count
    $processedCount = 0
    $errorCount = 0
    
    for ($i = 0; $i -lt $totalItems; $i++) {
        $item = $Items[$i]
        $percentComplete = [math]::Round(($i / $totalItems) * 100, 2)
        
        try {
            Write-Progress -Activity "アイテム処理中" `
                           -Status "処理中: $item (エラー: $errorCount)" `
                           -PercentComplete $percentComplete `
                           -CurrentOperation "($($i + 1)/$totalItems)"
            
            # 処理をシミュレート（ランダムにエラーを発生）
            if ((Get-Random -Minimum 1 -Maximum 10) -eq 1) {
                throw "処理エラー: $item"
            }
            
            Start-Sleep -Milliseconds 100
            $processedCount++
            
        } catch {
            $errorCount++
            Write-Warning "エラー: $($_.Exception.Message)"
        }
    }
    
    Write-Progress -Activity "アイテム処理中" -Completed
    Write-Host "処理完了: 成功 $processedCount件, エラー $errorCount件"
}

# 使用例
$testItems = 1..20 | ForEach-Object { "Item$_" }
Process-WithErrorHandling -Items $testItems
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/a5564a75-5fc0-4404-8db5-835d9edf7e47.png)

## まとめ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/e54c8e30-f3a2-47aa-a150-64560680f54b.png)

PowerShellの`Write-Progress`を使用することで、ユーザーフレンドリーなスクリプトを作成できます。特に長時間処理では必須の機能です。

- 基本的な使い方は`Write-Progress`の3つのパラメータを覚えるだけ
- 階層プログレスバーで複雑な処理も視覚化可能
- エラーハンドリングと組み合わせることで実用的なスクリプトに
- パフォーマンス考慮で更新頻度を調整することが重要

興味がありましたら試してみてください。

## 参考情報

https://learn.microsoft.com/ja-jp/powershell/module/microsoft.powershell.utility/write-progress?view=powershell-7.5

https://virtuallyjason.blogspot.com/2017/05/nested-progress-bars-in-powershell.html

https://zenn.dev/kenta_goto/articles/c3e3c6ef91bf1b