PowerShellでシステムのディスク種類(HDD, SSD)を確認する方法

Get-DiskとGet-PhysicalDiskを同じlist indexやFriendlyName、数値一致だけで結び付けません。MSFT_DiskToStorageReliabilityCounterとMSFT_PhysicalDiskToStorageReliabilityCounterを実際に辿り、同じprovider上の一つのMSFT_StorageReliabilityCounterへDiskとPhysicalDiskが1対1で結ばれ、reverse traversalも一致する場合だけSSD/HDDを報告します。

目次

provider・class・DeviceIdを含むcounter identityを作る

CIMのServerName・Namespace・ClassNameとcounter DeviceIdをProviderCounterKeyにします。どれかが空、classが想定外、Disk provider identityが作れない場合は推測せずUnknownです。FriendlyNameは表示専用でidentityに使いません。

$ErrorActionPreference='Stop'
$namespace='root/Microsoft/Windows/Storage'
function Get-ProviderCounterKey([Microsoft.Management.Infrastructure.CimInstance]$Counter){
  $server=[string]$Counter.CimSystemProperties.ServerName
  $ns=[string]$Counter.CimSystemProperties.Namespace
  $class=[string]$Counter.CimSystemProperties.ClassName
  $deviceId=[string]$Counter.DeviceId
  if([string]::IsNullOrWhiteSpace($server) -or [string]::IsNullOrWhiteSpace($ns) -or $class -cne 'MSFT_StorageReliabilityCounter' -or [string]::IsNullOrWhiteSpace($deviceId)){return $null}
  return "$server|$ns|$class|$deviceId"
}
function Get-DiskIdentity([Microsoft.Management.Infrastructure.CimInstance]$Disk){
  $server=[string]$Disk.CimSystemProperties.ServerName;$ns=[string]$Disk.CimSystemProperties.Namespace;$class=[string]$Disk.CimSystemProperties.ClassName
  if([string]::IsNullOrWhiteSpace($server) -or [string]::IsNullOrWhiteSpace($ns) -or $class -cne 'MSFT_Disk'){return $null}
  return "$server|$ns|$class|$([string]$Disk.Number)"
}
function New-UnknownRow($Disk,[string]$Reason){
  [pscustomobject]@{Status='Unknown';Reason=$Reason;DiskNumber=[string]$Disk.Number;DiskIdentity=(Get-DiskIdentity $Disk);ProviderCounterKey=$null;PhysicalObjectId=$null;MediaType='Unknown';DiskFriendlyName=[string]$Disk.FriendlyName;PhysicalFriendlyName=$null}
}

二つのdocumented associationを1対1で往復する

各MSFT_DiskからDiskToStorageReliabilityCounterを辿り、exactly one counterを要求します。そのcounterからPhysicalDiskToStorageReliabilityCounterを辿りexactly one PhysicalDiskを得て、PhysicalDisk→counterとcounter→Diskのreverse traversalも同じkeyへ戻ることを確認します。

