Files
FFU/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.HP.psm1
T
rbalsleyMSFT 1523091637 Feat: Add WIM compression for existing driver folders
Implements logic to compress previously downloaded driver folders into WIM files if the compression option is enabled. This ensures consistency in the driver store when re-running the download process.

Refactors the parallel task processing to rely on a standardized boolean 'Success' property from all driver download tasks. This simplifies the result handling logic and makes it more reliable than parsing status strings.
2025-06-28 00:51:22 -07:00

389 lines
22 KiB
PowerShell

# Function to get the list of HP models from the PlatformList.xml
function Get-HPDriversModelList {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string]$DriversFolder,
[Parameter(Mandatory = $true)]
[string]$Make # Expected to be 'HP'
)
WriteLog "Getting HP driver model list..."
$hpDriversFolder = Join-Path -Path $DriversFolder -ChildPath $Make
$platformListUrl = 'https://hpia.hpcloud.hp.com/ref/platformList.cab'
$platformListCab = Join-Path -Path $hpDriversFolder -ChildPath "platformList.cab"
$platformListXml = Join-Path -Path $hpDriversFolder -ChildPath "PlatformList.xml"
$modelList = [System.Collections.Generic.List[PSCustomObject]]::new()
try {
# Ensure HP drivers folder exists
if (-not (Test-Path -Path $hpDriversFolder)) {
WriteLog "Creating HP Drivers folder: $hpDriversFolder"
New-Item -Path $hpDriversFolder -ItemType Directory -Force | Out-Null
}
# Download PlatformList.cab if it doesn't exist or is outdated (e.g., older than 7 days)
if (-not (Test-Path -Path $platformListCab) -or ((Get-Date) - (Get-Item $platformListCab).LastWriteTime).TotalDays -gt 7) {
WriteLog "Downloading $platformListUrl to $platformListCab"
# Use the private helper function for download with retry
Start-BitsTransferWithRetry -Source $platformListUrl -Destination $platformListCab -ErrorAction Stop
WriteLog "PlatformList.cab download complete."
# Force extraction if downloaded
if (Test-Path -Path $platformListXml) {
Remove-Item -Path $platformListXml -Force
}
}
else {
WriteLog "Using existing PlatformList.cab found at $platformListCab"
}
# Extract PlatformList.xml if it doesn't exist
if (-not (Test-Path -Path $platformListXml)) {
WriteLog "Expanding $platformListCab to $platformListXml"
# Use the private helper function for process invocation
Invoke-Process -FilePath "expand.exe" -ArgumentList @("`"$platformListCab`"", "`"$platformListXml`"") -ErrorAction Stop | Out-Null
WriteLog "PlatformList.xml extraction complete."
}
else {
WriteLog "Using existing PlatformList.xml found at $platformListXml"
}
# Parse the PlatformList.xml using XmlReader for efficiency
WriteLog "Parsing PlatformList.xml to extract HP models..."
$settings = New-Object System.Xml.XmlReaderSettings
$settings.Async = $false # Ensure synchronous reading
$reader = [System.Xml.XmlReader]::Create($platformListXml, $settings)
$uniqueModels = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
while ($reader.Read()) {
if ($reader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $reader.Name -eq 'Platform') {
# Read the inner content of the Platform node
$platformReader = $reader.ReadSubtree()
while ($platformReader.Read()) {
if ($platformReader.NodeType -eq [System.Xml.XmlNodeType]::Element -and $platformReader.Name -eq 'ProductName') {
$modelName = $platformReader.ReadElementContentAsString()
if (-not [string]::IsNullOrWhiteSpace($modelName) -and $uniqueModels.Add($modelName)) {
# Add to list only if it's a new unique model
$modelList.Add([PSCustomObject]@{
Make = $Make
Model = $modelName
})
}
}
}
$platformReader.Close()
}
}
$reader.Close()
WriteLog "Successfully parsed $($modelList.Count) unique HP models from PlatformList.xml."
}
catch {
WriteLog "Error getting HP driver model list: $($_.Exception.Message)"
}
# Sort the list alphabetically by Model name before returning
return $modelList | Sort-Object -Property Model
}
# Function to download and extract drivers for a specific HP model (Designed for ForEach-Object -Parallel)
function Save-HPDriversTask {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[PSCustomObject]$DriverItemData, # Contains Make, Model
[Parameter(Mandatory = $true)]
[string]$DriversFolder,
[Parameter(Mandatory = $true)]
[ValidateSet("x64", "x86", "ARM64")]
[string]$WindowsArch,
[Parameter(Mandatory = $true)]
[ValidateSet(10, 11)]
[int]$WindowsRelease,
[Parameter(Mandatory = $true)]
[string]$WindowsVersion, # e.g., 22H2, 23H2, etc.
[Parameter()] # Made optional
[System.Collections.Concurrent.ConcurrentQueue[hashtable]]$ProgressQueue = $null, # Default to null
[Parameter()]
[bool]$CompressToWim = $false # New parameter for compression
)
$modelName = $DriverItemData.Model
$make = $DriverItemData.Make # Should be 'HP'
$identifier = $modelName # Unique identifier for progress updates
$sanitizedModelName = $modelName -replace '[\\/:"*?<>|]', '_'
$hpDriversBaseFolder = Join-Path -Path $DriversFolder -ChildPath $make # Changed variable name for clarity
$platformListXml = Join-Path -Path $hpDriversBaseFolder -ChildPath "PlatformList.xml"
$modelSpecificFolder = Join-Path -Path $hpDriversBaseFolder -ChildPath $sanitizedModelName # Sanitize model name for folder path
$driverRelativePath = Join-Path -Path $make -ChildPath $sanitizedModelName # Relative path for the driver folder
$finalStatus = "" # Initialize final status
$successState = $true # Assume success unless an operation fails
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Checking HP drivers for $modelName..." }
try {
# Check for existing drivers
$existingDriver = Test-ExistingDriver -Make $make -Model $sanitizedModelName -DriversFolder $DriversFolder -Identifier $identifier -ProgressQueue $ProgressQueue
if ($null -ne $existingDriver) {
# The return object from Test-ExistingDriver uses 'Model' as the identifier key.
# We need to return 'Identifier' for HP's logic.
$existingDriver | Add-Member -MemberType NoteProperty -Name 'Identifier' -Value $identifier -Force
$existingDriver.PSObject.Properties.Remove('Model')
# Special handling for existing folders that need compression
if ($CompressToWim -and $existingDriver.Status -eq 'Already downloaded') {
$wimFilePath = Join-Path -Path $hpDriversBaseFolder -ChildPath "$($sanitizedModelName).wim"
$sourceFolderPath = Join-Path -Path $hpDriversBaseFolder -ChildPath $sanitizedModelName
WriteLog "Attempting compression of existing folder '$sourceFolderPath' to '$wimFilePath'."
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Compressing existing..." }
try {
Compress-DriverFolderToWim -SourceFolderPath $sourceFolderPath -DestinationWimPath $wimFilePath -WimName $identifier -WimDescription "Drivers for $identifier" -ErrorAction Stop
$existingDriver.Status = "Already downloaded & Compressed"
$existingDriver.DriverPath = Join-Path -Path $make -ChildPath "$($sanitizedModelName).wim"
$existingDriver.Success = $true
WriteLog "Successfully compressed existing drivers for $identifier to $wimFilePath."
}
catch {
WriteLog "Error compressing existing drivers for $($identifier): $($_.Exception.Message)"
$existingDriver.Status = "Already downloaded (Compression failed)"
$existingDriver.Success = $false
}
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status $existingDriver.Status }
}
# Ensure the Success property exists on the object being returned.
if (-not $existingDriver.PSObject.Properties.Name -contains 'Success') {
$existingDriver | Add-Member -MemberType NoteProperty -Name 'Success' -Value $true
}
return $existingDriver
}
# If folder does not exist, proceed with download and extraction
WriteLog "HP drivers for '$identifier' not found locally. Starting download process..."
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Downloading..." }
# Ensure PlatformList.xml exists (it should have been downloaded by Get-HPDriversModelList)
if (-not (Test-Path -Path $platformListXml)) {
# Attempt to download/extract it again if missing
WriteLog "PlatformList.xml not found for HP task, attempting download/extract..."
$platformListUrl = 'https://hpia.hpcloud.hp.com/ref/platformList.cab'
$platformListCab = Join-Path -Path $hpDriversBaseFolder -ChildPath "platformList.cab"
# Base folder already checked/created
Start-BitsTransferWithRetry -Source $platformListUrl -Destination $platformListCab -ErrorAction Stop
if (Test-Path -Path $platformListXml) { Remove-Item -Path $platformListXml -Force }
Invoke-Process -FilePath "expand.exe" -ArgumentList @("`"$platformListCab`"", "`"$platformListXml`"") -ErrorAction Stop | Out-Null
WriteLog "PlatformList.xml download/extract complete for HP task."
if (-not (Test-Path -Path $platformListXml)) {
throw "Failed to obtain PlatformList.xml for HP driver task."
}
}
# Parse PlatformList.xml to find SystemID and OSReleaseID for the specific model
WriteLog "Parsing $platformListXml for model '$modelName' details..."
[xml]$platformListContent = Get-Content -Path $platformListXml -Raw -Encoding UTF8 -ErrorAction Stop
$platformNode = $platformListContent.ImagePal.Platform | Where-Object { $_.ProductName.'#text' -match "^$([regex]::Escape($modelName))$" } | Select-Object -First 1
if ($null -eq $platformNode) {
throw "Model '$modelName' not found in PlatformList.xml."
}
$systemID = $platformNode.SystemID
# --- OS Node Selection with Fallback Logic ---
$selectedOSNode = $null
$selectedOSVersion = $null
$selectedOSRelease = $WindowsRelease # Start with the requested release
# Complete list of Windows 11 feature-update versions (newest to oldest)
$win11Versions = @(
"24H2", "23H2", "22H2", "21H2"
)
# Complete list of Windows 10 feature-update versions (newest to oldest)
$win10Versions = @(
"22H2", "21H2", "21H1", "20H2", "2004", "1909", "1903", "1809", "1803", "1709", "1703", "1607", "1511", "1507"
)
# Helper function to find a matching OS node for a given release and version list
function Find-MatchingOSNode {
param(
[int]$ReleaseToSearch,
[array]$VersionsToSearch
)
$osNodesForRelease = $platformNode.OS | Where-Object {
($ReleaseToSearch -eq 11 -and $_.IsWindows11 -contains 'true') -or
($ReleaseToSearch -eq 10 -and ($null -eq $_.IsWindows11 -or $_.IsWindows11 -notcontains 'true'))
}
if ($null -eq $osNodesForRelease) { return $null }
foreach ($version in $VersionsToSearch) {
foreach ($osNode in $osNodesForRelease) {
$releaseIDs = $osNode.OSReleaseIdFileName -replace 'H', 'h' -split ' '
if ($releaseIDs -contains $version.ToLower()) {
return @{ Node = $osNode; Version = $version }
}
}
}
return $null
}
# 1. Attempt Exact Match (Requested Release and Version)
WriteLog "Attempting to find exact match for Win$($WindowsRelease) ($($WindowsVersion))..."
$exactMatchResult = Find-MatchingOSNode -ReleaseToSearch $WindowsRelease -VersionsToSearch @($WindowsVersion)
if ($null -ne $exactMatchResult) {
$selectedOSNode = $exactMatchResult.Node
$selectedOSVersion = $exactMatchResult.Version
WriteLog "Exact match found: Win$($selectedOSRelease) ($($selectedOSVersion))."
}
else {
WriteLog "Exact match not found for Win$($WindowsRelease) ($($WindowsVersion))."
# 2. Fallback: Same Release, Other Versions (Newest First)
WriteLog "Attempting fallback within Win$($WindowsRelease)..."
$versionsForCurrentRelease = if ($WindowsRelease -eq 11) { $win11Versions } else { $win10Versions }
$fallbackVersions = $versionsForCurrentRelease | Where-Object { $_ -ne $WindowsVersion }
$fallbackResult = Find-MatchingOSNode -ReleaseToSearch $WindowsRelease -VersionsToSearch $fallbackVersions
if ($null -ne $fallbackResult) {
$selectedOSNode = $fallbackResult.Node
$selectedOSVersion = $fallbackResult.Version
WriteLog "Fallback successful within Win$($selectedOSRelease). Using version: $($selectedOSVersion)."
}
else {
WriteLog "Fallback within Win$($WindowsRelease) unsuccessful."
# 3. Fallback: Other Release, Versions (Newest First)
$otherRelease = if ($WindowsRelease -eq 11) { 10 } else { 11 }
WriteLog "Attempting fallback to Win$($otherRelease)..."
$versionsForOtherRelease = if ($otherRelease -eq 11) { $win11Versions } else { $win10Versions }
$otherFallbackResult = Find-MatchingOSNode -ReleaseToSearch $otherRelease -VersionsToSearch $versionsForOtherRelease
if ($null -ne $otherFallbackResult) {
$selectedOSNode = $otherFallbackResult.Node
$selectedOSVersion = $otherFallbackResult.Version
$selectedOSRelease = $otherRelease
WriteLog "Fallback successful to Win$($selectedOSRelease). Using version: $($selectedOSVersion)."
}
else {
WriteLog "Fallback to Win$($otherRelease) also failed."
}
}
}
if ($null -eq $selectedOSNode) {
$allAvailableVersions = @()
if ($platformNode.OS) {
foreach ($osNode in $platformNode.OS) {
$osRel = if ($osNode.IsWindows11 -contains 'true') { 11 } else { 10 }
$relIDs = $osNode.OSReleaseIdFileName -replace 'H', 'h' -split ' '
foreach ($id in $relIDs) { $allAvailableVersions += "Win$($osRel) $($id)" }
}
}
$availableVersionsString = ($allAvailableVersions | Select-Object -Unique) -join ', '
if ([string]::IsNullOrWhiteSpace($availableVersionsString)) { $availableVersionsString = "None" }
throw "Could not find any suitable OS driver pack for model '$modelName' matching requested or fallback versions (Win$($WindowsRelease) $WindowsVersion). Available: $availableVersionsString"
}
$osReleaseIdFileName = $selectedOSNode.OSReleaseIdFileName -replace 'H', 'h'
WriteLog "Using SystemID: $systemID and OS Info: Win$($selectedOSRelease) ($($selectedOSVersion)) for '$modelName'"
$archSuffix = $WindowsArch -replace "^x", ""
$modelRelease = "$($systemID)_$($archSuffix)_$($selectedOSRelease).0.$($selectedOSVersion.ToLower())"
$driverCabUrl = "https://hpia.hpcloud.hp.com/ref/$systemID/$modelRelease.cab"
$driverCabFile = Join-Path -Path $hpDriversBaseFolder -ChildPath "$modelRelease.cab" # Store in base HP folder
$driverXmlFile = Join-Path -Path $hpDriversBaseFolder -ChildPath "$modelRelease.xml" # Store in base HP folder
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Downloading driver index..." }
WriteLog "Downloading HP Driver cab from $driverCabUrl to $driverCabFile"
Start-BitsTransferWithRetry -Source $driverCabUrl -Destination $driverCabFile -ErrorAction Stop
WriteLog "Expanding HP Driver cab $driverCabFile to $driverXmlFile"
if (Test-Path -Path $driverXmlFile) { Remove-Item -Path $driverXmlFile -Force }
Invoke-Process -FilePath "expand.exe" -ArgumentList @("`"$driverCabFile`"", "`"$driverXmlFile`"") -ErrorAction Stop | Out-Null
WriteLog "Parsing driver XML $driverXmlFile"
[xml]$driverXmlContent = Get-Content -Path $driverXmlFile -Raw -Encoding UTF8 -ErrorAction Stop
$updates = $driverXmlContent.ImagePal.Solutions.UpdateInfo | Where-Object { $_.Category -match '^Driver' }
$totalDrivers = ($updates | Measure-Object).Count
$downloadedCount = 0
WriteLog "Found $totalDrivers driver updates for $modelName."
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Found $totalDrivers drivers. Downloading..." }
if (-not (Test-Path -Path $modelSpecificFolder)) {
New-Item -Path $modelSpecificFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
}
foreach ($update in $updates) {
$driverName = $update.Name -replace '[\\/:"*?<>|]', '_'
$category = $update.Category -replace '[\\/:"*?<>|]', '_'
$version = $update.Version -replace '[\\/:"*?<>|]', '_'
$driverUrl = "https://$($update.URL)"
$driverFileName = Split-Path -Path $driverUrl -Leaf
$downloadFolder = Join-Path -Path $modelSpecificFolder -ChildPath $category
$driverFilePath = Join-Path -Path $downloadFolder -ChildPath $driverFileName
$extractFolder = Join-Path -Path $downloadFolder -ChildPath ($driverName + "_" + $version + "_" + ($driverFileName -replace '\.exe$', ''))
$downloadedCount++
$progressMsg = "($downloadedCount/$totalDrivers) Downloading $driverName..."
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status $progressMsg }
WriteLog "$progressMsg URL: $driverUrl"
if (Test-Path -Path $extractFolder) {
WriteLog "Driver already extracted to $extractFolder, skipping download."
continue
}
if (-not (Test-Path -Path $downloadFolder)) {
New-Item -Path $downloadFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
}
WriteLog "Downloading driver to: $driverFilePath"
Start-BitsTransferWithRetry -Source $driverUrl -Destination $driverFilePath -ErrorAction Stop
WriteLog "Driver downloaded: $driverFilePath"
WriteLog "Creating extraction folder: $extractFolder"
New-Item -Path $extractFolder -ItemType Directory -Force -ErrorAction Stop | Out-Null
$arguments = "/s /e /f `"$extractFolder`""
WriteLog "Extracting driver $driverFilePath with args: $arguments"
#DEBUG
# wrap $driverFilePath in quotes to handle spaces
# $driverFilePath = "`"$driverFilePath`""
WriteLog "Running HP Driver Extraction Command: $driverFilePath $arguments"
Invoke-Process -FilePath $driverFilePath -ArgumentList $arguments -ErrorAction Stop | Out-Null
# Start-Process -FilePath $driverFilePath -ArgumentList $arguments -Wait -NoNewWindow -ErrorAction Stop | Out-Null
WriteLog "Driver extracted to: $extractFolder"
Remove-Item -Path $driverFilePath -Force -ErrorAction SilentlyContinue
WriteLog "Deleted driver installer: $driverFilePath"
}
Remove-Item -Path $driverCabFile, $driverXmlFile -Force -ErrorAction SilentlyContinue
WriteLog "Cleaned up driver cab and xml files for $modelName"
$finalStatus = "Completed"
if ($CompressToWim) {
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status "Compressing..." }
$wimFilePath = Join-Path -Path $hpDriversBaseFolder -ChildPath "$($identifier).wim"
WriteLog "Compressing '$modelSpecificFolder' to '$wimFilePath'..."
try {
Compress-DriverFolderToWim -SourceFolderPath $modelSpecificFolder -DestinationWimPath $wimFilePath -WimName $identifier -WimDescription "Drivers for $identifier" -ErrorAction Stop
WriteLog "Compression successful for '$identifier'."
$finalStatus = "Completed & Compressed"
$driverRelativePath = Join-Path -Path $make -ChildPath "$($identifier).wim" # Update relative path to the WIM
}
catch {
WriteLog "Error during compression for '$identifier': $($_.Exception.Message)"
$finalStatus = "Completed (Compression Failed)"
}
}
$successState = $true
}
catch {
$errorMessage = "Error saving HP drivers for $($modelName): $($_.Exception.Message)"
WriteLog $errorMessage
$finalStatus = "Error: $($_.Exception.Message.Split([Environment]::NewLine)[0])"
$successState = $false
$driverRelativePath = $null # Ensure path is null on error
if (Test-Path -Path $modelSpecificFolder -PathType Container) {
WriteLog "Attempting to remove partially created folder $modelSpecificFolder due to error."
Remove-Item -Path $modelSpecificFolder -Recurse -Force -ErrorAction SilentlyContinue
}
}
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status $finalStatus }
return [PSCustomObject]@{ Identifier = $identifier; Status = $finalStatus; Success = $successState; DriverPath = $driverRelativePath }
}
Export-ModuleMember -Function *