From 4a10e27ddf30e95086edf8cc7d753e19d184db83 Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Thu, 30 Oct 2025 18:26:05 -0700 Subject: [PATCH] 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. --- FFUDevelopment/BuildFFUVM.ps1 | 123 +++++++++--------- .../FFUUI.Core/FFUUI.Core.Drivers.Dell.psm1 | 51 +++++--- .../FFUUI.Core/FFUUI.Core.Drivers.Lenovo.psm1 | 38 +++--- .../FFUUI.Core/FFUUI.Core.Drivers.psm1 | 38 +++++- 4 files changed, 145 insertions(+), 105 deletions(-) diff --git a/FFUDevelopment/BuildFFUVM.ps1 b/FFUDevelopment/BuildFFUVM.ps1 index 8830996..59e8654 100644 --- a/FFUDevelopment/BuildFFUVM.ps1 +++ b/FFUDevelopment/BuildFFUVM.ps1 @@ -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,52 +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 ($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." - } + if ($failedDownloads.Count -gt 0) { + $firstFailure = $failedDownloads[0] + $errorMessage = "Driver download failed for model '$($firstFailure.Model)': $($firstFailure.Status)" + WriteLog $errorMessage + throw $errorMessage } - 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." @@ -5052,6 +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." } } # Existing single-model driver download logic diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Dell.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Dell.psm1 index 8e817ed..59290b9 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Dell.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Dell.psm1 @@ -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 } } } diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Lenovo.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Lenovo.psm1 index e0fbae4..db2950a 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Lenovo.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.Lenovo.psm1 @@ -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 { diff --git a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 index f931407..b904f32 100644 --- a/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 +++ b/FFUDevelopment/FFUUI.Core/FFUUI.Core.Drivers.psm1 @@ -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") } }