ディスク温度や通電時間などの「ストレージ信頼性カウンター」を、PowerShellではなくVB.NETのWinFormsアプリから直接取得したい――そんな要件に最短で応えるための実装ガイドです。PowerShell SDKを使う方法と、WMI/CIMをネイティブに叩く方法の2通りを、UI実装・例外対策・運用ノウハウまで含めて具体的に解説します。
WinFormsアプリから StorageReliabilityCounter を取得する全体像
PowerShellの
Get‑PhysicalDisk | Get‑StorageReliabilityCounter
で得られるカウンターは、WMI/CIM では root\Microsoft\Windows\Storage 名前空間の MSFT_StorageReliabilityCounter に相当します。アプリから取得するアプローチは大きく2つです。
| アプローチ | 要点 | メリット | 留意点 | おすすめ度 |
|---|---|---|---|---|
| ① PowerShell SDK 経由 | PowerShellエンジンを組み込み、Get‑PhysicalDisk と Get‑StorageReliabilityCounter をそのまま実行してJSONで受け取る | 最短・最少コード。PowerShellと同じ結果 | 環境によりモジュール読込(Storage)が失敗することがある | ◎(まずはこれ) |
| ② WMI/CIM を直接クエリ | MSFT_PhysicalDisk から GetRelated("MSFT_StorageReliabilityCounter") で関連インスタンスを取得 | PowerShell依存なし。サーバー常駐でも軽量 | 機種差・ファーム差の非対応や空配列を自前で吸収する必要 | ○(制約の少ない常駐サービスなど) |
取得できる主なカウンター(プロパティの例)
実装機種により出力は変わりますが、代表的なものは以下です。
| プロパティ | 意味 | 単位/範囲 | 備考 |
|---|---|---|---|
| Temperature | 現在温度 | ℃ | 一部デバイスは未実装で Null/0 |
| TemperatureMax | 観測最大温度 | ℃ | 履歴最大 |
| TemperatureThreshold | しきい値 | ℃ | 超過時は警告 |
| PowerOnHours | 通電時間累計 | 時間 | SMARTのPOH相当 |
| PowerCycleCount | 電源投入回数 | 回 | 装置寿命推定の参考 |
| ReadErrorsTotal / WriteErrorsTotal | 累計読み書きエラー | 回 | 訂正の有無は別途プロパティがある場合あり |
| Wear | SSD消耗度 | 0–100 または 0–255 | ベンダー依存。正規化推奨 |
要件・前提
- 対象OS:Windows 10/11、Windows Server(
root\Microsoft\Windows\Storageが利用可能) - 権限:管理者推奨(環境により通常権限だと Storage モジュールが失敗することあり)
- ハードウェア:SATAのHDD/SSDは取得事例が多い。NVMeやUSB接続は未実装の個体がある
- 開発環境:.NET 6/7/8(VB)、WinForms、
System.Management参照(WMI)、Microsoft.PowerShell.SDK(PowerShell方式)
実装アプローチ①:PowerShell SDKでそのまま叩く
PowerShellの出力をJSONで受け取り、System.Text.JsonでデシリアライズしてDataGridViewに表示するのが最短です。ディスク情報と信頼性カウンターを結合するため、PowerShell側で「ディスク1台につきカウンターを内包する形」のJSONを生成します。
手順
- プロジェクトにNuGetで
Microsoft.PowerShell.SDKとSystem.Text.Jsonを追加 - フォームに
DataGridViewとTimer(例:5秒)を配置 - 以下のコードを貼り付け
モデルクラス(VB.NET)
Public Class DiskWithRc
Public Property FriendlyName As String
Public Property DeviceId As UInteger
Public Property UniqueId As String
Public Property SerialNumber As String
Public Property Reliability As ReliabilityCounter
End Class
Public Class ReliabilityCounter
Public Property Temperature As Integer?
Public Property TemperatureMax As Integer?
Public Property TemperatureThreshold As Integer?
Public Property PowerOnHours As ULong?
Public Property PowerCycleCount As UInteger?
Public Property ReadErrorsTotal As ULong?
Public Property WriteErrorsTotal As ULong?
Public Property Wear As Integer?
End Class
' 表示用のフラット行
Public Class DiskRow
Public Property FriendlyName As String
Public Property DeviceId As UInteger
Public Property SerialNumber As String
Public Property Temperature As Integer?
Public Property PowerOnHours As ULong?
Public Property WearPercent As Integer?
Public Property ReadErrorsTotal As ULong?
Public Property WriteErrorsTotal As ULong?
End Class
PowerShell呼び出し(VB.NET)
PowerShell 7 の環境では、Windows PowerShell のモジュールを互換読み込みする必要がある場合があります。以下は両方に配慮しつつ、結果をJSONで取得する例です。
Imports System.Management.Automation
Imports System.Text.Json
Public Class Form1
Private ReadOnly _jsonOptions As New JsonSerializerOptions With {
.PropertyNameCaseInsensitive = True
}
```
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
DataGridView1.AutoGenerateColumns = True
Await RefreshGridAsync()
Timer1.Interval = 5000 ' 5秒
AddHandler Timer1.Tick, Async Sub(_, __) Await RefreshGridAsync()
Timer1.Start()
End Sub
Private Async Function RefreshGridAsync() As Task
Try
Dim disks = Await Task.Run(Function() LoadDisksWithRcJson())
Dim rows = ProjectRows(disks)
DataGridView1.DataSource = rows
ApplyHeatmapStyle()
Catch ex As Exception
' ログやステータス表示など
MessageBox.Show($"取得に失敗しました: {ex.Message}")
End Try
End Function
Private Function LoadDisksWithRcJson() As List(Of DiskWithRc)
Using ps = PowerShell.Create()
Dim script As String =
```
" $ErrorActionPreference = 'Stop'
try {
Import-Module -Name Storage -ErrorAction Stop
} catch {
if ($PSVersionTable.PSVersion.Major -ge 6) {
Import-Module -Name Storage -UseWindowsPowerShell -ErrorAction Stop
} else { throw }
}
Get-PhysicalDisk |
Select-Object FriendlyName, DeviceId, UniqueId, SerialNumber,
@{n='Reliability'; e={ $_ | Get-StorageReliabilityCounter }} |
ConvertTo-Json -Depth 6 -Compress
"
ps.AddScript(script)
Dim results = ps.Invoke()
If ps.HadErrors Then
Throw New InvalidOperationException(String.Join(Environment.NewLine, ps.Streams.Error.Select(Function(e) e.ToString())))
End If
Dim json = String.Join("", results.Select(Function(r) r.ToString()))
Return JsonSerializer.Deserialize(Of List(Of DiskWithRc))(json, _jsonOptions)
End Using
End Function
```
Private Function ProjectRows(disks As List(Of DiskWithRc)) As List(Of DiskRow)
Dim list As New List(Of DiskRow)
If disks Is Nothing Then Return list
For Each d In disks
Dim rc = d.Reliability
Dim wearNorm As Integer? = Nothing
If rc?.Wear.HasValue Then
Dim w = rc.Wear.Value
If w > 100 Then
wearNorm = CInt(Math.Min(100, Math.Round(w * 100.0 / 255.0)))
Else
wearNorm = w
End If
End If
list.Add(New DiskRow With {
.FriendlyName = d.FriendlyName,
.DeviceId = d.DeviceId,
.SerialNumber = d.SerialNumber,
.Temperature = rc?.Temperature,
.PowerOnHours = rc?.PowerOnHours,
.WearPercent = wearNorm,
.ReadErrorsTotal = rc?.ReadErrorsTotal,
.WriteErrorsTotal = rc?.WriteErrorsTotal
})
Next
Return list
End Function
Private Sub ApplyHeatmapStyle()
For Each row As DataGridViewRow In DataGridView1.Rows
Dim tempObj = row.Cells("Temperature").Value
If tempObj Is Nothing OrElse tempObj Is DBNull.Value Then Continue For
Dim t As Integer
If Integer.TryParse(tempObj.ToString(), t) Then
If t >= 60 Then
row.Cells("Temperature").Style.BackColor = Color.LightCoral
ElseIf t >= 50 Then
row.Cells("Temperature").Style.BackColor = Color.Moccasin
Else
row.Cells("Temperature").Style.BackColor = Color.White
End If
End If
Next
End Sub
```
End Class
ポイント
- JSONは深さ指定が必須:
-Depth 6を付けないと入れ子のReliabilityが欠落します。 - モジュール読み込みの互換対策:PowerShell 7 では
-UseWindowsPowerShell付きのImport-Module Storageを試みています。 - 型の揺れ対策:
Wearは0–255スケールの場合があるため0–100に正規化しています。 - UI更新:タイマーで定期更新し、温度セルの色で閾値を可視化。
実装アプローチ②:WMI/CIMでネイティブ取得
PowerShellに依存せず、WMI(System.Management)を直接叩く方法です。MSFT_DiskToStorageReliabilityCounter の関連付けが返らない個体があるため、MSFT_PhysicalDisk → GetRelated("MSFT_StorageReliabilityCounter") が安定です。
WMIコード(VB.NET)
Imports System.Management
Public Class WmiReader
Public Shared Function ReadRows() As List(Of DiskRow)
Dim rows As New List(Of DiskRow)
Dim scope = New ManagementScope("\.\root\Microsoft\Windows\Storage")
scope.Connect()
```
Dim q = New ObjectQuery("SELECT * FROM MSFT_PhysicalDisk")
Dim searcher = New ManagementObjectSearcher(scope, q)
For Each disk As ManagementObject In searcher.Get()
Dim friendly = ToStr(disk("FriendlyName"))
Dim deviceId = ToUInt32(disk("DeviceId"))
Dim serial = ToStr(disk("SerialNumber"))
Dim related = disk.GetRelated("MSFT_StorageReliabilityCounter")
If related Is Nothing Then Continue For
For Each rc As ManagementObject In related
rows.Add(New DiskRow With {
.FriendlyName = friendly,
.DeviceId = deviceId,
.SerialNumber = serial,
.Temperature = ToNullableInt(rc("Temperature")),
.PowerOnHours = ToNullableULong(rc("PowerOnHours")),
.WearPercent = NormalizeWear(ToNullableInt(rc("Wear"))),
.ReadErrorsTotal = ToNullableULong(rc("ReadErrorsTotal")),
.WriteErrorsTotal = ToNullableULong(rc("WriteErrorsTotal"))
})
Next
Next
Return rows
End Function
Private Shared Function ToStr(obj As Object) As String
If obj Is Nothing OrElse obj Is DBNull.Value Then Return Nothing
Return Convert.ToString(obj)
End Function
Private Shared Function ToUInt32(obj As Object) As UInteger
If obj Is Nothing OrElse obj Is DBNull.Value Then Return 0UI
Return Convert.ToUInt32(obj)
End Function
Private Shared Function ToNullableInt(obj As Object) As Integer?
If obj Is Nothing OrElse obj Is DBNull.Value Then Return Nothing
Return Convert.ToInt32(obj)
End Function
Private Shared Function ToNullableULong(obj As Object) As ULong?
If obj Is Nothing OrElse obj Is DBNull.Value Then Return Nothing
Return Convert.ToUInt64(obj)
End Function
Private Shared Function NormalizeWear(w As Integer?) As Integer?
If Not w.HasValue Then Return Nothing
If w.Value > 100 Then
Return CInt(Math.Min(100, Math.Round(w.Value * 100.0 / 255.0)))
End If
Return w
End Function
```
End Class
フォーム統合
Private Sub LoadByWmi()
Try
Dim rows = WmiReader.ReadRows()
DataGridView1.DataSource = rows
ApplyHeatmapStyle()
Catch ex As Exception
MessageBox.Show($"WMI取得に失敗しました: {ex.Message}")
End Try
End Sub
WMI方式の利点と落とし穴
- 利点:PowerShell不要、サービスや常駐監視でも軽量。
- 落とし穴:NVMe/USB等の一部デバイスで
GetRelatedが空配列(0件)になることがある。例外ではなく「無い」のが正しい挙動なので、Null/0件をそのままスキップする。
表示UI(DataGridView)の作り込みポイント
| UI要素 | 意図 | 実装例 |
|---|---|---|
| 温度セルのヒートマップ | 異常温度を一目で発見 | >=60℃赤、50–59℃オレンジ |
| Wearの正規化 | 0–255スケール対策 | 100分率に換算しバー表示にしても良い |
| 自動更新 | 簡易モニタリング | 5–30秒程度でタイマー更新 |
| 列の自動サイズ調整 | 視認性向上 | DataGridView.AutoSizeColumnsMode = AllCells |
PowerShell方式とWMI方式の比較(詳細)
| 観点 | PowerShell SDK | WMI/CIM |
|---|---|---|
| 実装量 | 少ない(JSONをデシリアライズ) | やや多い(Null/型対策が必要) |
| 依存関係 | Microsoft.PowerShell.SDK, Storageモジュール | System.Management のみ |
| 実行権限 | 管理者推奨(Storageモジュール読込のため) | 標準権限でも通ること多い(環境依存) |
| パフォーマンス | 初回にPSホスト起動コスト | 軽量で安定 |
| 機種差吸収 | PSが吸収することが多い | 素の値が返るため自前の吸収が必要 |
ハードウェア依存・取得できないケースの扱い
- NVMe/USBで空になる:ファームウェアがカウンター未実装の場合、
GetRelated自体が0件で返ります。例外ではないため、単にスキップしてください。 - SATAのHDD/SSDは良好:温度やPOH、エラー総数などが取得できる事例が多いです。
- Wearの解釈:100が寿命という意味ではなく、ベンダーごとのスコアリングです。0–255のベンダー値は正規化してUIに出すとユーザー理解が進みます。
エラー対策とリトライ方針
| 症状 | 想定原因 | 対処 |
|---|---|---|
Import-Module Storage に失敗 | PowerShell 7とWindows PowerShellの互換問題 | -UseWindowsPowerShell での再試行/管理者実行 |
| 温度が常に0/Null | デバイス未実装 | 「未取得」扱いでUI表示をグレーアウト |
| WMIがタイムアウト | 一時的なWMIスタック不調 | 1–2回のリトライ/バックグラウンドスレッドで取得 |
| AnyCPUで動かない | 古いPS参照DLL(x86)が混在 | NuGetの Microsoft.PowerShell.SDK を使う |
セキュリティ・権限の実務ポイント
- 最小権限原則:管理者権限を前提にせず、まずは標準権限で実行。失敗した場合にのみユーザーへ昇格を促す。
- 例外メッセージのサニタイズ:UIに出すメッセージは簡潔かつ機密情報を含めない。
- 監視間隔:5–30秒程度が実運用の落としどころ。過剰ポーリングは発熱・寿命に影響しないが、CPUとバッテリーには影響。
データの整形としきい値監視
カウンターはベンダー依存が大きいので、UIやアラートは「しきい値を超えたかどうか」を基準にします。
- 温度:注意=50℃、警告=60℃(任意に変更)
- POH:装置寿命の閾値ではなく、トレンド監視(増加速度)で判断
- Wear:20%以下で注意、10%以下で交換検討など、運用基準を表示
- エラー総数:増分監視(前回との差分)で急増を検知
サンプル:最小構成のフォームデザイナ
' Form1(最小例)
' - DataGridView1
' - Timer1(Interval=5000)
' Loadイベントで RefreshGridAsync を起動
' 「WMIで読みたい」ボタンを追加して LoadByWmi を呼んでもよい
拡張:ログ保存とCSVエクスポート
簡易監視用途なら、更新のたびにCSVへ追記し、異常時のみポップアップする構成が便利です。
Private Sub ExportCsv(rows As IEnumerable(Of DiskRow), path As String)
Using sw As New IO.StreamWriter(path, append:=True, encoding:=Text.Encoding.UTF8)
For Each r In rows
Dim line = String.Join(","c, {
DateTime.Now.ToString("s"),
Quote(r.FriendlyName),
r.DeviceId.ToString(),
Quote(r.SerialNumber),
If(r.Temperature.HasValue, r.Temperature.Value.ToString(), ""),
If(r.PowerOnHours.HasValue, r.PowerOnHours.Value.ToString(), ""),
If(r.WearPercent.HasValue, r.WearPercent.Value.ToString(), ""),
If(r.ReadErrorsTotal.HasValue, r.ReadErrorsTotal.Value.ToString(), ""),
If(r.WriteErrorsTotal.HasValue, r.WriteErrorsTotal.Value.ToString(), "")
})
sw.WriteLine(line)
Next
End Using
End Sub
Private Function Quote(s As String) As String
If String.IsNullOrEmpty(s) Then Return ""
Return $"""{s.Replace("""", """""")}"""
End Function
テストの観点
- Null耐性:各カラムにNull/0/極端な値を流し、UIと計算が破綻しないか確認。
- 大量ディスク:JBOD/ストレージスペース等で10台以上を想定し、列幅とスクロールの操作性を検証。
- 権限差:標準ユーザー・管理者・UAC無効など複数プロファイルで取得可否を確認。
よくある質問(FAQ)
Q. 物理ディスクが見えるのに温度が空欄です。
A. デバイス側がカウンターを実装していない可能性が高いです。例外ではなく仕様なので、UIでは「—」など未取得表示にしてください。
Q. PowerShell方式でStorageモジュールが読み込めません。
A. PowerShell 7 の場合は -UseWindowsPowerShell を付けて互換ロードを試みます。これでも不可なら管理者実行またはWMI方式を選択します。
Q. MSFT_DiskToStorageReliabilityCounter を使うべきですか?
A. それが返らない個体があるため、MSFT_PhysicalDisk.GetRelated("MSFT_StorageReliabilityCounter") の方が実地では確実です。
Q. 監視間隔はどのくらいが良いですか?
A. 5–30秒程度が現実的です。温度は急変しにくいため、1秒更新は過剰です。
サンプル:列の見せ方(ユーザー理解を高める工夫)
- 温度:数値+セル色(閾値で色分け)
- Wear:
xx%の数値+進捗バー(DataGridViewのカスタムセルでも良い) - POH:
12,345 hのように単位を明示 - エラー総数:差分(前回比)も別列に出すと「いま増えているか」が分かる
実装チェックリスト
- PowerShell方式:
-Depth指定を忘れない - WMI方式:Null/0件を例外にしない
- Wearの正規化:0–255の場合に100分率化
- UIの可観測性:温度・Wearの閾値色分け
- ログ:CSVへ追記(発生時刻・差分)
- 権限:失敗したら昇格を提案
まとめ
WinForms(VB.NET)からストレージ信頼性カウンターを取得するなら、まずはPowerShell SDKで Get‑PhysicalDisk | Get‑StorageReliabilityCounter をJSON化して取り込むのが最短です。運用でPowerShell依存を避けたい、あるいは常駐監視で軽さを重視するならWMI/CIMの直接取得に切り替えましょう。どちらの方式でも、Null・ベンダー差(特にWearと温度)への耐性と、UIでの閾値可視化が品質を左右します。本稿のコードを土台に、しきい値アラートやログ蓄積を加えるだけで、実戦投入可能な簡易ディスクヘルスモニターが完成します。
付録:PowerShellだけで突き合わせる(参考)
ディスク一覧と信頼性カウンターを別々にJSON化し、アプリ側で DeviceId をキーに結合する方法も可能です。
# 1) 物理ディスク
Get‑PhysicalDisk | Select FriendlyName,DeviceId,UniqueId,SerialNumber |
ConvertTo‑Json -Compress
# 2) 信頼性カウンター
Get‑PhysicalDisk | Get‑StorageReliabilityCounter |
Select * | ConvertTo‑Json -Depth 5 -Compress
ただし、ベンダーや環境によって突き合わせキーの安定性が揺れる場合があるため、本文の「1台ごとにReliabilityを内包させる」方法が堅牢です。
付録:DataGridView列の推奨構成
| 列名 | 型 | 例 | 書式 | 備考 |
|---|---|---|---|---|
| FriendlyName | 文字列 | Samsung SSD 860 EVO | – | 表示名 |
| DeviceId | 数値 | 2 | – | 重複注意 |
| SerialNumber | 文字列 | S3Z6… | – | 特定用途ではマスク推奨 |
| Temperature | 整数 | 42 | 0 ℃ | Nullは「—」表示 |
| PowerOnHours | 整数(ULong?) | 12345 | N0 + ” h” | 桁区切り |
| WearPercent | 整数(%) | 73 | 0 % | 0–255は正規化 |
| ReadErrorsTotal | 整数 | 0 | N0 | 差分監視推奨 |
| WriteErrorsTotal | 整数 | 0 | N0 | 差分監視推奨 |
付録:例外処理テンプレート
Private Function SafeInvoke(Of T)(op As Func(Of T), Optional onError As Action(Of Exception) = Nothing) As T
Try
Return op()
Catch ex As Exception
onError?.Invoke(ex)
Return Nothing
End Try
End Function
上記を使って、PowerShell呼び出しやWMIクエリ全体を安全にラップすると、UIが固まりません。
付録:しきい値カスタム
Public Class Thresholds
Public Property TempWarn As Integer = 60
Public Property TempCaution As Integer = 50
Public Property WearWarn As Integer = 10 ' %(残り寿命の目安)
End Class
設定ファイル化すれば、現場の基準に合わせて即時変更できます。
以上の手順をそのまま実装すれば、WinForms(VB.NET)で StorageReliabilityCounter を安全かつ読みやすく可視化できます。まずはPowerShell方式で動かし、要件に応じてWMI方式に切り替える二段構えが現実的です。

コメント