Public/HP.Client.Update/Private/Resolve-HPUpdateState.ps1
|
function Get-HPUpdateReferenceCatalog { param( [Parameter(Mandatory)][string]$Platform, [Parameter(Mandatory)][psobject]$OperatingSystem, [string]$CatalogDirectory = (Get-HPUpdatePath -Name Catalogs) ) $architecture = if ([Environment]::Is64BitOperatingSystem) { '64' } else { '32' } $referenceDirectory = Join-Path $CatalogDirectory 'Reference' $versions = @($(if ($OperatingSystem.Family -eq 'win11') { '11.0' } else { '10.0' }) + '.' + $OperatingSystem.Version.ToLowerInvariant()) if ($OperatingSystem.Family -eq 'win11' -and $OperatingSystem.Version -eq '25H2') { $versions += '11.0.24h2' } $xmlPath = $null $expandPath = Join-Path $env:SystemRoot 'System32\expand.exe' function Test-HPUpdateCabSignature { param([Parameter(Mandatory)][string]$Path) try { $stream = [System.IO.File]::OpenRead($Path) try { if ($stream.Length -lt 4) { return $false } $buffer = [byte[]]::new(4) $null = $stream.Read($buffer, 0, 4) return ([System.Text.Encoding]::ASCII.GetString($buffer) -eq 'MSCF') } finally { $stream.Dispose() } } catch { return $false } } function Get-HPUpdateExtractedXmlPath { param([Parameter(Mandatory)][string]$Directory) $xmlPathByName = @(Get-ChildItem -LiteralPath $Directory -Filter '*.xml' -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName) if ($xmlPathByName) { return $xmlPathByName } foreach ($candidate in @(Get-ChildItem -LiteralPath $Directory -File -Recurse -ErrorAction SilentlyContinue)) { try { $document = [System.Xml.XmlDocument]::new() $document.XmlResolver = $null $document.Load($candidate.FullName) if ($document.DocumentElement -and $document.DocumentElement.LocalName -eq 'ImagePal') { return $candidate.FullName } } catch [System.Xml.XmlException] { } } $null } foreach ($version in $versions) { $cabReferenceName = "${Platform}_${architecture}_${version}" $cabPath = Join-Path $referenceDirectory "$cabReferenceName.cab" $extractDirectory = "$cabPath.dir" $xmlPath = @(Get-ChildItem -LiteralPath $extractDirectory -Filter '*.xml' -File -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName) if ($xmlPath) { break } $null = New-Item -Path $referenceDirectory -ItemType Directory -Force try { Invoke-WebRequest -Uri "https://hpia.hpcloud.hp.com/ref/$Platform/$cabReferenceName.cab" -OutFile $cabPath -UseBasicParsing -ErrorAction Stop } catch { $statusCode = $null if ($_.Exception.Response -and $_.Exception.Response.StatusCode) { $statusCode = [int]$_.Exception.Response.StatusCode } if ($version -eq '11.0.25h2' -and $statusCode -eq 404) { continue } throw } if (-not (Test-Path -LiteralPath $cabPath -PathType Leaf)) { continue } $cabItem = Get-Item -LiteralPath $cabPath -ErrorAction SilentlyContinue if (-not $cabItem -or $cabItem.Length -le 0) { continue } Remove-Item -LiteralPath $extractDirectory -Recurse -Force -ErrorAction SilentlyContinue $null = New-Item -Path $extractDirectory -ItemType Directory -Force $queue = [System.Collections.Generic.Queue[string]]::new() $queue.Enqueue($cabPath) $depth = 0 while ($queue.Count -and -not $xmlPath -and $depth -lt 4) { $depth++ $currentCabPath = $queue.Dequeue() $stageDirectory = Join-Path $extractDirectory "stage$depth" Remove-Item -LiteralPath $stageDirectory -Recurse -Force -ErrorAction SilentlyContinue $null = New-Item -Path $stageDirectory -ItemType Directory -Force $expandOutput = & $expandPath $currentCabPath '-F:*' $stageDirectory 2>&1 if ($LASTEXITCODE -ne 0) { Write-Verbose "Could not extract '$currentCabPath' (expand.exe exit code $LASTEXITCODE). $($expandOutput -join ' ')" continue } $xmlPath = @(Get-HPUpdateExtractedXmlPath -Directory $stageDirectory) if ($xmlPath) { break } foreach ($innerCab in @(Get-ChildItem -LiteralPath $stageDirectory -Filter '*.cab' -File -Recurse -ErrorAction SilentlyContinue | Select-Object -ExpandProperty FullName)) { if ($innerCab -and $innerCab -ne $currentCabPath -and (Test-HPUpdateCabSignature -Path $innerCab)) { $queue.Enqueue($innerCab) } } } if ($xmlPath) { break } } if (-not $xmlPath) { throw "HP reference XML was not created from '$cabPath'." } $catalog = [System.Xml.XmlDocument]::new() $catalog.XmlResolver = $null $catalog.Load($xmlPath) $catalog } function Get-HPUpdateDriverInventory { @(Get-CimInstance -ClassName Win32_PnPSignedDriver -ErrorAction Stop | Where-Object { $_.DeviceID -and $_.DriverVersion }) } function ConvertTo-HPUpdateVersion { param([object]$Value) $match = [regex]::Match([string]$Value, '\d+(?:[.,]\d+){1,3}') if (-not $match.Success) { return $null } $version = $null if ([version]::TryParse(($match.Value -replace ',', '.'), [ref]$version)) { return $version } $null } function Resolve-HPUpdateDetailPath { param([Parameter(Mandatory)][string]$Path) $resolvedPath = $Path $tokenPaths = [ordered]@{ '<WINSYSDIR>' = (Join-Path $env:windir 'System32') '<DRIVERS>' = (Join-Path $env:windir 'System32\drivers') '<WINDIR>' = $env:windir '<WINDISK>' = [IO.Path]::GetPathRoot($env:windir).TrimEnd('\') '<SYSTEMDRIVE>' = $env:SystemDrive '<PROGRAMFILESDIRX86>' = ${env:ProgramFiles(x86)} '<PROGRAMFILESDIR>' = $env:ProgramFiles '<PROGRAMFILES>' = $env:ProgramFiles '<COMMONPROGRAMFILES>' = $env:CommonProgramFiles '<PROGRAMDATA>' = $env:ProgramData } foreach ($token in $tokenPaths.Keys) { if ($tokenPaths[$token]) { $resolvedPath = $resolvedPath -ireplace [regex]::Escape($token), [string]$tokenPaths[$token] } } if ($resolvedPath -match '<[^>]+>') { return $null } $resolvedPath } function Get-HPUpdateDeviceMatches { param( [Parameter(Mandatory)][psobject]$Softpaq, [object[]]$DriverInventory = @(), [System.Xml.XmlDocument]$ReferenceCatalog ) $deviceMatches = [System.Collections.Generic.List[object]]::new() foreach ($device in @($Softpaq.devices)) { $deviceId = ([string]$device.matchId).Trim().Trim('*') if ([string]::IsNullOrWhiteSpace($deviceId)) { continue } $installedDevice = @($DriverInventory | Where-Object { $_.DeviceID.IndexOf($deviceId, [StringComparison]::OrdinalIgnoreCase) -ge 0 } | Select-Object -First 1) if (-not $installedDevice) { continue } $referenceDevice = if ($ReferenceCatalog) { @($ReferenceCatalog.SelectNodes('ImagePal/Devices/Device') | Where-Object { ([string]$_.DeviceID).IndexOf($deviceId, [StringComparison]::OrdinalIgnoreCase) -ge 0 } | Select-Object -First 1) } else { $null } $deviceMatches.Add([pscustomobject]@{ DeviceName = [string]$installedDevice.DeviceName DeviceId = [string]$installedDevice.DeviceID InstalledVersion = [string]$installedDevice.DriverVersion ExpectedVersion = if ($referenceDevice) { [string]$referenceDevice.DriverVersion } else { '' } ReferenceMatched = [bool]$referenceDevice }) } @($deviceMatches) } function Get-HPUpdateDetailFileState { param( [Parameter(Mandatory)][psobject]$Softpaq, [Parameter(Mandatory)][psobject]$OperatingSystem, [Parameter(Mandatory)][hashtable]$FileVersionCache ) $osCode = ('{0}_{1}' -f $OperatingSystem.Family.ToUpperInvariant().Replace('WIN', 'W'), $OperatingSystem.Version) $results = [System.Collections.Generic.List[object]]::new() foreach ($detailFile in @($Softpaq.detailFileInfo | Where-Object { $_.cvaOsCode -eq $osCode })) { $expectedVersion = ConvertTo-HPUpdateVersion "$($detailFile.majorVersion).$($detailFile.minorVersion).$($detailFile.majorBuild).$($detailFile.minorBuild)" if (-not $expectedVersion -or -not $detailFile.fileName) { continue } $cacheKey = [string]$detailFile.fileName if (-not $FileVersionCache.ContainsKey($cacheKey)) { $candidates = [System.Collections.Generic.List[object]]::new() $expandedPath = Resolve-HPUpdateDetailPath -Path ([string]$detailFile.path) if ($expandedPath) { try { $directPath = Join-Path $expandedPath $detailFile.fileName if (Test-Path -LiteralPath $directPath -PathType Leaf) { $candidates.Add((Get-Item -LiteralPath $directPath)) } } catch { Write-Verbose "Ignoring invalid HP detail-file path '$($detailFile.path)': $($_.Exception.Message)" } } if ($expandedPath -and $expandedPath -match '(?i)DriverStore\\FileRepository') { $driverStore = Join-Path $env:windir 'System32\DriverStore\FileRepository' foreach ($file in (Get-ChildItem -LiteralPath $driverStore -Filter $detailFile.fileName -File -Recurse -ErrorAction SilentlyContinue)) { $candidates.Add($file) } } $FileVersionCache[$cacheKey] = @($candidates) } $files = @($FileVersionCache[$cacheKey]) $installedVersions = @($files | ForEach-Object { ConvertTo-HPUpdateVersion $_.VersionInfo.FileVersion } | Where-Object { $_ }) $results.Add([pscustomobject]@{ FileName = [string]$detailFile.fileName ExpectedVersion = $expectedVersion InstalledVersions = $installedVersions InstalledVersion = @($installedVersions | Sort-Object -Descending | Select-Object -First 1)[0] FilesFound = $files.Count UsesDriverBinaryPath = ([string]$detailFile.path) -match '(?i)<DRIVERS>' }) } @($results) } function Get-HPUpdateWindowsUpdateVersions { param([Parameter(Mandatory)][psobject]$Softpaq) @($Softpaq.windowsUpdates | Where-Object { $_ -match '(?i)SoftwareComponent' } | ForEach-Object { [regex]::Matches([string]$_, '\d+(?:\.\d+){1,3}') | ForEach-Object { ConvertTo-HPUpdateVersion $_.Value } } | Where-Object { $_ } | Sort-Object -Descending -Unique) } function Resolve-HPUpdateState { param( [Parameter(Mandatory)][psobject]$Softpaq, [Parameter(Mandatory)][object[]]$DriverInventory, [Parameter(Mandatory)][System.Xml.XmlDocument]$ReferenceCatalog, [Parameter(Mandatory)][psobject]$OperatingSystem, [Parameter(Mandatory)][hashtable]$FileVersionCache ) $matches = @(Get-HPUpdateDeviceMatches -Softpaq $Softpaq -DriverInventory $DriverInventory -ReferenceCatalog $ReferenceCatalog) if (-not $matches.Count) { return [pscustomobject]@{ IsApplicable = $false; IsInstalled = $null; WhyApplicable = 'No supported installed device was found.'; Matches = @() } } $driverBinaryMatches = @($matches | Where-Object { $installedVersion = $null; $expectedVersion = $null [version]::TryParse($_.InstalledVersion, [ref]$installedVersion) -and [version]::TryParse($_.ExpectedVersion, [ref]$expectedVersion) -and $installedVersion -lt $expectedVersion }) $driverCurrentMatches = @($matches | Where-Object { $installedVersion = $null; $expectedVersion = $null [version]::TryParse($_.InstalledVersion, [ref]$installedVersion) -and [version]::TryParse($_.ExpectedVersion, [ref]$expectedVersion) -and $installedVersion -ge $expectedVersion }) $familyKey = @($matches.DeviceId | Sort-Object -Unique) -join '|' $detailFiles = @(Get-HPUpdateDetailFileState -Softpaq $Softpaq -OperatingSystem $OperatingSystem -FileVersionCache $FileVersionCache) $outdatedFiles = @($detailFiles | Where-Object { $_.InstalledVersion -and $_.InstalledVersion -lt $_.ExpectedVersion }) $currentFiles = @($detailFiles | Where-Object { $_.InstalledVersion -and $_.InstalledVersion -ge $_.ExpectedVersion }) if ($currentFiles.Count) { return [pscustomobject]@{ IsApplicable = $true; IsInstalled = $true; WhyApplicable = 'Installed HP package detail files meet or exceed the catalog version.'; Matches = @($matches); PnpOutdated = [bool]$driverBinaryMatches.Count; FamilyKey = $familyKey } } $windowsUpdateVersions = @(Get-HPUpdateWindowsUpdateVersions -Softpaq $Softpaq) $hasNewerWindowsUpdateComponent = [bool]($driverCurrentMatches | Where-Object { $installedVersion = ConvertTo-HPUpdateVersion $_.InstalledVersion $installedVersion -and @($windowsUpdateVersions | Where-Object { $_ -gt $installedVersion }).Count } | Select-Object -First 1) if ($hasNewerWindowsUpdateComponent) { return [pscustomobject]@{ IsApplicable = $true; IsInstalled = $false; WhyApplicable = 'An HP Windows Update component version is newer than the active driver version.'; Matches = @($matches); PnpOutdated = $true; FamilyKey = $familyKey } } if ($driverCurrentMatches.Count) { return [pscustomobject]@{ IsApplicable = $true; IsInstalled = $true; WhyApplicable = 'The active PnP driver version meets or exceeds the HP reference catalog version.'; Matches = @($matches); PnpOutdated = [bool]$driverBinaryMatches.Count; FamilyKey = $familyKey } } $unresolvedPackageDetails = @($detailFiles | Where-Object { $_.FilesFound -eq 0 -and -not $_.UsesDriverBinaryPath }) if ($detailFiles.Count -and $unresolvedPackageDetails.Count -eq $detailFiles.Count) { return [pscustomobject]@{ IsApplicable = $false; IsInstalled = $null; WhyApplicable = 'HP package-specific detail files are absent, so the associated PnP device alone is insufficient to establish an update requirement.'; Matches = @($matches); PnpOutdated = [bool]$driverBinaryMatches.Count; FamilyKey = $familyKey } } if ($outdatedFiles.Count) { return [pscustomobject]@{ IsApplicable = $true; IsInstalled = $false; WhyApplicable = 'An installed HP package detail file is older than the catalog version.'; Matches = @($matches); PnpOutdated = [bool]$driverBinaryMatches.Count; FamilyKey = $familyKey } } [pscustomobject]@{ IsApplicable = $true; IsInstalled = -not $driverBinaryMatches.Count; WhyApplicable = if ($driverBinaryMatches.Count) { 'Installed driver version is older than the HP reference catalog version.' } else { 'No local package detail file was found for this supported device.' }; Matches = @($matches); PnpOutdated = [bool]$driverBinaryMatches.Count; FamilyKey = $familyKey } } |