Advertisement
nullzilla

Upgrade Windows 11 with ISO

Oct 20th, 2023 (edited)
1,837
1
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. <# Upgrade Windows 11 with ISO by Isaac Good
  2. Notes:
  3.     - Make sure to increase the script timeout in Syncro to longer than your
  4.       $TimeToWaitForCompletion + typical download time or you won't get full output in logs.
  5.     - Microsoft servers are very limiting about downloads so for best results host your own
  6.       copy of the ISO on your website or a storage bucket like Wasabi that has free egress.
  7.     - Some machines will just exit Setup after a few seconds so I created an optional fallback of
  8.       running as the logged in user, elevating them temporarily to admin if needed. Sometimes
  9.       even that fails. Would love to have a better solution, please share your ideas.
  10.     - Read all the variables and make sure they're set appropriately for your environment!
  11.  
  12. Changelog:
  13. 1.1.2 / 2024-12-12
  14.         Changed - ISO is no longer deleted if upgrade fails (avoids excessive bandwidth/retry time)
  15.         Added - $ProvidedURLARM64 if you want to provide your own ISO for ARM64
  16.         Note - Fido now detects ARM64 and downloads the appropriate ISO
  17. 1.1.1 / 2023-11-01
  18.         Fixed - Since the Net command doesn't show local computer name as part of username,
  19.                 the script tried to give admin to users that already were admin, then removed it.
  20.   1.1 / 2023-10-20
  21.         Changed - Variable-ized some parameters, general cleanup & optimization
  22.         Added - Error catching/handling for key functions
  23.         Added - Option to provide your own ISO download URL to avoid rate limiting
  24.         Added - Option to upgrade Windows 10 to 11
  25.         Added - Option to temporarily elevate current user to Administrators group if needed
  26.         Added - Option to extend rollback period
  27.         Added - Alternate method of starting setup when running under SYSTEM fails (no idea why)
  28.         Added - Folder exclusion to prevent Windows Defender from interfering
  29.         Fixed - Replace deprecated Get-WMIObject with Get-CIMInstance
  30.   1.0 / 2023-10-08 - Original script provided by Doc in Syncro forums: https://community.syncromsp.com/t/windows-11-feature-update-script-to-22h2/9538/12
  31. #>
  32.  
  33. # Install Variables
  34. # Reference: https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-setup-command-line-options?view=windows-11
  35. $TargetFolder = "$env:Temp\Windows11Upgrade"
  36. $ISOFilePath = "$TargetFolder\Windows.iso"
  37. $WindowsSetupArguments = "/Auto Upgrade /BitLocker TryKeepActive /Compat IgnoreWarning /CopyLogs $TargetFolder /DynamicUpdate Enable /Eula Accept /MigrateDrivers All /NoReboot /Quiet /ShowOOBE None /Telemetry Disable"
  38. $RequiredSpaceInGB = '20'
  39. $UpgradeWindows10 = $true
  40. $RebootAfterSetup = $false
  41. $ElevateUserIfNeeded = $true # Temporarily adds user to Administrators group if needed, there is a brief window where script could be interrupted and user would stay admin!
  42. $TimeToWaitForCompletion = '180' # In minutes. Depending on the machine can take 20mins up to several hours
  43. $DaysToAllowRollback = '30' # Min 2, Max 60, Windows default is 10. If devices are tight for space you may want to reduce this
  44.  
  45. # Fido is used to retrieve Microsoft's ISO file URL https://github.com/pbatard/Fido/tree/master
  46. # These settings don't matter if you're providing your own ISO URL
  47. $FidoURL = "https://raw.githubusercontent.com/pbatard/Fido/master/Fido.ps1"
  48. $TargetVersion = 'Latest' # Example: '22H2' or 'Latest'
  49. $Language = 'English'
  50.  
  51. # Provide your own ISO download URL
  52. # If you do so, $TargetVersion and $Language above will be ignored
  53. $ProvidedURL = ''
  54. $ProvidedURLARM64 = ''
  55.  
  56. # Skip the TPM & CPU checks (optional)
  57. # Upgrading incompatible hardware means you will likely have to use this ISO upgrade method
  58. # for every new feature update on that machine so is commented out by default
  59. #reg add "HKLM\SYSTEM\Setup\MoSetup" /v "AllowUpgradesWithUnsupportedTPMOrCPU" /t REG_DWORD /d 1 /f
  60.  
  61. # Disable Privacy Settings Experience at first sign-in (optional)
  62. reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE /f /v DisablePrivacyExperience /t REG_DWORD /d 1 | Out-Null
  63.  
  64. ##### END OF VARIABLES #####
  65.  
  66. function Exit-WithError {
  67.     param ($Text)
  68.     Write-Output $Text
  69.     if (Get-Module | Where-Object { $_.ModuleBase -match 'Syncro' }) {
  70.         Rmm-Alert -Category "Windows 11 ISO Upgrade" -Body $Text
  71.     }
  72.     # Pause # Uncomment for interactive troubleshooting
  73.     SchTasks /delete /tn "Windows Setup" /f
  74.     Start-Sleep 10
  75.     exit 1
  76. }
  77.  
  78. function Get-Download {
  79.     param ($URL, $TargetFolder, $FileName)
  80.     $DownloadSize = (Invoke-WebRequest $URL -Method Head -UseBasicParsing).Headers.'Content-Length'
  81.     Write-Output "Downloading: $URL ($([math]::round($DownloadSize/1GB, 1)) GB)`nDestination: $TargetFolder\$FileName"
  82.     # Check if file already exists
  83.     if ($DownloadSize -ne (Get-ItemProperty $TargetFolder\$FileName -ErrorAction SilentlyContinue).Length) {
  84.         Invoke-WebRequest -Uri $URL -OutFile $TargetFolder\$FileName -UseBasicParsing
  85.         # Verify download success
  86.         $DownloadSizeOnDisk = (Get-ItemProperty $TargetFolder\$FileName -ErrorAction SilentlyContinue).Length
  87.         if ($DownloadSize -ne $DownloadSizeOnDisk) {
  88.             Remove-Item $TargetFolder\$FileName
  89.             Exit-WithError "Download size ($DownloadSize) and size on disk ($DownloadSizeOnDisk) do not match, download failed."
  90.         }
  91.     } else { Write-Output 'File with same size already exists at download target.' }
  92. }
  93.  
  94. function Start-Cleanup {
  95.     Write-Output "Cleaning up..."
  96.     SchTasks /delete /tn "Windows Setup" /f
  97.     while ((Test-Path $ISOFilePath) -and $CleanupAttempts -lt 10) {
  98.         Get-DiskImage -ImagePath "$ISOFilePath" -ErrorAction SilentlyContinue | Dismount-DiskImage | Out-Null
  99.         Start-Sleep 5
  100.         Remove-Item "$ISOFilePath" -ErrorAction SilentlyContinue
  101.         $CleanupAttempts = $CleanupAttempts + 1
  102.     }
  103. }
  104.  
  105. $OS = (Get-CimInstance Win32_OperatingSystem).Name
  106. if ($OS -notmatch "Windows 11" -and $UpgradeWindows10 -eq $false) {
  107.     Write-Output "Device is not running Windows 11 and Windows 10 upgrade is disabled, exiting."
  108.     exit
  109. } elseif ($OS -notmatch "Windows 10" -and (Get-ItemProperty -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name 'DisplayVersion').DisplayVersion -eq "$TargetVersion") {
  110.     Write-Output "Already running Windows 11 $TargetVersion, exiting."
  111.     exit
  112. }
  113.  
  114. # Check disk free space
  115. $Disk = Get-CimInstance -Class Win32_LogicalDisk | Where-Object { $_.DeviceID -eq $env:SystemDrive }
  116. $FreeSpaceInGB = [math]::Round($Disk.FreeSpace / 1GB, 2)
  117. if ($FreeSpaceInGB -lt $RequiredSpaceInGB) {
  118.     Exit-WithError "Drive $DriveLetter has only $FreeSpaceInGB GB free of the required $RequiredSpaceInGB GB, exiting."
  119. }
  120.  
  121. # Create the $TargetFolder directory if it doesn't exist and switch to it
  122. if (-not (Test-Path -Path "$TargetFolder" -PathType Container)) {
  123.     New-Item -Path "$TargetFolder" -ItemType Directory | Out-Null
  124. }
  125. Set-Location $TargetFolder
  126.  
  127. # Set how long to allow rollback
  128. DISM /Online /Set-OSUninstallWindow /Value:$DaysToAllowRollback | Out-Null
  129.  
  130. # Add folder exclusion so Windows Defender doesn't freak out about mounting a downloaded ISO
  131. Add-MpPreference -ExclusionPath $TargetFolder -ErrorAction SilentlyContinue
  132.  
  133. # Download the ISO
  134. if ((Get-CimInstance -ClassName Win32_Processor | Select-Object -ExpandProperty Architecture) -eq 12) {
  135.     $ProvidedURL = $ProvidedURLARM64
  136. }
  137. $ProgressPreference = "SilentlyContinue" # avoid slowdown from displaying progress
  138. if (-not $ProvidedURL) {
  139.     try { Get-Download -URL $FidoURL -TargetFolder $TargetFolder -FileName 'Fido.ps1' }
  140.     catch { Exit-WithError "Fido script download error: $($_.Exception.Message)" }
  141.     # Set execution policy to allow Fido.ps1 to run
  142.     Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
  143.     $ProvidedURL = &".\Fido.ps1" -Win 11 -Lang $Language -Rel $TargetVersion -GetURL
  144.     Remove-Item "$TargetFolder\Fido.ps1"
  145.     if (-not $ProvidedURL) {
  146.         Exit-WithError "Fido script did not return a download URL. Likely blocked by MS, see script log to confirm."
  147.     }
  148. }
  149. try { Get-Download -URL $ProvidedURL -TargetFolder $TargetFolder -FileName 'Windows.iso' }
  150. catch { Exit-WithError "Windows ISO download error: $($_.Exception.Message)" }
  151.  
  152. Write-Output "Mounting the ISO..."
  153. try { $MountResult = Mount-DiskImage -ImagePath $ISOFilePath -ErrorAction Stop }
  154. catch { Exit-WithError "Windows ISO mount error: $($_.Exception.Message)" }
  155.  
  156. Write-Output "Starting Windows Setup..."
  157. $BeginTime = Get-Date
  158. $MountedDrive = ($MountResult | Get-Volume).DriveLetter
  159. $process = Start-Process -FilePath "${MountedDrive}:\setup.exe" -ArgumentList $WindowsSetupArguments -Wait -PassThru
  160. Write-Output "Setup exit code: $($process.ExitCode)"
  161. if ($process.ExitCode -eq '-2147024769' -or $process.ExitCode -eq '-1073741502') {
  162.     $CurrentUser = Get-CimInstance -class Win32_ComputerSystem | Select-Object -ExpandProperty UserName
  163.     # If CurrentUser is a non-domain user, trim down to just the username as Net command doesn't show computer name in username
  164.     if ($CurrentUser -Like "$env:ComputerName\*") { $CurrentUser = $CurrentUser | Split-Path -Leaf }
  165.     if ($CurrentUser) {
  166.         Write-Output "Running setup under SYSTEM failed, trying to run as the currently logged in user instead."
  167.         if ((Net LocalGroup Administrators) -NotContains $CurrentUser -and $ElevateUserIfNeeded -eq $true) {
  168.             Write-Output "$CurrentUser is not in the Administrators group, adding them temporarily."
  169.             Net LocalGroup Administrators $CurrentUser /Add
  170.             $SetUserAsAdmin = $true
  171.         } elseif ((Net LocalGroup Administrators) -NotContains $CurrentUser -and $ElevateUserIfNeeded -eq $false) {
  172.             Exit-WithError "Running setup under SYSTEM failed, $CurrentUser is not in the Administrators group and ElevateUserToAdmin is off so scheduled task method also failed."
  173.         }
  174.         $StartTime = ((Get-Date).AddMinutes(2)).ToString('HH:mm')
  175.         SchTasks /Create /TN "Windows Setup" /SC Once /ST $StartTime /TR "${MountedDrive}:\setup.exe $WindowsSetupArguments" /RU $CurrentUser /RL Highest /F
  176.     } else {
  177.         Exit-WithError "Running setup under SYSTEM failed and there was no logged in user so scheduled task method also failed."
  178.     }
  179. } elseif ($process.ExitCode -eq '-1047526912') {
  180.     Exit-WithError "Setup exited because the device does not meet the minimum requirements to upgrade Windows."
  181. } elseif ($process.ExitCode -eq '-2147024680') {
  182.     Exit-WithError "Device may be running an ARM processor which MS doesn't provide an ISO for, you can generate your own using uupdump.net or use the Windows Update GUI."
  183. }
  184.  
  185. Write-Output "Waiting for upgrade to complete..."
  186. if ($SetUserAsAdmin) {
  187.     Start-Sleep 140
  188.     Write-Output "While the upgrade is running we can remove $CurrentUser from the Administrators group."
  189.     Net LocalGroup Administrators $CurrentUser /Delete
  190.     if (-not (Get-Process | Select-Object Path | Where-Object { $_.Path -like "${MountedDrive}:\*" })) {
  191.         Exit-WithError "Setup isn't running, scheduled task method must have also failed. Try upgrading this machine manually."
  192.     }
  193. }
  194. while ((Get-EventLog -Log 'Application' -Source 'System Restore' -EntryType 'Information' -InstanceId '8198' -After $BeginTime -ErrorAction SilentlyContinue).Count -eq 0 -and $minutes -lt $TimeToWaitForCompletion) {
  195.     Start-Sleep 60
  196.     $minutes = $minutes + 1
  197. }
  198. if ($minutes -eq $TimeToWaitForCompletion) {
  199.     Exit-WithError "It's been over $TimeToWaitForCompletion minutes, something probably went wrong, check logs for details."
  200. } else {
  201.     Write-Output "Event ID 8198 found, indicating setup has completed and is ready to restart. Cleaning up."
  202.     Start-Cleanup
  203.     if ($RebootAfterSetup) {
  204.         'Rebooting...'
  205.         shutdown /r /f
  206.     }
  207. }
  208.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement