WinForms(VB.NET)でStorageReliabilityCounterを取得する方法|PowerShell SDKとWMI/CIMの実装手順とサンプルコード

ディスク温度や通電時間などの「ストレージ信頼性カウンター」を、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累計読み書きエラー回訂正の有無は別途プロパティがある場合あり
WearSSD消耗度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を生成します。

手順

  1. プロジェクトにNuGetで Microsoft.PowerShell.SDK と System.Text.Json を追加
  2. フォームに DataGridView と Timer(例:5秒)を配置
  3. 以下のコードを貼り付け

モデルクラス(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 SDKWMI/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整数420 ℃Nullは「—」表示
PowerOnHours整数(ULong?)12345N0 + ” h”桁区切り
WearPercent整数(%)730 %0–255は正規化
ReadErrorsTotal整数0N0差分監視推奨
WriteErrorsTotal整数0N0差分監視推奨

付録:例外処理テンプレート

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方式に切り替える二段構えが現実的です。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次