Enhances driver download error handling and logging across Dell and Lenovo driver tasks.

- Implements detailed failure tracking for driver downloads, capturing model names and statuses for failed attempts.
- Updates logging to provide clearer messages for both successful and failed downloads, improving traceability.
- Modifies the user interface to display comprehensive error messages when downloads fail, including a summary of failed models.
- Ensures that all exceptions during download and extraction processes are logged and thrown, preventing silent failures.
This commit is contained in:
rbalsleyMSFT
2025-10-30 18:26:05 -07:00
parent b4305a1edb
commit 4a10e27ddf
4 changed files with 145 additions and 105 deletions
+60 -65
View File
@@ -4948,46 +4948,78 @@ if ($driversJsonPath -and (Test-Path $driversJsonPath) -and ($InstallDrivers -or
-ListViewControl $null `
-MainThreadLogPath $LogFile
# After processing, update the driver mapping file
# After processing, update the driver mapping file and detect failures
$successfullyDownloaded = [System.Collections.Generic.List[PSCustomObject]]::new()
$failedDownloads = [System.Collections.Generic.List[PSCustomObject]]::new()
if ($null -ne $parallelResults) {
# Create a lookup table from the original items to get the 'Make'
$makeLookup = @{}
$driversToProcess | ForEach-Object { $makeLookup[$_.Model] = $_.Make }
# Filter for objects that could be results, avoiding stray log strings
foreach ($result in ($parallelResults | Where-Object { $_ -is [hashtable] })) {
foreach ($result in $parallelResults) {
if ($null -eq $result) { continue }
# The result from Invoke-ParallelProcessing is a hashtable.
# Access properties using their keys.
$modelName = $result['Identifier']
$resultCode = $result['ResultCode']
$driverPath = $result['DriverPath']
$lookupModelName = $null
$resultStatus = $null
$resultDriverPath = $null
$resultSuccess = $false
if ([string]::IsNullOrWhiteSpace($modelName)) {
WriteLog "Could not determine model name from result object: $($result | ConvertTo-Json -Compress -Depth 3)"
continue
if ($result -is [hashtable]) {
$lookupModelName = $result['Identifier']
$resultStatus = $result['Status']
if ($result.ContainsKey('DriverPath')) { $resultDriverPath = $result['DriverPath'] }
if ($result.ContainsKey('Success')) {
$resultSuccess = [bool]$result['Success']
}
elseif ($result.ContainsKey('ResultCode')) {
$resultSuccess = ($result['ResultCode'] -eq 0)
}
}
elseif ($result -is [pscustomobject]) {
if ($result.PSObject.Properties.Name -contains 'Identifier' -and -not [string]::IsNullOrWhiteSpace($result.Identifier)) {
$lookupModelName = $result.Identifier
}
elseif ($result.PSObject.Properties.Name -contains 'Model' -and -not [string]::IsNullOrWhiteSpace($result.Model)) {
$lookupModelName = $result.Model
}
if ($result.PSObject.Properties.Name -contains 'Status') { $resultStatus = $result.Status }
if ($result.PSObject.Properties.Name -contains 'DriverPath') { $resultDriverPath = $result.DriverPath }
if ($result.PSObject.Properties.Name -contains 'Success') {
$resultSuccess = [bool]$result.Success
}
elseif ($result.PSObject.Properties.Name -contains 'ResultCode') {
$resultSuccess = ($result.ResultCode -eq 0)
}
}
if ($resultCode -eq 0 -and -not [string]::IsNullOrWhiteSpace($driverPath)) {
# The task was successful and returned a driver path.
$makeJson = $makeLookup[$modelName]
if ($resultSuccess -and -not [string]::IsNullOrWhiteSpace($resultDriverPath)) {
$modelKey = if (-not [string]::IsNullOrWhiteSpace($lookupModelName)) { $lookupModelName } else { 'Unknown model' }
$makeJson = $null
if (-not [string]::IsNullOrWhiteSpace($lookupModelName) -and $makeLookup.ContainsKey($lookupModelName)) {
$makeJson = $makeLookup[$lookupModelName]
}
if ($makeJson) {
$successfullyDownloaded.Add([PSCustomObject]@{
Make = $makeJson
Model = $modelName
DriverPath = $driverPath
Model = $modelKey
DriverPath = $resultDriverPath
})
}
else {
WriteLog "Warning: Could not find 'Make' for successful download of model '$modelName'. Skipping from DriverMapping.json."
WriteLog "Warning: Could not find 'Make' for successful download of model '$modelKey'. Skipping from DriverMapping.json."
}
}
else {
$logMessage = "Driver download failed or did not return a path for model '$modelName'. Status: $($result['Status'])"
WriteLog $logMessage
Write-Warning $logMessage
$failureModel = if (-not [string]::IsNullOrWhiteSpace($lookupModelName)) { $lookupModelName } else { 'Unknown model' }
$failureStatus = if (-not [string]::IsNullOrWhiteSpace($resultStatus)) { $resultStatus } else { 'Driver download failed without a status message. Check the log for details.' }
$failedDownloads.Add([PSCustomObject]@{
Model = $failureModel
Status = $failureStatus
})
WriteLog "Driver download failed for '$failureModel'. Status: $failureStatus"
}
}
}
@@ -4995,7 +5027,13 @@ if ($driversJsonPath -and (Test-Path $driversJsonPath) -and ($InstallDrivers -or
WriteLog "Invoke-ParallelProcessing returned null or no results."
}
# Update the driver mapping JSON if there are any successful downloads
if ($failedDownloads.Count -gt 0) {
$firstFailure = $failedDownloads[0]
$errorMessage = "Driver download failed for model '$($firstFailure.Model)': $($firstFailure.Status)"
WriteLog $errorMessage
throw $errorMessage
}
if ($successfullyDownloaded.Count -gt 0) {
try {
WriteLog "Updating DriverMapping.json with $($successfullyDownloaded.Count) successfully downloaded drivers."
@@ -5007,51 +5045,8 @@ if ($driversJsonPath -and (Test-Path $driversJsonPath) -and ($InstallDrivers -or
Write-Warning "The driver download process completed, but failed to update the DriverMapping.json file. Please check the log for details."
}
}
WriteLog "Finished processing drivers from $driversJsonPath."
# After processing, update the driver mapping file
$successfullyDownloaded = [System.Collections.Generic.List[PSCustomObject]]::new()
if ($null -ne $parallelResults) {
# Create a lookup table from the original items to get the 'Make'
$makeLookup = @{}
$driversToProcess | ForEach-Object { $makeLookup[$_.Model] = $_.Make }
foreach ($result in $parallelResults) {
if ($null -ne $result) {
# Collect successful results for driver mapping
if ($result.PSObject.Properties['Success'] -and $result.Success -and $result.PSObject.Properties['DriverPath'] -and -not [string]::IsNullOrWhiteSpace($result.DriverPath)) {
$modelName = if ($result.PSObject.Properties.Name -contains 'Identifier') { $result.Identifier } else { $result.Model }
# Find the 'Make' from the original list
$makeJson = $makeLookup[$modelName]
if ($makeJson) {
$successfullyDownloaded.Add([PSCustomObject]@{
Make = $makeJson
Model = $modelName
DriverPath = $result.DriverPath
})
}
else {
WriteLog "Warning: Could not find 'Make' for successful download of model '$modelName'. Skipping from DriverMapping.json."
}
}
}
}
}
# Update the driver mapping JSON if there are any successful downloads
if ($successfullyDownloaded.Count -gt 0) {
try {
WriteLog "Updating DriverMapping.json with $($successfullyDownloaded.Count) successfully downloaded drivers."
Update-DriverMappingJson -DownloadedDrivers $successfullyDownloaded -DriversFolder $DriversFolder
}
catch {
WriteLog "Warning: Failed to update DriverMapping.json: $($_.Exception.Message)"
# This is not a fatal error for the build process itself, so just show a warning.
Write-Warning "The driver download process completed, but failed to update the DriverMapping.json file. Please check the log for details."
}
}
}
}
# Existing single-model driver download logic
@@ -23,14 +23,14 @@ function Get-DellDriversModelList {
$final = [System.Collections.Generic.List[pscustomobject]]::new()
foreach ($m in $dellModels) {
$final.Add([pscustomobject]@{
Make = $Make
Model = $m.ModelDisplay
Brand = $m.Brand
ModelNumber = $m.ModelNumber
SystemId = $m.SystemId
CabRelativePath = $m.CabRelativePath
CabUrl = $m.CabUrl
})
Make = $Make
Model = $m.ModelDisplay
Brand = $m.Brand
ModelNumber = $m.ModelNumber
SystemId = $m.SystemId
CabRelativePath = $m.CabRelativePath
CabUrl = $m.CabUrl
})
}
return $final
}
@@ -66,7 +66,7 @@ function Get-DellDriversModelList {
$settings = New-Object System.Xml.XmlReaderSettings
$settings.IgnoreWhitespace = $true
$settings.IgnoreComments = $true
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML,$settings)
$reader = [System.Xml.XmlReader]::Create($dellCatalogXML, $settings)
$inDriver = $false
$inModel = $false
$depthModel = -1
@@ -144,7 +144,7 @@ function Save-DellDriversTask {
$target = (Resolve-Path $Path -ErrorAction SilentlyContinue)?.ProviderPath
if ($null -eq $target) { return }
if ($target -eq $dellRoot) { return }
if (-not ($target.StartsWith($dellRoot,[System.StringComparison]::OrdinalIgnoreCase))) { return }
if (-not ($target.StartsWith($dellRoot, [System.StringComparison]::OrdinalIgnoreCase))) { return }
Remove-Item -Path $target -Recurse -Force -ErrorAction SilentlyContinue
}
@@ -246,18 +246,18 @@ function Save-DellDriversTask {
$driverPath = $component.path
$downloadUrl = $baseLocation + $driverPath
$fileName = [IO.Path]::GetFileName($driverPath)
$name = $component.Name.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_' -replace '[\,]','-'
$category = $component.Category.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]','_'
$name = $component.Name.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]', '_' -replace '[\,]', '-'
$category = $component.Category.Display.'#cdata-section' -replace '[\\\/\:\*\?\"\<\>\| ]', '_'
$version = [version]$component.vendorVersion
$namePrefix = ($name -split '-')[0]
if (-not $latestDrivers[$category]) { $latestDrivers[$category] = @{} }
if (-not $latestDrivers[$category][$namePrefix] -or $latestDrivers[$category][$namePrefix].Version -lt $version) {
$latestDrivers[$category][$namePrefix] = [pscustomobject]@{
Name = $name
DownloadUrl = $downloadUrl
Name = $name
DownloadUrl = $downloadUrl
DriverFileName = $fileName
Version = $version
Category = $category
Version = $version
Category = $category
}
}
}
@@ -280,7 +280,7 @@ function Save-DellDriversTask {
$status = "$idx/$total Downloading $driverName"
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $status }
$categorySafe = ($pkg.Category -replace '[\\\/\:\*\?\"\<\>\| ]','_')
$categorySafe = ($pkg.Category -replace '[\\\/\:\*\?\"\<\>\| ]', '_')
$downloadFolder = Join-Path $modelPath $categorySafe
if (-not (Test-Path $downloadFolder)) { New-Item -Path $downloadFolder -ItemType Directory -Force | Out-Null }
$driverFilePath = Join-Path $downloadFolder $pkg.DriverFileName
@@ -296,7 +296,11 @@ function Save-DellDriversTask {
if (-not (Test-Path $driverFilePath)) {
WriteLog "$status URL: $($pkg.DownloadUrl)"
try { Start-BitsTransferWithRetry -Source $pkg.DownloadUrl -Destination $driverFilePath }
catch { WriteLog "Download failed: $($pkg.DownloadUrl) $($_.Exception.Message)"; continue }
catch {
$failureMessage = "Failed to download driver '$driverName' from $($pkg.DownloadUrl): $($_.Exception.Message)"
WriteLog $failureMessage
throw (New-Object System.Exception($failureMessage, $_.Exception))
}
}
$status = "$idx/$total Extracting $driverName"
@@ -326,6 +330,11 @@ function Save-DellDriversTask {
if ($ok) {
Remove-Item $driverFilePath -Force -ErrorAction SilentlyContinue
}
else {
$failureMessage = "Failed to extract driver '$driverName'."
WriteLog $failureMessage
throw (New-Object System.Exception($failureMessage))
}
}
if ($CompressToWim) {
@@ -349,10 +358,10 @@ function Save-DellDriversTask {
return [pscustomobject]@{ Model = $modelDisplay; Status = $statusFinal; Success = $true; DriverPath = $driverRelativePath }
}
catch {
$err = "Error: $($_.Exception.Message.Split('.')[0])"
$errorStatus = "Error: $($_.Exception.Message)"
WriteLog "Save-DellDriversTask error for $($modelDisplay): $($_.Exception.ToString())"
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $err }
return [pscustomobject]@{ Model = $modelDisplay; Status = $err; Success = $false; DriverPath = $null }
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $modelDisplay -Status $errorStatus }
return [pscustomobject]@{ Model = $modelDisplay; Status = $errorStatus; Success = $false; DriverPath = $null }
}
}
@@ -216,8 +216,10 @@ function Save-LenovoDriversTask {
Start-BitsTransferWithRetry -Source $packageUrl -Destination $packageXMLPath
}
catch {
WriteLog "($processedPackages/$totalPackages) Failed to download package XML '$packageUrl'. Skipping. Error: $($_.Exception.Message)"
continue # Skip this package
$failureMessage = "Failed to download Lenovo package XML '$packageUrl': $($_.Exception.Message)"
WriteLog "($processedPackages/$totalPackages) $failureMessage"
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue
throw (New-Object System.Exception($failureMessage, $_.Exception))
}
# Load and parse the package XML
@@ -286,9 +288,10 @@ function Save-LenovoDriversTask {
WriteLog "($processedPackages/$totalPackages) Driver downloaded: $driverFileName"
}
catch {
WriteLog "($processedPackages/$totalPackages) Failed to download driver '$driverUrl'. Skipping. Error: $($_.Exception.Message)"
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue # Clean up package XML
continue # Skip this driver
$failureMessage = "Failed to download driver '$packageTitle' from $($driverUrl): $($_.Exception.Message)"
WriteLog "($processedPackages/$totalPackages) $failureMessage"
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue
throw (New-Object System.Exception($failureMessage, $_.Exception))
}
# --- Extraction Logic ---
@@ -325,14 +328,13 @@ function Save-LenovoDriversTask {
$extractionSucceeded = $true
}
catch {
WriteLog "($processedPackages/$totalPackages) Failed to extract driver '$driverFilePath' to temporary path. Skipping. Error: $($_.Exception.Message)"
# Don't delete the downloaded exe yet if extraction fails
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue # Clean up package XML
# Clean up temp folder if extraction failed
$failureMessage = "Failed to extract driver package '$packageTitle': $($_.Exception.Message)"
WriteLog "($processedPackages/$totalPackages) $failureMessage"
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue
if ($tempExtractBase -and (Test-Path -Path $tempExtractBase)) {
Remove-Item -Path $tempExtractBase -Recurse -Force -ErrorAction SilentlyContinue
}
continue # Skip further processing for this driver
throw (New-Object System.Exception($failureMessage, $_.Exception))
}
# --- Post-Extraction Handling (Move from Temp to Final Destination) ---
@@ -375,10 +377,9 @@ function Save-LenovoDriversTask {
Move-Item -Path $item.FullName -Destination $finalDestinationPath -Force -ErrorAction Stop
}
catch {
WriteLog "($processedPackages/$totalPackages) Failed to move item '$($item.FullName)' to '$finalDestinationPath'. Error: $($_.Exception.Message)"
# Decide if this should stop the whole process or just skip this item
# For now, we'll log and continue, but mark overall success as false
$extractionSucceeded = $false
$failureMessage = "Failed to move extracted item '$($item.FullName)' to '$finalDestinationPath': $($_.Exception.Message)"
WriteLog "($processedPackages/$totalPackages) $failureMessage"
throw (New-Object System.Exception($failureMessage, $_.Exception))
}
} # End foreach ($item in $extractedItems)
@@ -415,6 +416,9 @@ function Save-LenovoDriversTask {
# Always delete the package XML
WriteLog "($processedPackages/$totalPackages) Deleting package XML file: $packageXMLPath"
Remove-Item -Path $packageXMLPath -Force -ErrorAction SilentlyContinue
if (-not $extractionSucceeded) {
throw (New-Object System.Exception("Failed to extract driver '$packageTitle'. See log for details."))
}
} # End foreach package
@@ -451,12 +455,10 @@ function Save-LenovoDriversTask {
}
catch {
$status = "Error: $($_.Exception.Message.Split('.')[0])" # Shorten error message
WriteLog "Error saving Lenovo drivers for '$identifier': $($_.Exception.ToString())" # Log full exception string
$status = "Error: $($_.Exception.Message)"
WriteLog "Error saving Lenovo drivers for '$identifier': $($_.Exception.ToString())"
$success = $false
# Enqueue the error status before returning
if ($null -ne $ProgressQueue) { Invoke-ProgressUpdate -ProgressQueue $ProgressQueue -Identifier $identifier -Status $status }
# Ensure return object is created even on error
return [PSCustomObject]@{ Identifier = $identifier; Status = $status; Success = $success; DriverPath = $null }
}
finally {
@@ -716,6 +716,7 @@ function Invoke-DownloadSelectedDrivers {
$overallSuccess = $true
$successfullyDownloaded = [System.Collections.Generic.List[PSCustomObject]]::new()
$failedDownloads = [System.Collections.Generic.List[PSCustomObject]]::new()
# Check the results from the parallel processing tasks
if ($null -ne $parallelResults) {
@@ -737,11 +738,21 @@ function Invoke-DownloadSelectedDrivers {
if ([string]::IsNullOrWhiteSpace($modelName)) {
WriteLog "Could not determine model name from result object: $($result | ConvertTo-Json -Compress -Depth 3)"
$overallSuccess = $false
$failedDownloads.Add([PSCustomObject]@{
Model = 'Unknown model'
Status = 'Driver task returned without a model identifier.'
})
continue
}
if ($resultCode -ne 0) {
$overallSuccess = $false
$failureStatus = $result['Status']
if ([string]::IsNullOrWhiteSpace($failureStatus)) { $failureStatus = 'Driver download failed. Check the log for details.' }
$failedDownloads.Add([PSCustomObject]@{
Model = $modelName
Status = $failureStatus
})
WriteLog "Error detected for model $modelName."
}
elseif (-not [string]::IsNullOrWhiteSpace($driverPath)) {
@@ -758,6 +769,16 @@ function Invoke-DownloadSelectedDrivers {
WriteLog "Warning: Could not find 'Make' for successful download of model '$modelName'. Skipping from DriverMapping.json."
}
}
else {
$overallSuccess = $false
$fallbackStatus = $result['Status']
if ([string]::IsNullOrWhiteSpace($fallbackStatus)) { $fallbackStatus = 'Driver download did not return a driver path.' }
$failedDownloads.Add([PSCustomObject]@{
Model = $modelName
Status = $fallbackStatus
})
WriteLog "Driver download did not provide a path for model $modelName."
}
}
}
@@ -855,8 +876,21 @@ function Invoke-DownloadSelectedDrivers {
[System.Windows.MessageBox]::Show("All selected driver downloads processed. Check status column for details.", "Download Process Finished", "OK", "Information")
}
else {
$State.Controls.txtStatus.Text = "Driver downloads processed with some errors. Check status column and log."
[System.Windows.MessageBox]::Show("Driver downloads processed, but some errors occurred. Please check the status column for each driver and the log file for details.", "Download Process Finished with Errors", "OK", "Warning")
$State.Controls.txtStatus.Text = "Driver download failed. Resolve the errors and try again."
$messageLines = [System.Collections.Generic.List[string]]::new()
if ($failedDownloads.Count -gt 0) {
$messageLines.Add("Driver download failed for:")
foreach ($item in ($failedDownloads | Select-Object -First 5)) {
$messageLines.Add("- $($item.Model): $($item.Status)")
}
if ($failedDownloads.Count -gt 5) {
$messageLines.Add("...see the log for additional failures.")
}
}
else {
$messageLines.Add("One or more driver downloads failed. Check the log for details.")
}
[System.Windows.MessageBox]::Show(($messageLines -join [System.Environment]::NewLine), "Driver Download Failed", "OK", "Error")
}
}