Private/Get-WUWindows11ISO.ps1
|
function Get-WUWindows11ISO { <# .SYNOPSIS Downloads the latest Windows 11 ISO from Microsoft with PowerShell 5.1 compatibility .DESCRIPTION This function downloads the latest Windows 11 ISO file from Microsoft using multiple methods including Fido, UUP dump, and the official Media Creation Tool. It supports multiple languages and editions, with automatic fallback mechanisms and enhanced reliability features including retry logic and PowerShell 5.1 compatibility. .PARAMETER OutputPath Directory where the ISO file should be saved. Defaults to current directory. .PARAMETER Language Language code for the Windows 11 ISO (e.g., 'en-us', 'de-de', 'fr-fr'). Defaults to system language or 'en-us' if system language not supported. .PARAMETER Edition Windows 11 edition to download. Defaults to 'Professional'. Valid options: Home, Professional, Education, Enterprise .PARAMETER Architecture System architecture. Defaults to 'x64'. Valid options: x64, arm64 .PARAMETER Method Download method to use. If not specified, will try methods in order of preference. Valid options: Fido, UUP, MediaCreationTool, Auto .PARAMETER SkipVerification Skip file verification after download .PARAMETER Force Overwrite existing ISO files .PARAMETER MaxRetries Maximum number of retry attempts for failed downloads. Defaults to 3. .PARAMETER RetryDelay Delay in seconds between retry attempts. Defaults to 10. .EXAMPLE Get-WUWindows11ISO -OutputPath "C:\ISOs" Downloads Windows 11 Professional x64 in system language to C:\ISOs .EXAMPLE Get-WUWindows11ISO -Language "de-de" -Edition "Enterprise" -Method "Fido" Downloads Windows 11 Enterprise x64 in German using Fido method .NOTES Requires internet connection and sufficient disk space (5+ GB) Enhanced for PowerShell 5.1 compatibility with improved retry logic Some methods may require PowerShell to be run as Administrator #> [CmdletBinding()] param( [Parameter()] [string]$OutputPath = (Get-Location).Path, [Parameter()] [string]$Language, [Parameter()] [ValidateSet('Home', 'Professional', 'Education', 'Enterprise')] [string]$Edition = 'Professional', [Parameter()] [ValidateSet('x64', 'arm64')] [string]$Architecture = 'x64', [Parameter()] [ValidateSet('Fido', 'UUP', 'MediaCreationTool', 'Auto')] [string]$Method = 'Auto', [Parameter()] [switch]$SkipVerification, [Parameter()] [switch]$Force, [Parameter()] [int]$MaxRetries = 3, [Parameter()] [int]$RetryDelay = 10 ) # PowerShell 5.1 compatible TLS setup try { Write-WULog "Configuring TLS for PowerShell 5.1 compatibility" -Level Info [System.Net.ServicePointManager]::SecurityProtocol = 'Tls12' Write-WULog "TLS 1.2 configured successfully" -Level Info } catch { Write-WULog "Failed to configure TLS 1.2: $($_.Exception.Message)" -Level Warning try { # Fallback method for older systems [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 } catch { Write-WULog "TLS configuration fallback also failed. Downloads may fail." -Level Error } } Write-WULog "Starting Windows 11 ISO download process (Enhanced PS 5.1 compatible version)" -Level Info # Validate and create output directory if (-not (Test-Path $OutputPath)) { try { New-Item -Path $OutputPath -ItemType Directory -Force | Out-Null Write-WULog "Created output directory: $OutputPath" -Level Info } catch { Write-Error "Failed to create output directory: $($_.Exception.Message)" return $null } } # Auto-detect system language if not specified if (-not $Language) { $Language = Get-WUSystemLanguageEnhanced Write-WULog "Auto-detected system language: $Language" -Level Info } # Focus on Fido-only approach with enhanced network resilience $downloadMethods = if ($Method -eq 'Auto' -or $Method -eq 'Fido') { @('Fido') } else { Write-WULog "Only Fido method is currently supported for reliable downloads" -Level Warning @('Fido') } $isoFile = $null $lastError = $null $globalRetryCount = 0 $maxGlobalRetries = $MaxRetries # Enhanced retry logic specifically for network interruptions while ($globalRetryCount -lt $maxGlobalRetries -and (-not $isoFile -or -not (Test-Path $isoFile))) { $globalRetryCount++ if ($globalRetryCount -gt 1) { Write-WULog "Global retry attempt $globalRetryCount of $maxGlobalRetries" -Level Info # Longer delays for Microsoft server restrictions $delayTime = $RetryDelay * $globalRetryCount * 2 # Exponential backoff Write-WULog "Waiting $delayTime seconds to avoid Microsoft server restrictions..." -Level Info Start-Sleep -Seconds $delayTime } foreach ($currentMethod in $downloadMethods) { Write-WULog "Attempting download using method: $currentMethod (Global attempt: $globalRetryCount)" -Level Info try { switch ($currentMethod) { 'Fido' { $isoFile = Invoke-FidoDownloadEnhanced -OutputPath $OutputPath -Language $Language -Edition $Edition -Architecture $Architecture -Force:$Force -RetryOnNetworkError } } if ($isoFile -and (Test-Path $isoFile)) { Write-WULog "Successfully downloaded Windows 11 ISO using $currentMethod method: $isoFile" -Level Info break } } catch { $lastError = $_.Exception $errorMessage = $_.Exception.Message # Enhanced error analysis for network issues if ($errorMessage -match "RPC server.*unavailable|0x800706BA") { Write-WULog "Network RPC error detected during download - this is a common issue during large file transfers" -Level Warning } elseif ($errorMessage -match "715-123130|banned.*service|location.*hiding") { Write-WULog "Microsoft server restrictions detected - IP may be temporarily blocked" -Level Warning } elseif ($errorMessage -match "403|forbidden|rate.*limit|too.*many.*requests") { Write-WULog "Rate limiting detected from Microsoft servers" -Level Warning } else { Write-WULog "Download failed with error: $errorMessage" -Level Warning } # Don't break out early - let global retry handle this } } if ($isoFile -and (Test-Path $isoFile)) { break } if ($globalRetryCount -lt $maxGlobalRetries) { Write-WULog "Download attempt $globalRetryCount failed. Will retry after extended delay..." -Level Warning } } if (-not $isoFile -or -not (Test-Path $isoFile)) { $errorMessage = "All download methods failed after $globalRetryCount global attempts." if ($lastError) { $errorMessage += " Last error: $($lastError.Message)" } Write-Error $errorMessage return $null } # Verify downloaded ISO file if (-not $SkipVerification) { Write-WULog "Verifying downloaded ISO file..." -Level Info if (-not (Test-ISOFileEnhanced -Path $isoFile)) { Write-Error "Downloaded ISO file failed verification" return $null } Write-WULog "ISO file verification passed" -Level Info } # Return the downloaded ISO file information $isoInfo = Get-Item $isoFile $result = [PSCustomObject]@{ Path = $isoInfo.FullName Name = $isoInfo.Name Size = [math]::Round($isoInfo.Length / 1GB, 2) Language = $Language Edition = $Edition Architecture = $Architecture Method = $currentMethod DownloadDate = Get-Date RetryCount = $globalRetryCount } Write-WULog "Windows 11 ISO download completed successfully after $globalRetryCount global attempts" -Level Info return $result } #region Enhanced Helper Functions function Get-WUSystemLanguageEnhanced { <# .SYNOPSIS Gets the system language code suitable for Windows ISO downloads (Enhanced) #> try { # Get system locale with multiple methods for reliability $systemLocale = $null try { $systemLocale = Get-Culture } catch { try { $systemLocale = [System.Globalization.CultureInfo]::CurrentCulture } catch { Write-WULog "Could not determine system culture, using en-us" -Level Warning return 'en-us' } } $languageTag = $systemLocale.Name.ToLower() # Enhanced language mapping with more comprehensive coverage $languageMap = @{ 'en-us' = 'en-us'; 'en-gb' = 'en-gb'; 'en-ca' = 'en-us'; 'en-au' = 'en-us' 'de-de' = 'de-de'; 'de-at' = 'de-de'; 'de-ch' = 'de-de' 'fr-fr' = 'fr-fr'; 'fr-ca' = 'fr-fr'; 'fr-be' = 'fr-fr'; 'fr-ch' = 'fr-fr' 'es-es' = 'es-es'; 'es-mx' = 'es-es'; 'es-ar' = 'es-es'; 'es-co' = 'es-es' 'it-it' = 'it-it'; 'it-ch' = 'it-it' 'pt-br' = 'pt-br'; 'pt-pt' = 'pt-pt' 'ja-jp' = 'ja-jp'; 'ko-kr' = 'ko-kr' 'zh-cn' = 'zh-cn'; 'zh-tw' = 'zh-tw'; 'zh-hk' = 'zh-tw'; 'zh-sg' = 'zh-cn' 'nl-nl' = 'nl-nl'; 'nl-be' = 'nl-nl' 'sv-se' = 'sv-se'; 'da-dk' = 'da-dk'; 'no-no' = 'nb-no'; 'nb-no' = 'nb-no' 'fi-fi' = 'fi-fi'; 'pl-pl' = 'pl-pl'; 'ru-ru' = 'ru-ru' 'cs-cz' = 'cs-cz'; 'hu-hu' = 'hu-hu'; 'tr-tr' = 'tr-tr' 'ar-sa' = 'ar-sa'; 'he-il' = 'he-il'; 'th-th' = 'th-th' } if ($languageMap.ContainsKey($languageTag)) { return $languageMap[$languageTag] } # Try just the language part if full locale not found $languageOnly = $languageTag.Split('-')[0] $fallbackMap = @{ 'en' = 'en-us'; 'de' = 'de-de'; 'fr' = 'fr-fr'; 'es' = 'es-es' 'it' = 'it-it'; 'pt' = 'pt-br'; 'ja' = 'ja-jp'; 'ko' = 'ko-kr' 'zh' = 'zh-cn'; 'nl' = 'nl-nl'; 'sv' = 'sv-se'; 'da' = 'da-dk' 'no' = 'nb-no'; 'fi' = 'fi-fi'; 'pl' = 'pl-pl'; 'ru' = 'ru-ru' 'cs' = 'cs-cz'; 'hu' = 'hu-hu'; 'tr' = 'tr-tr' 'ar' = 'ar-sa'; 'he' = 'he-il'; 'th' = 'th-th' } if ($fallbackMap.ContainsKey($languageOnly)) { return $fallbackMap[$languageOnly] } Write-WULog "System language '$languageTag' not supported, defaulting to 'en-us'" -Level Warning return 'en-us' } catch { Write-WULog "Failed to detect system language: $($_.Exception.Message)" -Level Warning return 'en-us' } } function Invoke-FidoDownloadEnhanced { <# .SYNOPSIS Downloads Windows 11 ISO using the Fido PowerShell script with enhanced network error handling #> param( [string]$OutputPath, [string]$Language, [string]$Edition, [string]$Architecture, [switch]$Force, [switch]$RetryOnNetworkError ) Write-WULog "Starting enhanced Fido download method with network resilience" -Level Info # Download Fido script with retry logic $fidoPath = Join-Path $OutputPath "Fido.ps1" $fidoUrl = "https://github.com/pbatard/Fido/raw/master/Fido.ps1" $fidoDownloadSuccess = $false $fidoRetryCount = 0 $maxFidoRetries = 3 while ($fidoRetryCount -lt $maxFidoRetries -and -not $fidoDownloadSuccess) { $fidoRetryCount++ try { Write-WULog "Downloading Fido script (attempt $fidoRetryCount): $fidoUrl" -Level Info # Use more robust download compatible with PowerShell 5.1 $webClient = New-Object System.Net.WebClient try { # Set user agent for better compatibility $webClient.Headers.Add("User-Agent", "PowerShell/WindowsUpdateTools") $webClient.DownloadFile($fidoUrl, $fidoPath) } finally { $webClient.Dispose() } if (Test-Path $fidoPath) { $fidoSize = (Get-Item $fidoPath).Length if ($fidoSize -gt 1000) { # Basic size check $fidoDownloadSuccess = $true Write-WULog "Fido script downloaded successfully ($fidoSize bytes)" -Level Info } else { throw "Downloaded Fido script appears incomplete" } } else { throw "Fido script file not found after download" } } catch { Write-WULog "Fido download attempt $fidoRetryCount failed: $($_.Exception.Message)" -Level Warning if ($fidoRetryCount -lt $maxFidoRetries) { Start-Sleep -Seconds 5 } } } if (-not $fidoDownloadSuccess) { throw "Failed to download Fido script after $maxFidoRetries attempts" } try { # Convert locale codes to Fido language names based on official Fido repository Write-WULog "Converting language code '$Language' to Fido language name" -Level Info $fidoLanguageMap = @{ 'en-us' = 'English International' # Fido default for most English systems 'en-gb' = 'English International' 'en-au' = 'English International' 'en-ca' = 'English International' 'es-es' = 'Spanish' 'es-mx' = 'Spanish International' 'fr-fr' = 'French' 'fr-ca' = 'French Canadian' 'de-de' = 'German' 'it-it' = 'Italian' 'pt-pt' = 'Portuguese' 'pt-br' = 'Portuguese Brazil' 'ja-jp' = 'Japanese' 'ko-kr' = 'Korean' 'zh-cn' = 'Chinese Simplified' 'zh-tw' = 'Chinese Traditional' 'ar-sa' = 'Arabic' 'nl-nl' = 'Dutch' 'sv-se' = 'Swedish' 'da-dk' = 'Danish' 'fi-fi' = 'Finnish' 'no-no' = 'Norwegian' 'nb-no' = 'Norwegian' 'pl-pl' = 'Polish' 'ru-ru' = 'Russian' 'tr-tr' = 'Turkish' 'he-il' = 'Hebrew' 'th-th' = 'Thai' 'vi-vn' = 'Vietnamese' 'uk-ua' = 'Ukrainian' 'cs-cz' = 'Czech' 'hu-hu' = 'Hungarian' 'ro-ro' = 'Romanian' 'sk-sk' = 'Slovak' 'sl-si' = 'Slovenian' 'hr-hr' = 'Croatian' 'sr-rs' = 'Serbian' 'bg-bg' = 'Bulgarian' 'et-ee' = 'Estonian' 'lv-lv' = 'Latvian' 'lt-lt' = 'Lithuanian' 'el-gr' = 'Greek' 'ms-my' = 'Malay' 'id-id' = 'Indonesian' 'fa-ir' = 'Persian' } # Convert language code to Fido language name $fidoLanguage = if ($fidoLanguageMap.ContainsKey($Language.ToLower())) { $fidoLanguageMap[$Language.ToLower()] } else { Write-WULog "Language '$Language' not found in Fido mapping, defaulting to 'English International'" -Level Warning 'English International' } Write-WULog "Mapped language '$Language' to Fido language: '$fidoLanguage'" -Level Info # Prepare Fido parameters with enhanced mapping $editionMap = @{ 'Home' = 'Home' 'Professional' = 'Pro' 'Education' = 'Education' 'Enterprise' = 'Enterprise' } $fidoEdition = if ($editionMap.ContainsKey($Edition)) { $editionMap[$Edition] } else { 'Pro' } $fidoParams = @{ Win = '11' Rel = 'Latest' Ed = $fidoEdition Lang = $fidoLanguage Arch = $Architecture GetUrl = $false } Write-WULog "Executing Fido with enhanced parameters: Win=$($fidoParams.Win), Rel=$($fidoParams.Rel), Ed=$($fidoParams.Ed), Lang='$($fidoParams.Lang)', Arch=$($fidoParams.Arch)" -Level Info # Execute Fido script with better error handling $previousLocation = Get-Location Set-Location $OutputPath try { # Check for existing ISO files before download $preExistingISOs = Get-ChildItem -Path $OutputPath -Filter "*.iso" -ErrorAction SilentlyContinue # Enhanced execution with better error capture and timeout handling Write-WULog "Starting Fido execution - this may take several minutes for large downloads..." -Level Info # Execute Fido with process monitoring $fidoProcess = Start-Process -FilePath "powershell.exe" -ArgumentList "-ExecutionPolicy Bypass", "-File", $fidoPath, "-Win", $fidoParams.Win, "-Rel", $fidoParams.Rel, "-Ed", $fidoParams.Ed, "-Lang", $fidoParams.Lang, "-Arch", $fidoParams.Arch -PassThru -Wait -NoNewWindow -RedirectStandardOutput "$OutputPath\fido_output.txt" -RedirectStandardError "$OutputPath\fido_error.txt" $fidoExitCode = $fidoProcess.ExitCode # Read output files for detailed error analysis $fidoOutput = "" $fidoError = "" if (Test-Path "$OutputPath\fido_output.txt") { $fidoOutput = Get-Content "$OutputPath\fido_output.txt" -Raw Remove-Item "$OutputPath\fido_output.txt" -ErrorAction SilentlyContinue } if (Test-Path "$OutputPath\fido_error.txt") { $fidoError = Get-Content "$OutputPath\fido_error.txt" -Raw Remove-Item "$OutputPath\fido_error.txt" -ErrorAction SilentlyContinue } # Enhanced error analysis if ($fidoExitCode -and $fidoExitCode -ne 0) { $fullOutput = "$fidoOutput $fidoError" # Check for specific error patterns if ($fullOutput -match "RPC server.*unavailable|0x800706BA") { throw "Network RPC error during download (common with large files) - Exit Code: $fidoExitCode" } elseif ($fullOutput -match "715-123130|banned.*service|location.*hiding") { throw "Microsoft server restrictions detected - IP temporarily blocked - Exit Code: $fidoExitCode" } elseif ($fullOutput -match "403|forbidden") { throw "Access forbidden by Microsoft servers - Exit Code: $fidoExitCode" } else { throw "Fido exited with code: $fidoExitCode. Output: $fullOutput" } } # Enhanced ISO file detection with better timing Write-WULog "Fido execution completed, scanning for downloaded ISO..." -Level Info Start-Sleep -Seconds 5 # Allow file system to update $newIsoFiles = Get-ChildItem -Path $OutputPath -Filter "*.iso" -ErrorAction SilentlyContinue | Where-Object { $_.Name -notlike "*windows10*" -and $_.Name -like "*win*11*" } | Sort-Object LastWriteTime -Descending # Filter out pre-existing ISOs if ($preExistingISOs) { $newIsoFiles = $newIsoFiles | Where-Object { $newFile = $_ -not ($preExistingISOs | Where-Object { $_.Name -eq $newFile.Name }) } } if ($newIsoFiles) { $isoFile = $newIsoFiles[0].FullName $isoSize = [math]::Round((Get-Item $isoFile).Length / 1GB, 2) Write-WULog "Enhanced Fido download completed successfully: $isoFile ($isoSize GB)" -Level Info return $isoFile } else { # Extended fallback search $allIsos = Get-ChildItem -Path $OutputPath -Filter "*.iso" -ErrorAction SilentlyContinue | Where-Object { $_.Length -gt 3GB } | # Must be at least 3GB for Windows 11 Sort-Object LastWriteTime -Descending if ($allIsos) { $isoFile = $allIsos[0].FullName Write-WULog "Found potential Windows 11 ISO: $isoFile" -Level Info return $isoFile } throw "No Windows 11 ISO file found after Fido execution. Check output directory: $OutputPath" } } finally { Set-Location $previousLocation } } catch { Write-WULog "Enhanced Fido execution failed: $($_.Exception.Message)" -Level Error throw } finally { # Clean up Fido script and temporary files $filesToClean = @($fidoPath, "$OutputPath\fido_output.txt", "$OutputPath\fido_error.txt") foreach ($file in $filesToClean) { if (Test-Path $file) { try { Remove-Item $file -Force -ErrorAction SilentlyContinue } catch { # Ignore cleanup errors } } } Write-WULog "Fido script cleanup completed" -Level Info } } function Invoke-UUPDownloadEnhanced { <# .SYNOPSIS UUP method currently disabled - Fido is the recommended approach #> param( [string]$OutputPath, [string]$Language, [string]$Edition, [string]$Architecture, [switch]$Force ) Write-WULog "UUP download method disabled - using Fido for reliability" -Level Warning throw "UUP download method disabled - use Fido method" } function Invoke-MediaCreationToolDownloadEnhanced { <# .SYNOPSIS Media Creation Tool method currently disabled - Fido is the recommended approach #> param( [string]$OutputPath, [string]$Language, [string]$Edition, [string]$Architecture, [switch]$Force ) Write-WULog "Media Creation Tool method disabled - using Fido for reliability" -Level Warning throw "Media Creation Tool method disabled - use Fido method" } function Test-ISOFileEnhanced { <# .SYNOPSIS Enhanced verification that a file is a valid Windows 11 ISO file #> param( [Parameter(Mandatory)] [string]$Path ) try { if (-not (Test-Path $Path)) { Write-WULog "ISO file does not exist: $Path" -Level Error return $false } $file = Get-Item $Path Write-WULog "Verifying ISO file: $($file.Name) ($([math]::Round($file.Length / 1GB, 2)) GB)" -Level Info # Enhanced size validation for Windows 11 if ($file.Length -lt 3.5GB) { Write-WULog "ISO file too small for Windows 11: $([math]::Round($file.Length / 1GB, 2)) GB (minimum ~3.5GB)" -Level Warning return $false } if ($file.Length -gt 8GB) { Write-WULog "ISO file larger than expected: $([math]::Round($file.Length / 1GB, 2)) GB (maximum ~8GB)" -Level Warning } # Enhanced file signature verification try { $buffer = New-Object byte[] 4096 $stream = [System.IO.File]::OpenRead($Path) $bytesRead = $stream.Read($buffer, 0, 4096) $stream.Close() if ($bytesRead -lt 4096) { Write-WULog "Could not read sufficient bytes for verification" -Level Warning return $false } # Check for standard ISO signatures $hasISOSignature = $false # Look for "CD001" at various offsets (ISO 9660) for ($offset = 32768; $offset -lt 40000; $offset += 2048) { if ($offset + 5 -lt $buffer.Length) { $signature = [System.Text.Encoding]::ASCII.GetString($buffer, ($offset % $buffer.Length), 5) if ($signature -eq "CD001") { $hasISOSignature = $true break } } } # Alternative check for UDF signatures if (-not $hasISOSignature) { $udfPatterns = @("BEA01", "NSR02", "NSR03") foreach ($pattern in $udfPatterns) { $patternBytes = [System.Text.Encoding]::ASCII.GetBytes($pattern) for ($i = 0; $i -le ($buffer.Length - $patternBytes.Length); $i++) { $match = $true for ($j = 0; $j -lt $patternBytes.Length; $j++) { if ($buffer[$i + $j] -ne $patternBytes[$j]) { $match = $false break } } if ($match) { $hasISOSignature = $true break } } if ($hasISOSignature) { break } } } if ($hasISOSignature) { Write-WULog "ISO file signature verification passed" -Level Info } else { Write-WULog "No standard ISO signature found, but file may still be valid" -Level Warning } return $true } catch { Write-WULog "File signature verification failed: $($_.Exception.Message)" -Level Warning # Don't fail completely on signature check errors return $true } } catch { Write-WULog "Enhanced ISO file verification failed: $($_.Exception.Message)" -Level Error return $false } } #endregion Enhanced Helper Functions |