PowerShellを使ってハードウェアのドライバをアップデートする方法という問いには、ハードウェアベンダーの署名済みINFパッケージを取得・検証し、対象InstanceIdとの互換性と承認fingerprintをPowerShellで固定し、デバイス マネージャーから同じINFフォルダーを一度だけ手動適用するという方法で答えます。PnPUtilは適合候補への導入を試みても低順位版を強制できないため、本手順の変更経路には使いません。PowerShellは対象と承認パッケージを固定し、デバイス マネージャーでの一回の手動適用後に実identityを検証します。この記事ではPnpDevice InstanceId、Win32_PnPSignedDriverのInfName、DriverVersion、Manufacturerを更新前後で照合するを判断軸にし、実行前の確認、記事固有のコード、合否判定、戻し方を一続きで示します。
Windows Update任せの一般論ではなく、対象デバイスと署名済みINFを明示する実際の更新戦略を示す。完了は「再起動後に対象InstanceIdのDriverVersionとInfNameが承認版となり、Status=OKで機器テストが通る」と定義します。対象が取れない場合は「適用対象0件ならINFの対応Hardware ID、アーキテクチャ、OSビルド、ドライバーランクを確認する」として切り分け、推測で成功扱いにしません。
更新対象デバイスをInstanceIdで確定
ハードウェアベンダーの署名済みINFパッケージを取得・検証し、対象InstanceIdとの互換性と承認fingerprintをPowerShellで固定し、デバイス マネージャーから同じINFフォルダーを一度だけ手動適用する。署名済みハードウェアドライバーの更新ではこの進め方により、操作したという事実ではなく、期待する状態へ到達したかでタイトルの問いへ答えられます。Windows Update任せの一般論ではなく、対象デバイスと署名済みINFを明示する実際の更新戦略を示す。
更新対象デバイスをInstanceIdで確定の合格条件は、再起動後に対象InstanceIdのDriverVersionとInfNameが承認版となり、Status=OKで機器テストが通ることです。作業時刻、実行ユーザー、端末名を添え、判断に使った値が後から追える形にします。
現在版とProviderを保存
現在版とProviderを保存では、署名済みハードウェアドライバーの更新の対象を「PnpDevice InstanceId、Win32_PnPSignedDriverのInfName、DriverVersion、Manufacturerを更新前後で照合する」という単位で扱います。PnPUtilは適合候補への導入を試みても低順位版を強制できないため、本手順の変更経路には使いません。PowerShellは対象と承認パッケージを固定し、デバイス マネージャーでの一回の手動適用後に実identityを検証します。対象が複数なら表示名の部分一致で先頭を採らず、一意になる条件を追加します。
署名済みハードウェアドライバーの更新を始める前に、PowerShellの版、コマンドの提供元、必要権限、管理ポリシーの有無を確認します。権限不足と対象なしは意味が異なるため、例外を0件へ置き換えません。
ベンダー配布INFの署名を検査
ベンダー配布INFの署名を検査は変更前の基準点です。PnpDevice InstanceId、Win32_PnPSignedDriverのInfName、DriverVersion、Manufacturerを更新前後で照合するを出力に含め、取得時刻と一緒に保存します。値だけを切り取ると別対象との比較になるため、識別列を省きません。
$instanceId = 'PCI\VEN_1234&DEV_5678&SUBSYS_00000000&REV_01\4&00000000&0&00E0'
$device = Get-PnpDevice -InstanceId $instanceId -ErrorAction Stop
$hardwareIds = @((Get-PnpDeviceProperty -InstanceId $instanceId -KeyName 'DEVPKEY_Device_HardwareIds' -ErrorAction Stop).Data)
if ($hardwareIds.Count -eq 0) { throw 'Hardware IDを取得できません。' }
$currentDriverRows = @(Get-CimInstance Win32_PnPSignedDriver -ErrorAction Stop | Where-Object DeviceID -eq $instanceId)
if ($currentDriverRows.Count -ne 1) { throw "現在の署名済みdriverを一意にできません: $($currentDriverRows.Count)件" }
$driverBaseline = [pscustomobject]@{
InstanceId=$instanceId; HardwareIds=@($hardwareIds); Status=[string]$device.Status
InfName=[string]$currentDriverRows[0].InfName; DriverVersion=[string]$currentDriverRows[0].DriverVersion
DriverProviderName=[string]$currentDriverRows[0].DriverProviderName; Signer=[string]$currentDriverRows[0].Signer
}
$driverBaseline | Format-List
PnPUtilは適合候補への導入を試みても低順位版を強制できないため、本手順の変更経路には使いません。PowerShellは対象と承認パッケージを固定し、デバイス マネージャーでの一回の手動適用後に実identityを検証します。出力が多い場合も最初から無理に一件へ絞らず、候補数と除外理由を残してから対象を決めます。
デバイス マネージャーで承認済みパッケージを手動適用
デバイス マネージャーで承認済みパッケージを手動適用では、ハードウェアベンダーの署名済みINFパッケージを取得・検証し、対象InstanceIdとの互換性と承認fingerprintをPowerShellで固定し、デバイス マネージャーから同じINFフォルダーを一度だけ手動適用する。署名済みハードウェアドライバーの更新の例中にある名前、パス、ID、時刻はサンプルなので、そのまま本番へ貼らず、直前の読み取り結果から承認値を入れます。
$infPath = 'C:\ApprovedDriver\driver.inf'
$expectedCatalogSha256 = 'REPLACE_WITH_APPROVED_CATALOG_SHA256'
$expectedSignerThumbprint = 'REPLACE_WITH_APPROVED_SIGNER_THUMBPRINT'
$expectedProviderName = 'REPLACE_WITH_APPROVED_PROVIDER_NAME'
$expectedInstalledSigner = 'REPLACE_WITH_APPROVED_INSTALLED_SIGNER'
$deviceFunctionalTest = 'REPLACE_WITH_DEVICE_SPECIFIC_FUNCTIONAL_TEST_PROCEDURE'
function Get-IttripInfSections([string]$Path) {
$sections = [ordered]@{}; $current = $null
foreach ($raw in (Get-Content -LiteralPath $Path -Encoding Unicode -ErrorAction Stop)) {
$line = (($raw -split ';', 2)[0]).Trim()
if ($line -match '^\[(.+)\]$') { $current=$Matches[1]; $sections[$current]=[Collections.Generic.List[string]]::new(); continue }
if ($current -and $line) { $sections[$current].Add($line) }
}
$sections
}
function Get-IttripStringSha256([string]$Text) {
$algorithm = [Security.Cryptography.SHA256]::Create()
try { ([BitConverter]::ToString($algorithm.ComputeHash([Text.Encoding]::UTF8.GetBytes($Text)))).Replace('-','').ToLowerInvariant() }
finally { $algorithm.Dispose() }
}
function Get-IttripBytesSha256([byte[]]$Bytes) {
$algorithm = [Security.Cryptography.SHA256]::Create()
try { ([BitConverter]::ToString($algorithm.ComputeHash($Bytes))).Replace('-','').ToLowerInvariant() }
finally { $algorithm.Dispose() }
}
function Get-IttripCertificateBinding($Certificate) {
[ordered]@{
Thumbprint=$Certificate.Thumbprint.Replace(' ','').ToUpperInvariant()
Subject=[string]$Certificate.Subject;Issuer=[string]$Certificate.Issuer;SerialNumber=[string]$Certificate.SerialNumber
NotBeforeUtc=$Certificate.NotBefore.ToUniversalTime().ToString('o');NotAfterUtc=$Certificate.NotAfter.ToUniversalTime().ToString('o')
SignatureAlgorithmOid=[string]$Certificate.SignatureAlgorithm.Value;PublicKeyAlgorithmOid=[string]$Certificate.PublicKey.Oid.Value
RawCertificateSha256=Get-IttripBytesSha256 $Certificate.RawData
}
}
function Resolve-IttripInfString($Sections,[string]$RawValue) {
$value=$RawValue.Trim().Trim('"')
if($value -notmatch '^%(.+)%$'){return $value}
$key=$Matches[1]
$values=@(foreach($sectionName in @($Sections.Keys|Where-Object{$_ -eq 'Strings' -or $_ -like 'Strings.*'})){
foreach($row in $Sections[$sectionName]){
if($row -match ('^'+[regex]::Escape($key)+'\s*=')){
(($row -split '=',2)[1]).Trim().Trim('"')
}
}
})
$resolved=@($values|Sort-Object -Unique)
if($resolved.Count -ne 1){throw "INF string tokenを一意に解決できません: $key ($($resolved.Count)件)"}
$resolved[0]
}
function Get-IttripPackageManifest([string]$PackageRoot) {
$rows=@(Get-ChildItem -LiteralPath $PackageRoot -File -Recurse -ErrorAction Stop|ForEach-Object{
[pscustomobject]@{RelativePath=$_.FullName.Substring($PackageRoot.Length).TrimStart('\').Replace('\','/');Length=[int64]$_.Length;Sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()}
}|Sort-Object RelativePath)
if($rows.Count -eq 0){throw 'driver packageが空です。'}
[pscustomobject]@{Rows=$rows;FileCount=$rows.Count;Sha256=Get-IttripStringSha256 ($rows|ConvertTo-Json -Depth 4 -Compress)}
}
function Get-IttripDriverCandidateBinding([string]$Path,[string]$HardwareId) {
$resolvedInf=(Resolve-Path -LiteralPath $Path -ErrorAction Stop).Path
$packageRoot=(Get-Item -LiteralPath (Split-Path -Parent $resolvedInf) -ErrorAction Stop).FullName.TrimEnd('\')
$sections=Get-IttripInfSections -Path $resolvedInf
if(-not $sections.Contains('Version') -or -not $sections.Contains('Manufacturer')){throw 'Version/Manufacturer sectionがありません。'}
$driverVerRows=@($sections['Version']|Where-Object{$_ -match '^DriverVer\s*='})
$providerRows=@($sections['Version']|Where-Object{$_ -match '^Provider\s*='})
if($driverVerRows.Count -ne 1 -or $providerRows.Count -ne 1){throw 'DriverVer/Providerを一意にできません。'}
$driverVer=(($driverVerRows[0]-split '=',2)[1]).Trim()
$providerName=Resolve-IttripInfString $sections (($providerRows[0]-split '=',2)[1])
if($expectedProviderName -like 'REPLACE_*' -or -not [StringComparer]::Ordinal.Equals($providerName,$expectedProviderName)){throw 'INF Providerが承認値と一致しません。'}
$archDecoration=switch($env:PROCESSOR_ARCHITECTURE.ToUpperInvariant()){'AMD64'{'NTamd64'}'ARM64'{'NTarm64'}default{'NTx86'}}
$catalogRows=@($sections['Version']|Where-Object{$_ -match '^CatalogFile(?:\.[^=]+)?\s*='})
$preferredCatalog=@($catalogRows|Where-Object{$_ -match "^CatalogFile\.$([regex]::Escape($archDecoration))\s*="})
if($preferredCatalog.Count -eq 0){$preferredCatalog=@($catalogRows|Where-Object{$_ -match '^CatalogFile\s*='})}
if($preferredCatalog.Count -ne 1){throw '対象architectureのCatalogFileを一意にできません。'}
$catalogName=(($preferredCatalog[0]-split '=',2)[1]).Trim();$catalogPath=Join-Path $packageRoot $catalogName
$manufacturerModels=@()
foreach($line in $sections['Manufacturer']){
if($line -notmatch '='){continue};$parts=@((($line-split '=',2)[1]).Split(',')|ForEach-Object Trim)
if($parts.Count -eq 1){$manufacturerModels+=$parts[0]}
for($i=1;$i -lt $parts.Count;$i++){if($parts[$i] -like "$archDecoration*"){$manufacturerModels+="$($parts[0]).$($parts[$i])"}}
}
$bindingRows=@(foreach($modelsSection in ($manufacturerModels|Select-Object -Unique)){
if(-not $sections.Contains($modelsSection)){continue}
foreach($line in $sections[$modelsSection]){
if($line -notmatch '='){continue};$fields=@((($line-split '=',2)[1]).Split(',')|ForEach-Object Trim);if($fields.Count -lt 2){continue}
$sameHardwareId=@($fields[1..($fields.Count-1)]|Where-Object{[StringComparer]::OrdinalIgnoreCase.Equals($_,$HardwareId)})
if($sameHardwareId.Count -eq 1){$installBase=$fields[0];$ddCandidates=@("$installBase.$archDecoration",$installBase)|Where-Object{$sections.Contains($_)};if($ddCandidates.Count -eq 0){throw "DDInstall sectionがありません: $installBase"};[pscustomobject]@{ModelsSection=$modelsSection;DDInstallSection=$ddCandidates[0]}}
}
})
if($bindingRows.Count -ne 1){throw "HWIDをdecorated Models行とDDInstallへ一意に結合できません: $($bindingRows.Count)件"}
$catalogHash=(Get-FileHash -LiteralPath $catalogPath -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()
$signature=Get-AuthenticodeSignature -FilePath $catalogPath -ErrorAction Stop
if($signature.Status -ne 'Valid' -or -not $signature.SignerCertificate){throw "Catalog署名が有効ではありません: $($signature.Status)"}
if($expectedCatalogSha256 -like 'REPLACE_*' -or $expectedSignerThumbprint -like 'REPLACE_*' -or $expectedInstalledSigner -like 'REPLACE_*' -or $deviceFunctionalTest -like 'REPLACE_*'){throw '承認済みCatalog/provider/signer/機器固有testを実値へ置き換えてください。'}
$certBinding=Get-IttripCertificateBinding $signature.SignerCertificate
$certJson=$certBinding|ConvertTo-Json -Depth 5 -Compress;$certBindingSha256=Get-IttripStringSha256 $certJson
if($catalogHash -ne $expectedCatalogSha256.ToLowerInvariant() -or $certBinding.Thumbprint -ne $expectedSignerThumbprint.Replace(' ','').ToUpperInvariant()){throw '承認済みCatalog hash/signerと一致しません。'}
$manifest=Get-IttripPackageManifest $packageRoot
$infFileName=[IO.Path]::GetFileName($resolvedInf);$infSha256=(Get-FileHash -LiteralPath $resolvedInf -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant();$bound=$bindingRows[0]
$fingerprintText="$HardwareId|$($bound.ModelsSection)|$($bound.DDInstallSection)|$driverVer|$providerName|$infFileName|$infSha256|$catalogName|$catalogHash|$certBindingSha256|$($manifest.Sha256)"
[pscustomobject]@{
HardwareId=$HardwareId;ModelsSection=$bound.ModelsSection;DDInstallSection=$bound.DDInstallSection;DriverVer=$driverVer
ProviderName=$providerName;InfFileName=$infFileName;InfSha256=$infSha256;CatalogFile=$catalogName;CatalogSha256=$catalogHash
CatalogSignerIdentityJson=$certJson;CatalogSignerBindingSha256=$certBindingSha256;PackageFileCount=$manifest.FileCount
PackageManifestSha256=$manifest.Sha256;FullBindingSha256=Get-IttripStringSha256 $fingerprintText
}
}
$candidate=Get-IttripDriverCandidateBinding -Path $infPath -HardwareId $hardwareIds[0]
$candidateVersionText=(($candidate.DriverVer-split ',')[-1]).Trim()
try{$candidateDriverVersion=([version]$candidateVersionText).ToString();$oldDriverVersion=([version][string]$driverBaseline.DriverVersion).ToString()}catch{throw 'driver versionを正規化できません。'}
if($candidateDriverVersion -eq $oldDriverVersion){throw '候補versionが旧versionと同じため更新操作を開始しません。'}
$operationId=[guid]::NewGuid().ToString('D');$checkpointPath='C:\ProgramData\ITtrip\DriverUpdate\ittrip-driver-update-checkpoint.json'
$operationUtc=(Get-Date).ToUniversalTime();$operationBootUtc=(Get-CimInstance Win32_OperatingSystem -ErrorAction Stop).LastBootUpTime.ToUniversalTime()
$functionalTestSha256=Get-IttripStringSha256 $deviceFunctionalTest
$checkpoint=[ordered]@{
Schema=2;OperationId=$operationId;OperationUtc=$operationUtc.ToString('o');OperationBootUtc=$operationBootUtc.ToString('o')
InstanceId=$driverBaseline.InstanceId;HardwareIds=@($driverBaseline.HardwareIds);OldInfName=$driverBaseline.InfName;OldDriverVersion=$driverBaseline.DriverVersion
OldStatus=$driverBaseline.Status;OldProviderName=$driverBaseline.DriverProviderName;OldSigner=$driverBaseline.Signer
CandidateDriverVersion=$candidateDriverVersion;CandidateDriverVerRaw=$candidate.DriverVer;CandidateHardwareId=$candidate.HardwareId;CandidateModelsSection=$candidate.ModelsSection;CandidateDDInstallSection=$candidate.DDInstallSection
CandidateProviderName=$candidate.ProviderName;CandidateInstalledSigner=$expectedInstalledSigner;CandidateInfFileName=$candidate.InfFileName;CandidateInfSha256=$candidate.InfSha256
CandidateCatalogFile=$candidate.CatalogFile;CandidateCatalogSha256=$candidate.CatalogSha256;CandidateCatalogSignerIdentityJson=$candidate.CatalogSignerIdentityJson
CandidateCatalogSignerBindingSha256=$candidate.CatalogSignerBindingSha256;CandidatePackageFileCount=$candidate.PackageFileCount
CandidatePackageManifestSha256=$candidate.PackageManifestSha256;CandidateFullBindingSha256=$candidate.FullBindingSha256
FunctionalTestProcedure=$deviceFunctionalTest;FunctionalTestSha256=$functionalTestSha256;InfPath=(Resolve-Path -LiteralPath $infPath).Path
}
$checkpointJson=$checkpoint|ConvertTo-Json -Depth 10;$checkpointJson
Write-Host "上のJSONをメモ帳で $checkpointPath へ手動保存します。既存fileは上書きしません。"
Read-Host '手動保存後、Enterでcheckpointを再読します'
if(-not(Test-Path -LiteralPath $checkpointPath -PathType Leaf)){throw 'checkpointがありません。Device Managerを開きません。'}
$persisted=Get-Content -LiteralPath $checkpointPath -Raw -ErrorAction Stop|ConvertFrom-Json
if(($persisted|ConvertTo-Json -Depth 10 -Compress) -cne ($checkpoint|ConvertTo-Json -Depth 10 -Compress)){throw '手動保存checkpointの全identity/fingerprint/testが一致しません。'}
$checkpointSha256=(Get-FileHash -LiteralPath $checkpointPath -Algorithm SHA256 -ErrorAction Stop).Hash
$approvalToken="OPEN-DEVICE-MANAGER $instanceId OP=$operationId CHECKPOINT=$checkpointSha256 BINDING=$($candidate.FullBindingSha256)"
if((Read-Host "GUI手順を表示する場合は $approvalToken を入力") -ne $approvalToken){throw '中止しました。'}
$deviceRebind=Get-PnpDevice -InstanceId $driverBaseline.InstanceId -ErrorAction Stop
$hardwareIdRebind=@((Get-PnpDeviceProperty -InstanceId $driverBaseline.InstanceId -KeyName 'DEVPKEY_Device_HardwareIds' -ErrorAction Stop).Data)
$candidateRebind=Get-IttripDriverCandidateBinding -Path $infPath -HardwareId $hardwareIds[0]
if($deviceRebind.InstanceId -ne $driverBaseline.InstanceId -or ($hardwareIdRebind-join '|') -ne ($driverBaseline.HardwareIds-join '|') -or $candidateRebind.FullBindingSha256 -ne $candidate.FullBindingSha256){throw '直前のdevice/HWID/package全結合が変わりました。'}
[pscustomobject]@{Checkpoint=$checkpointPath;CheckpointSha256=$checkpointSha256;CandidateDriverVersion=$candidateDriverVersion;CandidateBinding=$candidate.FullBindingSha256;FunctionalTest=$deviceFunctionalTest;ExecuteAutomatically=$false}
Write-Host 'デバイス マネージャーで同じInstanceIdのデバイスを選び、ドライバーの更新 > コンピューターを参照、で承認済みINFフォルダーを一回だけ指定します。PowerShellやPnPUtilは自動適用しません。'
Write-Host '再起動要求の有無を記録します。要求された場合は再起動し、次のstandalone検証ブロックをUpdate modeで実行します。'
全デバイスの無差別スキャンを更新戦略にしない。出所不明INF、署名無効、別モデル用パッケージなら中止する。署名済みハードウェアドライバーの更新でプレビュー対応コマンドを使える場合はWhatIfを先に実行し、非対応の操作は対象一覧と引数を画面へ出して人が承認してから一度だけ実行します。
手動適用後に承認パッケージを照合
手動適用後に承認パッケージを照合では同じ対象を別経路でもう一度読みます。判定したいのは「コマンドが終了したか」ではなく、再起動後に対象InstanceIdのDriverVersionとInfNameが承認版となり、Status=OKで機器テストが通るかどうかです。
$checkpointPath='C:\ProgramData\ITtrip\DriverUpdate\ittrip-driver-update-checkpoint.json'
$expectedCheckpointSha256='PASTE_CHECKPOINT_SHA256_FROM_PREVIOUS_BLOCK'
$verificationMode='Update' # Update または Rollback
$rebootWasRequired=$true # Device Managerの実測へ置換
if($expectedCheckpointSha256 -like 'PASTE_*'){throw '保存したcheckpoint SHA256へ置き換えてください。'}
if((Get-FileHash -LiteralPath $checkpointPath -Algorithm SHA256 -ErrorAction Stop).Hash -ne $expectedCheckpointSha256){throw 'checkpoint SHA256が保存時と一致しません。'}
$checkpoint=Get-Content -LiteralPath $checkpointPath -Raw -ErrorAction Stop|ConvertFrom-Json
if([int]$checkpoint.Schema -ne 2 -or $verificationMode -notin @('Update','Rollback')){throw 'checkpoint schemaまたはverification modeが不正です。'}
function Get-IttripStringSha256([string]$Text){$algorithm=[Security.Cryptography.SHA256]::Create();try{([BitConverter]::ToString($algorithm.ComputeHash([Text.Encoding]::UTF8.GetBytes($Text)))).Replace('-','').ToLowerInvariant()}finally{$algorithm.Dispose()}}
function Get-IttripBytesSha256([byte[]]$Bytes){$algorithm=[Security.Cryptography.SHA256]::Create();try{([BitConverter]::ToString($algorithm.ComputeHash($Bytes))).Replace('-','').ToLowerInvariant()}finally{$algorithm.Dispose()}}
function Get-IttripCertificateBinding($Certificate){[ordered]@{Thumbprint=$Certificate.Thumbprint.Replace(' ','').ToUpperInvariant();Subject=[string]$Certificate.Subject;Issuer=[string]$Certificate.Issuer;SerialNumber=[string]$Certificate.SerialNumber;NotBeforeUtc=$Certificate.NotBefore.ToUniversalTime().ToString('o');NotAfterUtc=$Certificate.NotAfter.ToUniversalTime().ToString('o');SignatureAlgorithmOid=[string]$Certificate.SignatureAlgorithm.Value;PublicKeyAlgorithmOid=[string]$Certificate.PublicKey.Oid.Value;RawCertificateSha256=Get-IttripBytesSha256 $Certificate.RawData}}
function Get-IttripPackageManifest([string]$PackageRoot){
$rows=@(Get-ChildItem -LiteralPath $PackageRoot -File -Recurse -ErrorAction Stop|ForEach-Object{[pscustomobject]@{RelativePath=$_.FullName.Substring($PackageRoot.Length).TrimStart('\').Replace('\','/');Length=[int64]$_.Length;Sha256=(Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant()}}|Sort-Object RelativePath)
if($rows.Count -eq 0){throw 'installed driver packageが空です。'}
[pscustomobject]@{FileCount=$rows.Count;Sha256=Get-IttripStringSha256 ($rows|ConvertTo-Json -Depth 4 -Compress)}
}
function Get-IttripInstalledPackageBinding([string]$PublishedInfName,$Checkpoint){
$basic=@(Get-WindowsDriver -Online -All -ErrorAction Stop|Where-Object{[StringComparer]::OrdinalIgnoreCase.Equals([string]$_.Driver,$PublishedInfName)})
if($basic.Count -ne 1){throw "published INFをdriver store inventoryへ一意に結合できません: $($basic.Count)件"}
$originalLeaf=[IO.Path]::GetFileName([string]$basic[0].OriginalFileName)
try{$inventoryVersion=([version][string]$basic[0].Version).ToString()}catch{throw 'installed package versionを正規化できません。'}
if(-not [StringComparer]::OrdinalIgnoreCase.Equals($originalLeaf,[string]$Checkpoint.CandidateInfFileName) -or
-not [StringComparer]::Ordinal.Equals([string]$basic[0].ProviderName,[string]$Checkpoint.CandidateProviderName) -or
$inventoryVersion -ne ([version][string]$Checkpoint.CandidateDriverVersion).ToString()){throw 'published INFのOriginalFileName/provider/versionが承認候補と一致しません。'}
$storeRoot=(Join-Path $env:windir 'System32\DriverStore\FileRepository').TrimEnd('\')
$infCandidates=@(Get-ChildItem -LiteralPath $storeRoot -Filter $originalLeaf -File -Recurse -ErrorAction Stop|Select-Object -ExpandProperty FullName -Unique)
$matched=@(foreach($installedInf in $infCandidates){
$packageRoot=(Split-Path -Parent $installedInf).TrimEnd('\')
$infHash=(Get-FileHash -LiteralPath $installedInf -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant();if($infHash -ne [string]$Checkpoint.CandidateInfSha256){continue}
$catalogPath=Join-Path $packageRoot ([string]$Checkpoint.CandidateCatalogFile);if(-not(Test-Path -LiteralPath $catalogPath -PathType Leaf)){continue}
$catalogHash=(Get-FileHash -LiteralPath $catalogPath -Algorithm SHA256 -ErrorAction Stop).Hash.ToLowerInvariant();if($catalogHash -ne [string]$Checkpoint.CandidateCatalogSha256){continue}
$signature=Get-AuthenticodeSignature -FilePath $catalogPath -ErrorAction Stop;if($signature.Status -ne 'Valid' -or -not $signature.SignerCertificate){continue}
$certJson=(Get-IttripCertificateBinding $signature.SignerCertificate)|ConvertTo-Json -Depth 5 -Compress;$certHash=Get-IttripStringSha256 $certJson
if($certHash -ne [string]$Checkpoint.CandidateCatalogSignerBindingSha256 -or $certJson -cne [string]$Checkpoint.CandidateCatalogSignerIdentityJson){continue}
$manifest=Get-IttripPackageManifest $packageRoot
if($manifest.FileCount -ne [int]$Checkpoint.CandidatePackageFileCount -or $manifest.Sha256 -ne [string]$Checkpoint.CandidatePackageManifestSha256){continue}
$fingerprintText="$($Checkpoint.CandidateHardwareId)|$($Checkpoint.CandidateModelsSection)|$($Checkpoint.CandidateDDInstallSection)|$($Checkpoint.CandidateDriverVerRaw)|$($Checkpoint.CandidateProviderName)|$($Checkpoint.CandidateInfFileName)|$infHash|$($Checkpoint.CandidateCatalogFile)|$catalogHash|$certHash|$($manifest.Sha256)"
$fullBinding=Get-IttripStringSha256 $fingerprintText
if($fullBinding -eq [string]$Checkpoint.CandidateFullBindingSha256){[pscustomobject]@{PublishedInfName=$PublishedInfName;OriginalInfFileName=$originalLeaf;DriverStoreInf=$installedInf;ProviderName=[string]$basic[0].ProviderName;Version=$inventoryVersion;InfSha256=$infHash;CatalogSha256=$catalogHash;CatalogSignerBindingSha256=$certHash;PackageManifestSha256=$manifest.Sha256;FullBindingSha256=$fullBinding}}
})
if($matched.Count -ne 1){throw "published INFから承認済みDriverStore package全identityへ一意に結合できません: $($matched.Count)件"}
$matched[0]
}
$currentBootUtc=(Get-CimInstance Win32_OperatingSystem -ErrorAction Stop).LastBootUpTime.ToUniversalTime()
if($rebootWasRequired -and ($currentBootUtc -le ([datetime]$checkpoint.OperationBootUtc).ToUniversalTime() -or $currentBootUtc -le ([datetime]$checkpoint.OperationUtc).ToUniversalTime())){throw '更新操作後の別起動境界がありません。'}
$device=Get-PnpDevice -InstanceId ([string]$checkpoint.InstanceId) -ErrorAction Stop
$hardwareIds=@((Get-PnpDeviceProperty -InstanceId ([string]$checkpoint.InstanceId) -KeyName 'DEVPKEY_Device_HardwareIds' -ErrorAction Stop).Data)
$drivers=@(Get-CimInstance Win32_PnPSignedDriver -ErrorAction Stop|Where-Object DeviceID -eq ([string]$checkpoint.InstanceId))
if($drivers.Count -ne 1 -or ($hardwareIds-join '|') -ne (@($checkpoint.HardwareIds)-join '|')){throw 'standalone検証で同じInstanceId/HWID/driver rowを一意にできません。'}
try{$actualVersion=([version][string]$drivers[0].DriverVersion).ToString();$oldVersion=([version][string]$checkpoint.OldDriverVersion).ToString();$targetVersion=([version][string]$checkpoint.CandidateDriverVersion).ToString()}catch{throw 'driver versionを正規化できません。'}
if($verificationMode -eq 'Rollback'){
$rollbackExact=$device.Status -eq $checkpoint.OldStatus -and [string]$drivers[0].InfName -eq [string]$checkpoint.OldInfName -and $actualVersion -eq $oldVersion -and [string]$drivers[0].DriverProviderName -eq [string]$checkpoint.OldProviderName -and [string]$drivers[0].Signer -eq [string]$checkpoint.OldSigner
[pscustomobject]@{Mode='Rollback';InstanceId=$device.InstanceId;ExpectedInf=$checkpoint.OldInfName;ActualInf=$drivers[0].InfName;ExpectedVersion=$oldVersion;ActualVersion=$actualVersion;ExpectedStatus=$checkpoint.OldStatus;ActualStatus=$device.Status;ExpectedProvider=$checkpoint.OldProviderName;ActualProvider=$drivers[0].DriverProviderName;ExpectedSigner=$checkpoint.OldSigner;ActualSigner=$drivers[0].Signer;ExactOldDriver=$rollbackExact}
if(-not $rollbackExact){throw 'Roll Back Driver後のold InstanceId/INF/version/status/provider/signer exact rereadに失敗しました。'}
return
}
$installed=Get-IttripInstalledPackageBinding -PublishedInfName ([string]$drivers[0].InfName) -Checkpoint $checkpoint
$functionalToken="FUNCTIONAL-PASS OP=$($checkpoint.OperationId) TEST=$($checkpoint.FunctionalTestSha256)"
Write-Host "機器固有testを実行してください: $($checkpoint.FunctionalTestProcedure)"
$functionalPassed=(Read-Host "成功時だけ $functionalToken を入力") -ceq $functionalToken
$updateExact=$device.Status -eq 'OK' -and $actualVersion -eq $targetVersion -and $actualVersion -ne $oldVersion -and
[string]$drivers[0].DriverProviderName -eq [string]$checkpoint.CandidateProviderName -and [string]$drivers[0].Signer -eq [string]$checkpoint.CandidateInstalledSigner -and
$installed.FullBindingSha256 -eq [string]$checkpoint.CandidateFullBindingSha256 -and $functionalPassed
[pscustomobject]@{Mode='Update';InstanceId=$device.InstanceId;OldVersion=$oldVersion;CandidateVersion=$targetVersion;ActualVersion=$actualVersion;ActualInf=$drivers[0].InfName;Provider=$drivers[0].DriverProviderName;Signer=$drivers[0].Signer;Status=$device.Status;InstalledPackageBinding=$installed.FullBindingSha256;FunctionalTestPassed=$functionalPassed;ExactApprovedUpdate=$updateExact}
if(-not $updateExact){Write-Warning '旧版、別版、同version別package、provider/signer/catalog不一致、Status非OK、または機器固有test失敗です。デバイス マネージャーの「ドライバーを元に戻す」を一回実行し、必要なら再起動後、このブロックをRollback modeで再実行します。';throw '承認済みpackage全identityと機器固有testを満たさず、更新は未完了です。'}
$rollbackToken="OPEN-ROLL-BACK-DRIVER $($checkpoint.InstanceId) FROM=$actualVersion TO=$oldVersion BINDING=$($installed.FullBindingSha256)"
if((Read-Host "任意の手動rollback手順を表示する場合だけ $rollbackToken を入力。不要ならEnter") -eq $rollbackToken){
$rollbackRebind=Get-PnpDevice -InstanceId ([string]$checkpoint.InstanceId) -ErrorAction Stop;$rollbackDriver=@(Get-CimInstance Win32_PnPSignedDriver -ErrorAction Stop|Where-Object DeviceID -eq ([string]$checkpoint.InstanceId))
if($rollbackDriver.Count -ne 1 -or ([version][string]$rollbackDriver[0].DriverVersion).ToString() -ne $targetVersion -or $rollbackRebind.Status -ne 'OK' -or (Get-IttripInstalledPackageBinding -PublishedInfName ([string]$rollbackDriver[0].InfName) -Checkpoint $checkpoint).FullBindingSha256 -ne [string]$checkpoint.CandidateFullBindingSha256){throw '手動rollback直前の承認package identity/version/statusが一致しません。'}
Write-Host 'デバイス マネージャーで同じInstanceIdの「ドライバーを元に戻す」を一回実行します。必要なら再起動後、このブロックをRollback modeへ変更してold INF/version/status/provider/signerを再読します。'
}
適用対象0件ならINFの対応Hardware ID、アーキテクチャ、OSビルド、ドライバーランクを確認する。署名済みハードウェアドライバーの更新の期待値と実測値が一致しないときは追加変更を重ねず、対象識別、権限、ポリシー、時間差の順で原因を分けます。
再起動後に版とデバイス状態を確認
全デバイスの無差別スキャンを更新戦略にしない。出所不明INF、署名無効、別モデル用パッケージなら中止する。再起動後に版とデバイス状態を確認に該当したら、警告を消して継続するのではなく、どの条件で止まったかを記録します。
適用対象0件ならINFの対応Hardware ID、アーキテクチャ、OSビルド、ドライバーランクを確認する。署名済みハードウェアドライバーの更新ではエラー本文、FullyQualifiedErrorId、対象ID、直前に成功した段階を残すと、別担当者が安全な地点から調査できます。
問題時はベンダーのロールバックへ
パッケージハッシュ、署名者、INF名、対象InstanceId、再起動要否、試験結果を変更記録へ残す。署名済みハードウェアドライバーの更新を繰り返す場合は、正常、対象なし、要承認、失敗を異なる終了状態として記録し、前回値との比較だけで異常を決めません。
| 問題時はベンダーのロールバックへの識別軸 | PnpDevice InstanceId、Win32_PnPSignedDriverのInfName、DriverVersion、Manufacturerを更新前後で照合する |
| 採用する実測 | 再起動後に対象InstanceIdのDriverVersionとInfNameが承認版となり、Status=OKで機器テストが通る |
| 0件時の扱い | 適用対象0件ならINFの対応Hardware ID、アーキテクチャ、OSビルド、ドライバーランクを確認する |
| 保留にする兆候 | 全デバイスの無差別スキャンを更新戦略にしない。出所不明INF、署名無効、別モデル用パッケージなら中止する |
署名済みハードウェアドライバーの更新の実行記録には、開始前の対象候補、採用した識別値、実行したコード、終了後の実測、除外した候補と理由を同じ作業番号で残します。特に「PnpDevice InstanceId、Win32_PnPSignedDriverのInfName、DriverVersion、Manufacturerを更新前後で照合する」を省くと、後日の再確認で別対象の値を比較するおそれがあります。画面コピーだけでなく、日時と端末名を含む構造化した出力も保存します。
PowerShellを使ってハードウェアのドライバをアップデートする方法を定期手順へ組み込む場合も、初回は対話的に候補を確認します。正常時は「再起動後に対象InstanceIdのDriverVersionとInfNameが承認版となり、Status=OKで機器テストが通る」、判定不能時は「適用対象0件ならINFの対応Hardware ID、アーキテクチャ、OSビルド、ドライバーランクを確認する」、中止時は「全デバイスの無差別スキャンを更新戦略にしない。出所不明INF、署名無効、別モデル用パッケージなら中止する」をそれぞれ別の結果として扱います。これにより、0件や例外を都合よく成功へ丸めず、次の担当者が同じ対象と条件で追試できます。
修正後コードの合格条件:対象Hardware IDをINFのdecorated ModelsとDDInstallへ一意に結合し、承認済みINF、全package manifest、Provider、Catalog SHA256、証明書全identityを固定します。InstanceIdとcheckpoint hashを含むトークンを入力した場合だけ、デバイス マネージャーの手動適用手順を表示します。
再修正後は期待DriverVersionだけでなく、published INFからDriverStore内の承認packageへ結合した全fingerprintを検証します。同versionの別package、Provider・Signer・Catalog差、Status非OK、機器固有test失敗はすべて未完了とし、デバイス マネージャーの手動rollback後に旧INF・版・状態・Provider・Signerを再取得します。
安全版 r4b-20260719:PnPUtilによる自動インストール・削除・rollbackを廃止しました。HWIDからdecorated Models、DDInstall、DriverVer、Catalog署名、全package hashを結合し、更新はデバイス マネージャーで手動実施します。再起動要求時は次回起動後に別工程で再読します。
独立監査追補 r4b-r5-20260719:更新前のdevice/HWID/old INF・version・status、candidate DriverVer/fingerprint、operation/boot時刻を手動保存checkpointへ固定します。standalone verifierは実versionと正規化DriverVerを比較し、手動Roll Back Driver後もold INF/version/statusをexact再読します。
独立監査追補 final-r2-seq003-040-049-20260719:変更操作はデバイス マネージャーの一回の手動適用に統一しました。standalone verifierはpublished INF、DriverStoreのINF hash、全file manifest、Provider、Catalog hash、証明書のSubject/Issuer/serial/期間/algorithm/raw certificate hash、driver rowのSignerと機器固有testを同時に満たす場合だけExactApprovedUpdateとします。
公式情報・参考資料
署名済みハードウェアドライバーの更新で使うコマンド名、引数、対応環境は次のMicrosoft一次資料で確認しました。記事の確認日は2026年7月17日です。OSやモジュール更新後は、実行端末のGet-Helpと併せて再確認してください。

コメント