$disks=@(Get-CimInstance -Namespace $namespace -ClassName MSFT_Disk -ErrorAction Stop)
$rows=[Collections.Generic.List[object]]::new()
foreach($disk in $disks){
  try{
    $diskIdentity=Get-DiskIdentity $disk
    if($null -eq $diskIdentity){$rows.Add((New-UnknownRow $disk 'Disk provider identity is incomplete'));continue}
    $counters=@(Get-CimAssociatedInstance -InputObject $disk -Association MSFT_DiskToStorageReliabilityCounter -ResultClassName MSFT_StorageReliabilityCounter -ErrorAction Stop)
    if($counters.Count -ne 1){$rows.Add((New-UnknownRow $disk "Disk-to-counter association count=$($counters.Count)"));continue}
    $counter=$counters[0];$counterKey=Get-ProviderCounterKey $counter
    if($null -eq $counterKey){$rows.Add((New-UnknownRow $disk 'Counter provider identity is incomplete'));continue}
    $physicalDisks=@(Get-CimAssociatedInstance -InputObject $counter -Association MSFT_PhysicalDiskToStorageReliabilityCounter -ResultClassName MSFT_PhysicalDisk -ErrorAction Stop)
    if($physicalDisks.Count -ne 1){$rows.Add((New-UnknownRow $disk "Counter-to-PhysicalDisk association count=$($physicalDisks.Count)"));continue}
    $physical=$physicalDisks[0]
    $physicalObjectId=[string]$physical.ObjectId
    if([string]::IsNullOrWhiteSpace($physicalObjectId)){$rows.Add((New-UnknownRow $disk 'PhysicalDisk ObjectId is missing'));continue}
    $reverseCounters=@(Get-CimAssociatedInstance -InputObject $physical -Association MSFT_PhysicalDiskToStorageReliabilityCounter -ResultClassName MSFT_StorageReliabilityCounter -ErrorAction Stop)
    if($reverseCounters.Count -ne 1 -or (Get-ProviderCounterKey $reverseCounters[0]) -cne $counterKey){$rows.Add((New-UnknownRow $disk 'PhysicalDisk reverse association is absent, multiple, or points to another counter'));continue}
    $reverseDisks=@(Get-CimAssociatedInstance -InputObject $counter -Association MSFT_DiskToStorageReliabilityCounter -ResultClassName MSFT_Disk -ErrorAction Stop)
    if($reverseDisks.Count -ne 1 -or (Get-DiskIdentity $reverseDisks[0]) -cne $diskIdentity){$rows.Add((New-UnknownRow $disk 'Counter reverse association is absent, multiple, or points to another disk'));continue}
    if([string]$counter.DeviceId -cne [string]$disk.Number -or [string]$counter.DeviceId -cne [string]$physical.DeviceId){$rows.Add((New-UnknownRow $disk 'Documented associated DeviceId values are inconsistent'));continue}
    $media=if([string]$physical.MediaType -in @('HDD','SSD')){[string]$physical.MediaType}else{'Unknown'}
    $status=if($media -eq 'Unknown'){'Unknown'}else{'Associated'}
    $reason=if($status -eq 'Associated'){'Exact documented 1:1 associations verified'}else{'Associated PhysicalDisk MediaType is not HDD or SSD'}
    $rows.Add([pscustomobject]@{Status=$status;Reason=$reason;DiskNumber=[string]$disk.Number;DiskIdentity=$diskIdentity;ProviderCounterKey=$counterKey;PhysicalObjectId=$physicalObjectId;MediaType=$media;DiskFriendlyName=[string]$disk.FriendlyName;PhysicalFriendlyName=[string]$physical.FriendlyName})
  }catch{
    $rows.Add((New-UnknownRow $disk "Association query failed: $($_.Exception.Message)"))
  }
}

重複とunmapped PhysicalDiskをUnknownへ閉じる

associationに合格した後もCounterKey・PhysicalObjectId・DiskNumberの重複を全体で検査します。一つでもduplicateなら該当行をUnknownへ戻します。直接列挙したPhysicalDiskのうちaccepted associationに含まれない行もUnknownとして残し、消しません。

foreach($property in @('ProviderCounterKey','PhysicalObjectId','DiskNumber')){
  $duplicates=@($rows|Where-Object{$_.Status -eq 'Associated' -and -not [string]::IsNullOrWhiteSpace([string]$_.$property)}|Group-Object -Property $property|Where-Object Count -gt 1)
  foreach($group in $duplicates){
    foreach($row in $group.Group){$row.Status='Unknown';$row.MediaType='Unknown';$row.Reason="Duplicate associated $property"}
  }
}
$allPhysical=@(Get-CimInstance -Namespace $namespace -ClassName MSFT_PhysicalDisk -ErrorAction Stop)
$knownObjectIds=@($rows|Where-Object{$_.Status -eq 'Associated'}|ForEach-Object{$_.PhysicalObjectId})
foreach($physical in $allPhysical){
  $objectId=[string]$physical.ObjectId
  if([string]::IsNullOrWhiteSpace($objectId) -or $objectId -notin $knownObjectIds){
    $rows.Add([pscustomobject]@{Status='Unknown';Reason='PhysicalDisk has no unique accepted Disk/counter association';DiskNumber=$null;DiskIdentity=$null;ProviderCounterKey=$null;PhysicalObjectId=$objectId;MediaType='Unknown';DiskFriendlyName=$null;PhysicalFriendlyName=[string]$physical.FriendlyName})
  }
}
$rows|Sort-Object Status,DiskNumber,PhysicalObjectId|Select-Object Status,Reason,DiskNumber,MediaType,ProviderCounterKey,PhysicalObjectId,DiskFriendlyName,PhysicalFriendlyName

受け入れ条件

list順を入れ替えても同じ1対1 associationが得られるfixtureだけが合格です。同じ数値でも別counter/provider、associationなし、0件・複数件、reverse不一致はUnknownです。Get-PhysicalDisk -DeviceNumberや配列indexによる紐付けは合格条件にしません。

RAID・Storage Spaces・virtual disk

RAID controller、Storage Spaces、virtual diskではOSが見せるDiskと物理mediaが1対1にならないことがあります。その場合はUnknownを正しい結果として扱い、controller vendorの管理APIや筐体slot情報など別の公式evidenceを取得します。

出力の読み方

Status=Associatedはassociationとidentity gateが通った意味で、device healthを保証しません。MediaType=UnknownをSSD/HDDへ補完せず、Reason、ProviderCounterKey、PhysicalObjectIdを調査用evidenceとして保存します。

公式情報・参考資料

この記事を書いた人

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

コメント

コメントする

目次