From e67590d0a15ec3dd9ea3bc58f5fb9aff1b499d8c Mon Sep 17 00:00:00 2001 From: rbalsleyMSFT <53497092+rbalsleyMSFT@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:02:59 -0800 Subject: [PATCH] Add HTTP fallback for BITS transfer network authentication errors Enhances the BITS transfer retry logic to detect when the current session lacks network authentication and automatically falls back to Invoke-WebRequest for completion. Detects the "not logged on to the network" error condition by checking both the HResult code (0x800704DD) and error message patterns, then triggers an HTTP-based fallback mechanism to retry the download with remaining attempts. This ensures downloads can succeed even when BITS fails due to authentication issues, improving reliability of the transfer function in environments with network authentication requirements. --- .../FFU.Common/FFU.Common.Core.psm1 | 72 ++++++++++++++++--- 1 file changed, 63 insertions(+), 9 deletions(-) diff --git a/FFUDevelopment/FFU.Common/FFU.Common.Core.psm1 b/FFUDevelopment/FFU.Common/FFU.Common.Core.psm1 index abb499e..30a3dd2 100644 --- a/FFUDevelopment/FFU.Common/FFU.Common.Core.psm1 +++ b/FFUDevelopment/FFU.Common/FFU.Common.Core.psm1 @@ -148,26 +148,45 @@ function Start-BitsTransferWithRetry { $attempt = 0 $lastError = $null + $notLoggedOnHResult = [int]0x800704dd + $fallbackTriggered = $false - while ($attempt -lt $Retries) { + while ($attempt -lt $Retries -and -not $fallbackTriggered) { $OriginalVerbosePreference = $VerbosePreference $OriginalProgressPreference = $ProgressPreference try { - $VerbosePreference = 'SilentlyContinue' - $ProgressPreference = 'SilentlyContinue' + $VerbosePreference = 'SilentlyContinue' + $ProgressPreference = 'SilentlyContinue' Start-BitsTransfer -Source $Source -Destination $Destination -Priority Normal -ErrorAction Stop $ProgressPreference = $OriginalProgressPreference $VerbosePreference = $OriginalVerbosePreference - WriteLog "Successfully transferred $Source to $Destination." - return + WriteLog "Successfully transferred $Source to $Destination." + return } catch { $lastError = $_ $attempt++ - WriteLog "Attempt $attempt of $Retries failed to download $Source. Error: $($lastError.Exception.Message)." - Start-Sleep -Seconds (1 * $attempt) + $errorMessage = $lastError.Exception.Message + WriteLog "Attempt $attempt of $Retries failed to download $Source. Error: $errorMessage." + $hResult = $null + if ($null -ne $lastError.Exception) { + $hResult = $lastError.Exception.HResult + } + $needsHttpFallback = $false + if ($hResult -eq $notLoggedOnHResult) { + $needsHttpFallback = $true + } + elseif ($errorMessage -match '0x800704DD' -or $errorMessage -match 'not.*logged on to the network') { + $needsHttpFallback = $true + } + if ($needsHttpFallback) { + WriteLog "BITS cannot download $Source because the current session is not logged on to the network. Falling back to Invoke-WebRequest." + $fallbackTriggered = $true + break + } + Start-Sleep -Seconds (1 * $attempt) } finally { if (Get-Variable -Name 'OriginalProgressPreference' -ErrorAction SilentlyContinue) { @@ -179,8 +198,43 @@ function Start-BitsTransferWithRetry { } } - WriteLog "Failed to download $Source after $Retries attempts. Last Error: $($lastError.Exception.Message)" - throw $lastError + if ($fallbackTriggered) { + $remainingAttempts = $Retries - $attempt + if ($remainingAttempts -lt 1) { + $remainingAttempts = 1 + } + $httpAttempt = 0 + while ($httpAttempt -lt $remainingAttempts) { + $httpAttempt++ + $OriginalVerbosePreference = $VerbosePreference + $OriginalProgressPreference = $ProgressPreference + try { + $VerbosePreference = 'SilentlyContinue' + $ProgressPreference = 'SilentlyContinue' + Invoke-WebRequest -Uri $Source -OutFile $Destination -ErrorAction Stop + $ProgressPreference = $OriginalProgressPreference + $VerbosePreference = $OriginalVerbosePreference + WriteLog "Successfully transferred $Source to $Destination via HTTP fallback." + return + } + catch { + $lastError = $_ + WriteLog "HTTP fallback attempt $httpAttempt of $remainingAttempts failed to download $Source. Error: $($lastError.Exception.Message)." + Start-Sleep -Seconds (1 * $httpAttempt) + } + finally { + if (Get-Variable -Name 'OriginalProgressPreference' -ErrorAction SilentlyContinue) { + $ProgressPreference = $OriginalProgressPreference + } + if (Get-Variable -Name 'OriginalVerbosePreference' -ErrorAction SilentlyContinue) { + $VerbosePreference = $OriginalVerbosePreference + } + } + } + } + + WriteLog "Failed to download $Source after $Retries attempts. Last Error: $($lastError.Exception.Message)" + throw $lastError } function Set-Progress {