-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathForce Update Windows 10 & 11 to Latest Version
344 lines (307 loc) · 19.6 KB
/
Force Update Windows 10 & 11 to Latest Version
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
<# Upgrade Windows 10 & 11 to Current Build
Adapted from nullzilla's script that can be found: https://pastebin.com/S9AuGaZ6
This script will attempt to upgrade in various ways depending on the current version installed
and the options selected below. Assuming all methods are allowed, it will:
- Exit if less than Windows 10 or already on current build or build is incompatible with all allowed methods.
- If the build is new enough for enablement, it applies the enablement CAB file. If that fails, it attempts the Update Assistant method.
- If the build is new enough for enablement but the UBR is not, it attempts to install the required cumulative update.
- If the build is too old for enablement, it attempts to upgrade via Update Assistant.
Notes:
- Make sure to increase the script timeout in Rewst to longer than your $TimeToWaitForCompletion + download time or you won't get full output in logs.
- Read all the variables and make sure they're set appropriately for your environment!
- For some reason silent install for Windows 11 Update Assistant doesn't work well in ScreenConnect Backstage, but works ok from RMM (mostly).
Future development ideas:
- Write attempted method to file/reg and then progressively try next method/alert if failed on next script run
- Check for other blocking registry entries, option to clear blockers
- Options to allow upgrade from 7 to 10/11, 10 to 11
- Integrate ISO upgrade script as another fallback method
Changelog:
2.0 / 2024-02-16
Added - psobject creation to be able to gather results and post back to Rewst
Added - POST to Rewst
Added - Jinja input variable to be able to set $RebootAfterUpgrade as a $true or $false depending on how we want the script to run
Added - Jinja input variable to be able to set $AttemptUpdateAssistant as a $true or $false to avoid force reboot during working hours
Added - Jinja input org variable to be set $Win10LatestVersion via {{ ORG.VARIABLES.Win10LatestVersion }} which is currently 22H2 - Note this is the last major version of Windows 10
Added - Jinja input org variable to be set $Win11LatestVersion via {{ ORG.VARIABLES.Win11LatestVersion }} which is currently 23H2
Removed - Commented out Syncro specific functions/commands
Removed - Commented out Privacy Settings Experience registry key
1.2 / 2023-11-19
Changed - Refactored to use more functions & variables (less redundant code)
Changed - Minimum build for enablement to 19042.1865 for 22H2
Changed - $Win1xTargetVersion to $Win1xLatestVersion to clarify they are only for determining if updates are needed
Added - Windows 11 Update Assistant & 23H2 enablement capabilities
Added - Optional installation of cumulative updates required for enablement packages
Added - Error catching and notification for failed downloads
Removed - Windows 10 21H2 enablement packages (end of servicing 6/13/2023)
Fixed - Wrapped Syncro commands in if's to eliminate errors when used with other platforms or in testing
1.1.1 / 2023-08-04
Fixed - TargetReleaseVersion registry check had no Write-Output
1.1 / 2023-08-03
Changed - Syncro Alert name to 'Upgrade Windows', update any Automated Remediations accordingly
Changed - Improved error catching and notification
Added - Groundwork for support for Windows 11 (no enablement packages yet and UA won't upgrade under SYSTEM user)
Added - General code cleanup, improved consistency and documentation
Added - Support for Windows 10 x86 and 21H2 enablement packages
Fixed - Correct enablement minimum build (from 1247 to 1237)
Fixed - Removed UBR 1237 requirement for non-enablement that was causing script to fail on 18362.1856
1.0 / 2022-12-08 - Initial release
#>
#if ($null -ne $env:SyncroModule) { Import-Module $env:SyncroModule -DisableNameChecking }
# Reboot after upgrade (if changing this to $false also set $AttemptWin1xUpdateAssistant to $false)
$RebootAfterUpgrade = {{ CTX.RebootAfterUpgrade }}
# Attempt Cumulative Update if UBR is not new enough for enablement
$AttemptCumulativeUpdate = $true
# Attempt Update Assistant method if enablement fails/not possible
# $true can cause a reboot regardless of reboot setting above as UA forces a restart
$AttemptUpdateAssistant = {{ CTX.AttemptUpdateAssistant }}
# Ignore Windows Update Target Release Version registry settings
$IgnoreTargetReleaseVersion = $false
# Location to download files
$TargetFolder = "$env:Temp"
# How many minutes to wait before assuming something went wrong.
# Depending on the machine and bandwidth upgrades can take up to several hours.
$TimeToWaitForCompletion = '180'
# Disable Privacy Settings Experience at first sign-in (optional)
#reg add HKLM\SOFTWARE\Policies\Microsoft\Windows\OOBE /f /v DisablePrivacyExperience /t REG_DWORD /d 1 | Out-Null
# Latest version of Windows currently available (for determining if updates are needed)
$Win10LatestVersion = "{{ ORG.VARIABLES.Win10LatestVersion }}"
$Win11LatestVersion = "{{ ORG.VARIABLES.Win11LatestVersion }}"
# Enablement Packages (Windows 11 x86 doesn't exist)
$Win10EPURLx64 = 'https://catalog.s.download.windowsupdate.com/c/upgr/2022/07/windows10.0-kb5015684-x64_d2721bd1ef215f013063c416233e2343b93ab8c1.cab'
$Win10EPURLx86 = 'https://catalog.s.download.windowsupdate.com/c/upgr/2022/07/windows10.0-kb5015684-x86_3734a3f6f4143b645788cc77154f6288c8054dd5.cab'
$Win10EPRequiredBuild = "19042"
$Win10EPRequiredUBR = "1865"
$Win11EPURLx64 = 'https://catalog.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/49a41627-758a-42b7-8786-cc61cc19f1ee/public/windows11.0-kb5027397-x64_955d24b7a533f830940f5163371de45ff349f8d9.cab'
$Win11EPRequiredBuild = "22621"
$Win11EPRequiredUBR = "2506"
# Cumulative Updates required for Enablement Packages
$Win10CUKB = 'KB5026361'
$Win10CUURL = 'https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/secu/2023/05/windows10.0-kb5026361-x64_961f439d6b20735f067af766e1813936bf76cb94.msu'
$Win10CURequiredBuild = "19042"
$Win10CURequiredUBR = "985"
$Win11CUKB = 'KB5032190'
$Win11CUURL = 'https://catalog.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/cd35ece3-585a-48e9-a9b5-ad5cd3699d32/public/windows11.0-kb5032190-x64_fdbd38c60e7ef2c6adab4bf5b508e751ccfbd525.msu'
$Win11CURequiredBuild = "22621"
$Win11CURequiredUBR = "521"
# Update Assistants
$Win10UAURL = 'https://go.microsoft.com/fwlink/?LinkID=799445'
$Win11UAURL = 'https://go.microsoft.com/fwlink/?LinkID=2171764'
### END OF VARIABLES / START FUNCTIONS ###
function Exit-WithError {
param ( $Text )
$PS_Results| Write-Output $Text
if (Get-Module | Where-Object { $_.ModuleBase -match 'Syncro' }) {
Rmm-Alert -Category "Upgrade Windows" -Body $Text
}
exit 1
}
#Removed 2024-02-15 by BS as not applicable for non-Syncro RMM
<#function Clear-Alert {
if (Get-Module | Where-Object { $_.ModuleBase -match 'Syncro' }) {
$PS_Results | Close-Rmm-Alert -Category "Upgrade Windows"
}
}#>
function Invoke-Reboot {
if ($RebootAfterUpgrade) {
$PS_Results | Write-Output "Reboot variable enabled, initiating reboot."
# If Automatic Restart Sign-On is enabled, /g allows the device to automatically sign in and lock
# based on the last interactive user. After sign in, it restarts any registered applications.
shutdown /g /f /t 120
exit
}
}
function Get-Download {
param ($URL, $TargetFolder, $FileName)
$DownloadSize = (Invoke-WebRequest $URL -Method Head -UseBasicParsing).Headers.'Content-Length'
Write-Output "Downloading: $URL ($([math]::round($DownloadSize/1MB, 1)) MB)`nDestination: $TargetFolder\$FileName..."
# Check if file already exists
if ($DownloadSize -ne (Get-ItemProperty $TargetFolder\$FileName -ErrorAction SilentlyContinue).Length) {
Start-BitsTransfer -Source $URL -Destination $TargetFolder\$FileName -Priority Normal
# Verify download success
$DownloadSizeOnDisk = (Get-ItemProperty $TargetFolder\$FileName -ErrorAction SilentlyContinue).Length
if ($DownloadSize -ne $DownloadSizeOnDisk) {
Remove-Item $TargetFolder\$FileName
Exit-WithError "Download size ($DownloadSize) and size on disk ($DownloadSizeOnDisk) do not match, download failed."
}
} else { Write-Output 'File with same size already exists at download target.' }
}
function Invoke-CumulativeUpdate {
# Determine destination filename
$KBFile = [io.path]::GetFileName("$KBURL")
try { Get-Download -URL $KBURL -TargetFolder $TargetFolder -FileName $KBFile }
catch { Exit-WithError "Cumulative Update download failed: $($_.Exception.Message)" }
Start-Process -FilePath "WUSA.exe" -ArgumentList "$TargetFolder\$KBFile /quiet /norestart"
# WUSA.exe only kicks off the update, so -Wait has no affect, we have to monitor event log instead
$BeginTime = Get-Date
Write-Output "Waiting for Cumulative Update to install..."
while ($null -eq ((New-Object -ComObject 'Microsoft.Update.Session').QueryHistory("", 0, 1) | Where-Object { $_.Date -gt $BeginTime -and $_.Title -like "*$KB*" }) -and $minutes -lt $TimeToWaitForCompletion) {
Start-Sleep 60
$minutes = $minutes + 1
}
if (((New-Object -ComObject 'Microsoft.Update.Session').QueryHistory("", 0, 1) | Where-Object { $_.Date -gt $BeginTime -and $_.Title -like "*$KB*" }).ResultCode -eq "4") {
Exit-WithError "Cumulative Update install failed."
} else {
Remove-Item $TargetFolder\$KBFile
if ($minutes -eq $TimeToWaitForCompletion) {
Exit-WithError "It's been over $TimeToWaitForCompletion minutes, something probably went wrong. Here's the Windows Update event logs:`n$((Get-EventLog -Log 'System' -Source 'Microsoft-Windows-WindowsUpdateClient' -After $BeginTime -ErrorAction SilentlyContinue).Message)"
} else {
Write-Output "Cumulative Update has completed successfully."
#Clear-Alert
}
Invoke-Reboot
}
}
function Invoke-EnablementUpgrade {
# Determine destination filename
$CABFile = [io.path]::GetFileName("$EPURL")
try { Get-Download -URL $EPURL -TargetFolder $TargetFolder -FileName $CABFile }
catch { Exit-WithError "Enablement package download failed: $($_.Exception.Message)" }
try {
Write-Output "Adding the enablement package to the image..."
$Arguments = "/Online /Add-Package /PackagePath:$TargetFolder\$CABFile /Quiet /NoRestart"
$Process = Start-Process 'dism.exe' -ArgumentList $Arguments -PassThru -Wait -NoNewWindow
if ($Process.ExitCode -eq '3010') {
Write-Output "Exit Code 3010: Package added successfully."
#Clear-Alert
} elseif ($null -ne $Process.StdError) {
Exit-WithError "DISM error: $($Process.StdError)"
}
Remove-Item $TargetFolder\$CABFile
} catch {
Write-Output "Enablement package install failed. Error: $($_.exception.Message)"
if ($AttemptUpdateAssistant) {
Write-Output "Attempting Update Assistant method instead."
Invoke-UpdateAssistant
} else { Exit-WithError "Error: $($_.exception.Message)" }
}
Invoke-Reboot
}
function Invoke-UpdateAssistant {
# Check for free space
$DiskSpaceRequired = '11' # in GBs
$DiskSpace = [Math]::Round((Get-CimInstance -Class Win32_Volume | Where-Object { $_.DriveLetter -eq $env:SystemDrive } | Select-Object -ExpandProperty FreeSpace) / 1GB)
if ($DiskSpace -lt $DiskSpaceRequired) {
Exit-WithError "Only $DiskSpace GB free, $DiskSpaceRequired GB required."
} else {
# Retrieve headers to make sure we have the actual file URL after any redirections
$UAURL = (Invoke-WebRequest -UseBasicParsing -Uri $UAURL -MaximumRedirection 0 -ErrorAction Ignore).headers.location
# Determine destination filename
$UAFile = [io.path]::GetFileName("$UAURL")
try { Get-Download -URL $UAURL -TargetFolder $TargetFolder -FileName $UAFile }
catch { Exit-WithError "Update Assistant download failed: $($_.Exception.Message)" }
if (-not(Test-Path "$TargetFolder\$UAFile")) {
Exit-WithError "Update Assistant downloaded file not found."
} else {
Write-Output "Update Assistant download successful, starting upgrade."
if ((Get-Process 'Windows10UpgraderApp' -ErrorAction SilentlyContinue).Count -gt 0) {
Exit-WithError "Update Assistant already running, wait for it to complete or reboot and try again."
} else {
try {
$Arguments = "/QuietInstall /SetPriorityLow /SkipEULA /CopyLogs $TargetFolder"
Start-Process "$TargetFolder\$UAFile" -ArgumentList "$Arguments"
Start-Sleep -s 120
Remove-Item "$TargetFolder\$UAFile" -Force
} catch { Exit-WithError "Update Assistant error: $($_.exception.Message)" }
Write-Output "Waiting for Update Assistant to complete..."
$BeginTime = Get-Date
while ((Get-EventLog -Log 'System' -Source 'Microsoft-Windows-Kernel-General' -EntryType 'Information' -InstanceId '16' -After $BeginTime -ErrorAction SilentlyContinue | Where-Object { $_.Message -like '*NewOS\WINDOWS\System32\config\BCD-Template*' }).Count -eq 0 -and $minutes -lt $TimeToWaitForCompletion) {
if ((Get-EventLog -Log 'Application' -Source 'Windows Error Reporting' -EntryType 'Information' -InstanceId '1001' -After $BeginTime -ErrorAction SilentlyContinue).Message -match 'WinSetupDiag') {
Exit-WithError "Update Assistant has failed, try running interactively."
} elseif ((Get-EventLog -Log 'Application' -Source 'Application Error' -EntryType 'Error' -InstanceId '1000' -After $BeginTime -ErrorAction SilentlyContinue).Message -match 'Faulting application name: setuphost.exe') {
Invoke-Reboot
Exit-WithError "SetupHost.exe failed. Windows may need a reboot (doing so now if enabled), then try running script again."
}
Start-Sleep 60
$minutes = $minutes + 1
}
if ($minutes -eq $TimeToWaitForCompletion) {
Exit-WithError "It's been over $TimeToWaitForCompletion minutes, something probably went wrong. Here's the Windows Update event logs:`n$((Get-EventLog -Log 'System' -Source 'Microsoft-Windows-WindowsUpdateClient' -After $BeginTime -ErrorAction SilentlyContinue).Message)"
} else {
Write-Output "Update Assistant completed successfully."
#Clear-Alert
}
Invoke-Reboot
}
}
}
}
### END FUNCTIONS / START MAIN SCRIPT ###
# Create Object for POST Back to Rewst Later
$PS_Results = New-Object -TypeName psobject
# Get version/build info
# 19041 and older do not have DisplayVersion key, if so we grab ReleaseID instead (no longer updated in new versions)
$MajorVersion = ([System.Environment]::OSVersion.Version).Major
$CurrentVersion = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion"
if ($CurrentVersion.DisplayVersion) {
$DisplayVersion = $CurrentVersion.DisplayVersion
} else { $DisplayVersion = $CurrentVersion.ReleaseId }
# Get build and UBR (we keep separate as UBR can be 3 or 4 digits which confuses comparison operators in combined form)
$Build = $CurrentVersion.CurrentBuildNumber
$UBR = $CurrentVersion.UBR
# Correct Microsoft's version number for Windows 11
if ($Build -ge 22000) { $MajorVersion = '11' }
# Set variables based on OS version
switch ($MajorVersion) {
10 {
$WinLatestVersion = $Win10LatestVersion
$EPRequiredBuild = $Win10EPRequiredBuild; $EPRequiredUBR = $Win10EPRequiredUBR
$CURequiredBuild = $Win10CURequiredBuild; $CURequiredUBR = $Win10CURequiredUBR
$KB = $Win10CUKB; $KBURL = $Win10CUURL; $UAURL = $Win10UAURL
if ([Environment]::Is64BitOperatingSystem) {
$EPURL = $Win10EPURLx64
} else { $EPURL = $Win10EPURLx86 }
}
11 {
$WinLatestVersion = $Win11LatestVersion
$EPRequiredBuild = $Win11EPRequiredBuild; $EPRequiredUBR = $Win11EPRequiredUBR
$CURequiredBuild = $Win11CURequiredBuild; $CURequiredUBR = $Win11CURequiredUBR
$KB = $Win11CUKB; $KBURL = $Win11CUURL; $UAURL = $Win11UAURL; $EPURL = $Win11EPURLx64
}
}
$PS_Results | Write-Output "Windows $MajorVersion $DisplayVersion build $Build.$UBR detected."
# Convert versions to numerical form so comparison operators can be used
$DisplayVersionNumerical = ($DisplayVersion).replace('H1', '05').replace('H2', '10')
$WinLatestVersionNumerical = ($WinLatestVersion).replace('H1', '05').replace('H2', '10')
# Create the $TargetFolder directory if it doesn't exist
if (-not (Test-Path -Path "$TargetFolder" -PathType Container)) {
$PS_Results | New-Item -Path "$TargetFolder" -ItemType Directory | Out-Null
}
# Ineligible Upgrade Conditions
if ($MajorVersion -lt '10') {
$PS_Results | Write-Output "Windows versions prior to 10 cannot be updated with this script."
$PS_Results | exit 0
} elseif ($DisplayVersionNumerical -ge $WinLatestVersionNumerical) {
$PS_Results | Write-Output "Already running $DisplayVersion which is the same or newer than target release $Win10LatestVersion, no update required."
#Clear-Alert
$PS_Results | exit 0
} elseif ($AttemptCumulativeUpdate -eq $false -and $AttemptUpdateAssistant -eq $false -and
$Build -lt $EPRequiredBuild -and $UBR -lt $EPRequiredUBR) {
$PS_Results | Exit-WithError "Windows $MajorVersion builds older than $EPRequiredBuild.$EPRequiredUBR cannot be upgraded with enablement package and Cumulative Update/Update Assistant methods are disabled."
} elseif ($IgnoreTargetReleaseVersion -eq $false -and
(Test-Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate") -eq $true) {
$WindowsUpdateKey = Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate" -ErrorAction SilentlyContinue
if ($WindowsUpdateKey.TargetReleaseVersion -eq 1 -and $WindowsUpdateKey.TargetReleaseVersionInfo) {
$WindowsUpdateTargetReleaseNumerical = ($WindowsUpdateKey.TargetReleaseVersionInfo).replace('H1', '05').replace('H2', '10')
if ($WindowsUpdateTargetReleaseNumerical -lt $winLatestVersionNumerical) {
$PS_Results | Exit-WithError "Windows Update TargetReleaseVersion registry settings are in place limiting upgrade to $($WindowsUpdateKey.TargetReleaseVersionInfo). To ignore these settings, change the script variable or target version and run again."
}
}
}
# Eligible Upgrade Conditions
if ($AttemptCumulativeUpdate -eq $true -and
$Build -ge $EPRequiredBuild -and $UBR -lt $EPRequiredUBR -and
$Build -ge $CURequiredBuild -and $UBR -ge $CURequiredUBR) {
$PS_Results | Write-Output "Windows $MajorVersion UBR's older than $EPRequiredUBR cannot be upgraded with enablement package. Installing required Cumulative Update."
$PS_Results | Invoke-CumulativeUpdate
} elseif ($Build -ge $EPRequiredBuild -and $UBR -ge $EPRequiredUBR) {
$PS_Results | Invoke-EnablementUpgrade
} elseif ($AttemptUpdateAssistant -eq $true) {
$PS_Results | Write-Output "Build is not compatible with enablement upgrade, attempting Update Assistant method instead."
$PS_Results | Invoke-UpdateAssistant
} else {
$PS_Results | Exit-WithError "Eligibility logic failed, check script for issues."
}
# Post results back to Rewst
$postData = $PS_Results | ConvertTo-Json
$postData = [System.Text.Encoding]::UTF8.GetBytes($postData)
Invoke-RestMethod -Method 'Post' -Uri $post_url -Body $postData -ContentType 'application/json; charset=utf-8'