[備忘録] PowerShellで作る「リアルタイム システム監視ダッシュボード」- PropertyGridの活用

## はじめに

PowerShellでGUIを作る際、「設定画面くらいしか作れない」と思っていませんか？
実は**PropertyGrid**を使えば、驚くほど高機能なアプリケーションを簡単に作ることができます。

今回は、システムのリアルタイム監視ダッシュボードを作りながら、PropertyGridの真の実力をお見せします。このダッシュボードは：

- リアルタイムでシステム情報を更新
- 監視項目を動的にオン/オフ切り替え
- アラート機能付き
- 設定をファイル保存/読み込み

これらの機能を、たった100行程度のPowerShellコードで実現します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/4720c47b-e475-4e86-ae01-b61ea00fa825.png)

## 画面イメージ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/7494748d-1317-4f87-8a38-6ca0534e2387.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/482de9cc-4bea-42fa-ada5-0476d4b05432.png)
監視中の敷居値を超えると赤くなり、アラーム音がなる

<iframe width="560" height="315" src="https://www.youtube.com/embed/nD8zX2LCLeU?si=MCo0QhsDJPD6tIpW" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

## なぜPropertyGridなのか？

通常のGUI開発では、各コントロールを手動で配置し、イベントハンドラーを設定する必要があります。しかしPropertyGridを使えば：

- **オブジェクトのプロパティを自動でGUI化**
- **型に応じた適切なエディターを自動選択**
- **カテゴリ別の整理表示**
- **リアルタイム値更新が簡単**

つまり、「データクラスを定義するだけで高機能GUIが完成」するのです。

## 実装：リアルタイム システム監視ダッシュボード

では早速、驚きのサンプルをご覧ください：

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

# システム監視設定クラス
class SystemMonitor {
    # === 監視対象設定 ===
    [System.ComponentModel.Category("監視対象")]
    [System.ComponentModel.DisplayName("CPU使用率監視")]
    [System.ComponentModel.Description("CPU使用率をリアルタイム監視します")]
    [bool]$MonitorCPU = $true
    
    [System.ComponentModel.Category("監視対象")]
    [System.ComponentModel.DisplayName("メモリ使用量監視")]
    [System.ComponentModel.Description("メモリ使用量をリアルタイム監視します")]
    [bool]$MonitorMemory = $true
    
    [System.ComponentModel.Category("監視対象")]
    [System.ComponentModel.DisplayName("ディスク容量監視")]
    [System.ComponentModel.Description("ディスク使用量をリアルタイム監視します")]
    [bool]$MonitorDisk = $true
    
    [System.ComponentModel.Category("監視対象")]
    [System.ComponentModel.DisplayName("プロセス数監視")]
    [System.ComponentModel.Description("実行中のプロセス数を監視します")]
    [bool]$MonitorProcessCount = $false
    
    # === リアルタイム値（読み取り専用） ===
    [System.ComponentModel.Category("📊 リアルタイム情報")]
    [System.ComponentModel.DisplayName("🔥 CPU使用率")]
    [System.ComponentModel.Description("現在のCPU使用率")]
    [System.ComponentModel.ReadOnly($true)]
    [string]$CurrentCPU = "取得中..."
    
    [System.ComponentModel.Category("📊 リアルタイム情報")]
    [System.ComponentModel.DisplayName("💾 メモリ使用量")]
    [System.ComponentModel.Description("現在のメモリ使用量")]
    [System.ComponentModel.ReadOnly($true)]
    [string]$CurrentMemory = "取得中..."
    
    [System.ComponentModel.Category("📊 リアルタイム情報")]
    [System.ComponentModel.DisplayName("💽 ディスク使用量")]
    [System.ComponentModel.Description("Cドライブの使用量")]
    [System.ComponentModel.ReadOnly($true)]
    [string]$CurrentDisk = "取得中..."
    
    [System.ComponentModel.Category("📊 リアルタイム情報")]
    [System.ComponentModel.DisplayName("⚙️ プロセス数")]
    [System.ComponentModel.Description("実行中のプロセス数")]
    [System.ComponentModel.ReadOnly($true)]
    [string]$CurrentProcessCount = "取得中..."
    
