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として保存します。

コメント