    [System.ComponentModel.Category("📊 リアルタイム情報")]
    [System.ComponentModel.DisplayName("🕐 最終更新")]
    [System.ComponentModel.Description("最後にデータを更新した時刻")]
    [System.ComponentModel.ReadOnly($true)]
    [string]$LastUpdate = "未更新"
    
    # === アラート設定 ===
    [System.ComponentModel.Category("🚨 アラート設定")]
    [System.ComponentModel.DisplayName("CPU使用率アラート")]
    [System.ComponentModel.Description("CPU使用率がこの値を超えるとアラート")]
    [System.ComponentModel.DefaultValue(80)]
    [int]$CPUAlertThreshold = 80
    
    [System.ComponentModel.Category("🚨 アラート設定")]
    [System.ComponentModel.DisplayName("メモリ使用率アラート")]
    [System.ComponentModel.Description("メモリ使用率がこの値を超えるとアラート")]
    [System.ComponentModel.DefaultValue(85)]
    [int]$MemoryAlertThreshold = 85
    
    [System.ComponentModel.Category("🚨 アラート設定")]
    [System.ComponentModel.DisplayName("アラート有効")]
    [System.ComponentModel.Description("アラート機能を有効にします")]
    [bool]$EnableAlerts = $true
    
    [System.ComponentModel.Category("🚨 アラート設定")]
    [System.ComponentModel.DisplayName("アラート音")]
    [System.ComponentModel.Description("アラート時に音を鳴らします")]
    [bool]$PlayAlertSound = $true
    
    # === 表示設定 ===
    [System.ComponentModel.Category("⚙️ 表示設定")]
    [System.ComponentModel.DisplayName("更新間隔（秒）")]
    [System.ComponentModel.Description("データ更新の間隔（秒）")]
    [System.ComponentModel.DefaultValue(2)]
    [int]$UpdateInterval = 2
    
    [System.ComponentModel.Category("⚙️ 表示設定")]
    [System.ComponentModel.DisplayName("テーマカラー")]
    [System.ComponentModel.Description("ダッシュボードのテーマカラー")]
    [System.Drawing.Color]$ThemeColor = [System.Drawing.Color]::DodgerBlue
}

# システム情報取得クラス
class SystemInfo {
    static [double] GetCPUUsage() {
        $cpu = Get-WmiObject Win32_Processor | Measure-Object -Property LoadPercentage -Average
        return [Math]::Round($cpu.Average, 1)
    }
    
    static [hashtable] GetMemoryInfo() {
        $os = Get-WmiObject Win32_OperatingSystem
        $totalMB = [Math]::Round($os.TotalVisibleMemorySize / 1024)
        $freeMB = [Math]::Round($os.FreePhysicalMemory / 1024)
        $usedMB = $totalMB - $freeMB
        $usagePercent = [Math]::Round(($usedMB / $totalMB) * 100, 1)
        
        return @{
            UsedMB = $usedMB
            TotalMB = $totalMB
            UsagePercent = $usagePercent
        }
    }
    
    static [hashtable] GetDiskInfo() {
        $disk = Get-WmiObject Win32_LogicalDisk -Filter "DeviceID='C:'"
        $totalGB = [Math]::Round($disk.Size / 1GB, 1)
        $freeGB = [Math]::Round($disk.FreeSpace / 1GB, 1)
        $usedGB = $totalGB - $freeGB
        $usagePercent = [Math]::Round(($usedGB / $totalGB) * 100, 1)
        
        return @{
            UsedGB = $usedGB
            TotalGB = $totalGB
            UsagePercent = $usagePercent
        }
    }
    
    static [int] GetProcessCount() {
        return (Get-Process).Count
    }
}

# メイン関数
function Start-SystemMonitorDashboard {
    # フォーム作成
    $form = New-Object System.Windows.Forms.Form
    $form.Text = "🖥️ リアルタイム システム監視ダッシュボード"
    $form.Size = New-Object System.Drawing.Size(550, 700)
    $form.StartPosition = "CenterScreen"
    $form.BackColor = [System.Drawing.Color]::FromArgb(240, 248, 255)
    
    # PropertyGrid作成
    $propertyGrid = New-Object System.Windows.Forms.PropertyGrid
    $propertyGrid.Size = New-Object System.Drawing.Size(520, 580)
    $propertyGrid.Location = New-Object System.Drawing.Point(10, 10)
    $propertyGrid.PropertySort = "Categorized"
    $propertyGrid.BackColor = [System.Drawing.Color]::White
    
    # ボタンパネル
    $buttonPanel = New-Object System.Windows.Forms.Panel
    $buttonPanel.Size = New-Object System.Drawing.Size(520, 80)
    $buttonPanel.Location = New-Object System.Drawing.Point(10, 600)
    
    # 各種ボタン
    $startButton = New-Object System.Windows.Forms.Button
    $startButton.Text = "▶️ 監視開始"
    $startButton.Size = New-Object System.Drawing.Size(100, 35)
    $startButton.Location = New-Object System.Drawing.Point(10, 10)
    $startButton.BackColor = [System.Drawing.Color]::LightGreen
    
    $stopButton = New-Object System.Windows.Forms.Button
    $stopButton.Text = "⏹️ 監視停止"
    $stopButton.Size = New-Object System.Drawing.Size(100, 35)
    $stopButton.Location = New-Object System.Drawing.Point(120, 10)
    $stopButton.BackColor = [System.Drawing.Color]::LightCoral
    $stopButton.Enabled = $false
    
    $saveButton = New-Object System.Windows.Forms.Button
    $saveButton.Text = "💾 設定保存"
    $saveButton.Size = New-Object System.Drawing.Size(100, 35)
    $saveButton.Location = New-Object System.Drawing.Point(230, 10)
    
    $loadButton = New-Object System.Windows.Forms.Button
    $loadButton.Text = "📁 設定読込"
    $loadButton.Size = New-Object System.Drawing.Size(100, 35)
    $loadButton.Location = New-Object System.Drawing.Point(340, 10)
    
    $aboutButton = New-Object System.Windows.Forms.Button
    $aboutButton.Text = "ℹ️ About"
    $aboutButton.Size = New-Object System.Drawing.Size(70, 35)
    $aboutButton.Location = New-Object System.Drawing.Point(450, 10)
    
    # 監視オブジェクト作成
    $monitor = [SystemMonitor]::new()
    $propertyGrid.SelectedObject = $monitor
    
    # タイマー作成
    $timer = New-Object System.Windows.Forms.Timer
    $timer.Interval = $monitor.UpdateInterval * 1000
    
    # タイマーイベント
    $timer.Add_Tick({
        try {
            if ($monitor.MonitorCPU) {
                $cpuUsage = [SystemInfo]::GetCPUUsage()
                $monitor.CurrentCPU = "${cpuUsage}%"
                
                if ($monitor.EnableAlerts -and $cpuUsage -gt $monitor.CPUAlertThreshold) {
                    if ($monitor.PlayAlertSound) { [Console]::Beep(800, 200) }
                    $form.BackColor = [System.Drawing.Color]::LightPink
                } else {
                    $form.BackColor = [System.Drawing.Color]::FromArgb(240, 248, 255)
                }
            }
            
            if ($monitor.MonitorMemory) {
                $memInfo = [SystemInfo]::GetMemoryInfo()
                $monitor.CurrentMemory = "$($memInfo.UsedMB)MB / $($memInfo.TotalMB)MB ($($memInfo.UsagePercent)%)"
                
                if ($monitor.EnableAlerts -and $memInfo.UsagePercent -gt $monitor.MemoryAlertThreshold) {
                    if ($monitor.PlayAlertSound) { [Console]::Beep(600, 200) }
                }
            }
            
            if ($monitor.MonitorDisk) {
                $diskInfo = [SystemInfo]::GetDiskInfo()
                $monitor.CurrentDisk = "$($diskInfo.UsedGB)GB / $($diskInfo.TotalGB)GB ($($diskInfo.UsagePercent)%)"
            }
            
            if ($monitor.MonitorProcessCount) {
                $processCount = [SystemInfo]::GetProcessCount()
                $monitor.CurrentProcessCount = "${processCount} プロセス"
            }
            
            $monitor.LastUpdate = Get-Date -Format "HH:mm:ss"
            $timer.Interval = $monitor.UpdateInterval * 1000
            $propertyGrid.Refresh()
        }
        catch {
            $monitor.LastUpdate = "エラー: $($_.Exception.Message)"
            $propertyGrid.Refresh()
        }
    })
    
    # イベントハンドラー
    $startButton.Add_Click({
        $timer.Start()
        $startButton.Enabled = $false
        $stopButton.Enabled = $true
        $form.Text = "🖥️ リアルタイム システム監視ダッシュボード [監視中]"
    })
    
    $stopButton.Add_Click({
        $timer.Stop()
        $startButton.Enabled = $true
        $stopButton.Enabled = $false
        $form.Text = "🖥️ リアルタイム システム監視ダッシュボード"
        $form.BackColor = [System.Drawing.Color]::FromArgb(240, 248, 255)
    })
    
    $saveButton.Add_Click({
        $saveDialog = New-Object System.Windows.Forms.SaveFileDialog
        $saveDialog.Filter = "Monitor Config (*.json)|*.json"
        $saveDialog.DefaultExt = "json"
        $saveDialog.FileName = "monitor_config.json"
        
        if ($saveDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
            $config = @{
                MonitorCPU = $monitor.MonitorCPU
                MonitorMemory = $monitor.MonitorMemory
                MonitorDisk = $monitor.MonitorDisk
                MonitorProcessCount = $monitor.MonitorProcessCount
                CPUAlertThreshold = $monitor.CPUAlertThreshold
                MemoryAlertThreshold = $monitor.MemoryAlertThreshold
                EnableAlerts = $monitor.EnableAlerts
                PlayAlertSound = $monitor.PlayAlertSound
                UpdateInterval = $monitor.UpdateInterval
                ThemeColor = $monitor.ThemeColor.Name
            }
            $config | ConvertTo-Json | Out-File -FilePath $saveDialog.FileName -Encoding UTF8
            [System.Windows.Forms.MessageBox]::Show("設定を保存しました！", "保存完了", "OK", "Information")
        }
    })
    
    $aboutButton.Add_Click({
        $aboutText = @"
🖥️ リアルタイム システム監視ダッシュボード

PowerShell + PropertyGrid で作成された
高機能システム監視ツールです。

✨ 主な機能:
• リアルタイム監視
• カスタマイズ可能なアラート
• 設定の保存/読み込み
• 直感的なGUI操作

Created with PowerShell ❤️
"@
        [System.Windows.Forms.MessageBox]::Show($aboutText, "About", "OK", "Information")
    })
    
    # コントロール追加
    $buttonPanel.Controls.AddRange(@($startButton, $stopButton, $saveButton, $loadButton, $aboutButton))
    $form.Controls.AddRange(@($propertyGrid, $buttonPanel))
    
    # フォーム終了時処理
    $form.Add_FormClosing({
        $timer.Stop()
        $timer.Dispose()
    })
    
    # フォーム表示
    Write-Host "🚀 システム監視ダッシュボードを起動中..."
    $form.ShowDialog()
}

# 実行
Start-SystemMonitorDashboard
```
## まとめ

このサンプルを実行すると、PowerShellでここまでできるのかと驚くはずです。特に：

- 設定変更が即座に反映される操作感
- 見た目のUI
- リアルタイム監視機能
- アラート機能まで搭載

これらすべてが、わずか150行程度のコードで実現できます。PropertyGridを使えば、PowerShellでも本格的なアプリケーションが簡単に作れるのです。

ぜひこのコードをコピー&ペーストして実行してみてください。システム監視の数値がリアルタイムで更新される様子に、きっと「おっ！」と驚くはずです。

PowerShellでのGUI開発の可能性を、PropertyGridで大きく広げてみませんか？