Windows Update cho System Admin & Helpdesk: 4 cách cập nhật và bộ lệnh kiểm tra lỗi
Đợt nâng cấp Windows 11 từ 24H2 lên 25H2 vừa rồi, mình phải xử lý hàng loạt laptop không chịu lên phiên bản mới. Máy có trong WSUS, group đã được approve, Intune vẫn check-in bình thường, vậy mà build vẫn đứng yên ở 26100. Có máy fail vì pending reboot, có máy bị phần mềm EDR chặn driver của Setup, có máy chỉ đơn giản là user tắt máy giữa chừng. Bài này tổng hợp lại tất cả các cách mình đã dùng để đẩy Windows Update xuống máy, cùng bộ lệnh để Helpdesk tự kiểm tra trước khi escalate.
Tóm tắt nhanh: Dùng Intune Update Ring hoặc WSUS làm kênh chính cho cả fleet. Với những máy bị kẹt, dùng PSWindowsUpdate để ép cài patch tháng, và Windows 11 Installation Assistant để ép nâng cấp feature update. Trước khi sửa, luôn check OS version, pending reboot và log lỗi.
Bối cảnh: vì sao cần nhiều cách
Một môi trường doanh nghiệp thường có hai loại update:
- Quality update — bản vá hằng tháng, phát hành vào thứ Ba của tuần thứ hai mỗi tháng (Patch Tuesday).
- Feature update — phiên bản mới mỗi năm một lần, thường vào khoảng tháng 10 (ví dụ 24H2 → 25H2).
Kênh chính (WSUS hoặc Intune) xử lý tốt khoảng 90% máy. Phần còn lại là máy ít bật, máy hay mang ra ngoài, máy bị lỗi component store, máy bị agent bảo mật can thiệp. Những máy này cần một “kế hoạch B” chạy bằng script.
Bảng tra build Windows 11
| Phiên bản | Build | Ghi chú |
|---|---|---|
| 24H2 | 26100.x |
Số sau dấu chấm (UBR) cho biết bản patch tháng |
| 25H2 | 26200.x |
25H2 dùng chung codebase với 24H2, nên số UBR thường giống nhau |
So sánh 4 cách cập nhật Windows
| Cách | Phù hợp khi | Ưu điểm | Hạn chế |
|---|---|---|---|
| 1. PSWindowsUpdate | Ép cài patch tháng, driver, firmware trên máy bị tụt patch | Kiểm soát chi tiết từng KB, chạy được qua Intune | Không xử lý pending reboot, script Intune có timeout |
| 2. Installation Assistant | Ép nâng cấp feature update (24H2 → 25H2) | Full in-place upgrade, dựng lại component store | Chạy lâu (vài giờ), dễ bị agent bảo mật chặn |
| 3. WSUS | Môi trường on-prem, cần duyệt từng bản update | Tiết kiệm băng thông, kiểm soát approval | Phải bảo trì server, SUSDB dễ phình |
| 4. Intune Update Ring | Máy hybrid/cloud, user làm việc từ xa | Không cần hạ tầng, có deadline và report | Cần license phù hợp, phải tính deferral hợp lý |
Ngoài ra, Helpdesk vẫn có thể bấm Settings → Windows Update → Check for updates trên máy user. Đây là cách nhanh nhất cho một máy lẻ, nhưng không dùng được để xử lý hàng loạt.
Cách 1: Dùng module PSWindowsUpdate
PSWindowsUpdate là module PowerShell cho phép scan, tải và cài update bằng lệnh. Mình dùng nó trong một Intune Win32 app để xử lý các máy bị tụt patch nhiều tháng.
Cài module (chạy bằng quyền Admin)
[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module PSWindowsUpdate -Scope AllUsers -Force -AllowClobber
Import-Module PSWindowsUpdate
Các lệnh hay dùng
# Scan update từ nguồn mặc định (WSUS nếu máy đang trỏ WSUS)
Get-WindowsUpdate
# Scan từ Microsoft Update (có driver, firmware, Office...)
Get-WindowsUpdate -MicrosoftUpdate
# Cài tất cả, không tự reboot
Get-WindowsUpdate -AcceptAll -Install -IgnoreReboot -Verbose
# Cài driver/firmware nhưng bỏ qua Feature Upgrade và Hotpatch
Get-WindowsUpdate -MicrosoftUpdate -AcceptAll -Install -IgnoreReboot -NotCategory "Upgrades" -NotTitle 'Security Update \(Hotpatch capable\)' -Verbose
# Cài đúng một KB
Get-WindowsUpdate -KBArticleID "KB5xxxxxx" -Install -AcceptAll -IgnoreReboot -Verbose
# Lịch sử update và trạng thái reboot
Get-WUHistory -Last 50
Get-WURebootStatus -Silent
-IgnoreReboot chỉ có nghĩa là không tự restart sau khi cài. Nó không bỏ qua trạng thái pending reboot. Nếu máy đang chờ reboot, bản update tiếp theo sẽ fail với lỗi 0x80240016.Script của mình
# ==========================================================
# Windows Update Remediation (PSWindowsUpdate)
# Deploy via Intune Win32 app - SYSTEM context, 64-bit
# ==========================================================
# ==========================================================
# Fix Patching Windows Update
# Deploy via Intune (SYSTEM context, 64-bit host)
# ==========================================================
$LogPath = "C:\Temp\Fix_Patching_Windows.log"
if (-not (Test-Path "C:\Temp")) {
New-Item -Path "C:\Temp" -ItemType Directory -Force | Out-Null
}
$ErrorActionPreference = "Stop"
Start-Transcript -Path $LogPath -Append -Force
try {
Write-Host ""
Write-Host "========================================="
Write-Host "Windows Update Remediation Started"
Write-Host "Computer: $env:COMPUTERNAME"
Write-Host "User: $(whoami)"
Write-Host "Time: $(Get-Date)"
Write-Host "========================================="
Write-Host ""
Write-Host "[STEP 1] Enabling TLS 1.2..."
[Net.ServicePointManager]::SecurityProtocol = `
[Net.ServicePointManager]::SecurityProtocol -bor `
[Net.SecurityProtocolType]::Tls12
Write-Host "[STEP 1] TLS configuration completed."
Write-Host "SecurityProtocol: $([Net.ServicePointManager]::SecurityProtocol)"
Write-Host ""
Write-Host "[STEP 2] Checking NuGet provider..."
$NuGet = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
if (-not $NuGet) {
Write-Host "[STEP 2] NuGet provider not found."
Write-Host "[STEP 2] Installing NuGet provider..."
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -ForceBootstrap -ErrorAction Stop | Out-Null
Write-Host "[STEP 2] NuGet installation command completed."
$NuGet = Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue
if (-not $NuGet) {
throw "NuGet provider installation completed but NuGet cannot be detected."
}
Write-Host "[STEP 2] NuGet provider installed successfully."
}
else {
Write-Host "[STEP 2] NuGet provider already installed."
}
Write-Host "[STEP 2] NuGet version: $($NuGet.Version)"
Write-Host ""
Write-Host "[STEP 3] Checking PSGallery..."
$PSGallery = Get-PSRepository -Name PSGallery -ErrorAction Stop
Write-Host "[STEP 3] PSGallery current policy: $($PSGallery.InstallationPolicy)"
if ($PSGallery.InstallationPolicy -ne "Trusted") {
Write-Host "[STEP 3] Setting PSGallery to Trusted..."
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted -ErrorAction Stop
$PSGallery = Get-PSRepository -Name PSGallery -ErrorAction Stop
if ($PSGallery.InstallationPolicy -ne "Trusted") {
throw "Failed to set PSGallery to Trusted."
}
Write-Host "[STEP 3] PSGallery successfully set to Trusted."
}
else {
Write-Host "[STEP 3] PSGallery already Trusted."
}
Write-Host ""
Write-Host "[STEP 4] Checking PSWindowsUpdate module..."
$PSWU = Get-Module -ListAvailable -Name PSWindowsUpdate | Sort-Object Version -Descending | Select-Object -First 1
if (-not $PSWU) {
Write-Host "[STEP 4] PSWindowsUpdate not found."
Write-Host "[STEP 4] Installing PSWindowsUpdate..."
Install-Module -Name PSWindowsUpdate -Repository PSGallery -Scope AllUsers -Force -AllowClobber -Confirm:$false -ErrorAction Stop
Write-Host "[STEP 4] PSWindowsUpdate installation command completed."
$PSWU = Get-Module -ListAvailable -Name PSWindowsUpdate | Sort-Object Version -Descending | Select-Object -First 1
if (-not $PSWU) {
throw "PSWindowsUpdate installation completed but module cannot be detected."
}
Write-Host "[STEP 4] PSWindowsUpdate installed successfully."
}
else {
Write-Host "[STEP 4] PSWindowsUpdate already installed."
}
Write-Host "[STEP 4] PSWindowsUpdate version: $($PSWU.Version)"
Write-Host ""
Write-Host "[STEP 5] Importing PSWindowsUpdate..."
Import-Module PSWindowsUpdate -Force -ErrorAction Stop
if (-not (Get-Command Get-WindowsUpdate -ErrorAction SilentlyContinue)) {
throw "Get-WindowsUpdate cmdlet not found after importing PSWindowsUpdate."
}
Write-Host "[STEP 5] PSWindowsUpdate imported successfully."
Write-Host "[STEP 5] Get-WindowsUpdate command verified."
Write-Host ""
Write-Host "[STEP 6] Searching and installing available updates..."
Write-Host "[STEP 6] Excluded KBs: KB5120994, KB5121003, KB5124008"
Get-WindowsUpdate -MicrosoftUpdate -AcceptAll -Install -IgnoreReboot -NotCategory "Upgrades" -NotTitle 'Security Update \(Hotpatch capable\)' -Verbose -ErrorAction Stop
Get-WindowsUpdate -AcceptAll -Install -IgnoreReboot -Verbose -ErrorAction Stop
Write-Host ""
Write-Host "========================================="
Write-Host "Windows Update completed successfully."
Write-Host "Time: $(Get-Date)"
Write-Host "========================================="
Stop-Transcript
exit 0
}
catch {
Write-Host ""
Write-Host "============== ERROR =============="
Write-Host "Message : $($_.Exception.Message)"
Write-Host "Type : $($_.Exception.GetType().FullName)"
Write-Host "Line : $($_.InvocationInfo.ScriptLineNumber)"
Write-Host "Command : $($_.InvocationInfo.Line)"
Write-Host "Time : $(Get-Date)"
Write-Host "==================================="
try { Stop-Transcript } catch {}
exit 1
}
Khi viết script loại này, nhớ ba điểm:
- Kiểm tra kết quả từng update.
-ErrorAction Stopkhông bắt được trường hợp cmdlet chạy xong nhưng một update trả vềResult = Failed. Hãy lưu output lại và đếm số dòngFailed. - Dừng khi có pending reboot. Sau phase cài driver/.NET, nếu máy cần reboot thì
exit 3010để Intune xử lý reboot, đừng chạy tiếp phase cài cumulative update. - Intune PowerShell script (Platform script) có timeout 30 phút. Tải và cài update dễ vượt quá thời gian này, nên đóng gói thành Win32 app thay vì Platform script.
Cách 2: Dùng Windows 11 Installation Assistant
Khi feature update qua WSUS/Intune không chạy, mình dùng Windows 11 Installation Assistant để ép in-place upgrade. Assistant tải toàn bộ media và chạy Windows Setup, nên nó cũng là cách “chữa cháy” cho máy bị lỗi component store.
Chạy thủ công
# Chạy im lặng, tự chấp nhận EULA, copy log ra thư mục riêng
Start-Process "C:\Temp\Windows11InstallationAssistant.exe" -ArgumentList '/QuietInstall /SkipEULA /CopyLogs "C:\Temp\AssistantLogs"'
Thiết kế để chạy tự động
Upgrade cần khoảng 6 tiếng máy bật liên tục và có mạng. Nếu user tắt máy giữa chừng, upgrade dừng lại. Vì vậy mình không chạy Assistant trực tiếp từ Intune, mà làm như sau:
- Intune Win32 app copy
Windows11InstallationAssistant.exevà script chạy vào máy, rồi tạo một Scheduled Task chạy bằng SYSTEM. - Task chạy mỗi khi máy khởi động và lặp lại mỗi 1-2 tiếng.
- Mỗi lần chạy, script kiểm tra:
- Build ≥
26200→ đã lên 25H2, tự xóa task và dừng. - Process
Windows10UpgraderApp,SetupHosthoặcsetupprepđang chạy → bỏ qua, không chạy chồng. - Còn lại → chạy Assistant.
- Build ≥
| Process | Xuất hiện khi |
|---|---|
Windows10UpgraderApp |
Assistant đang kiểm tra, tải và chuẩn bị |
setupprep / SetupHost |
Windows Setup đang cài ngầm, trước khi máy restart |
Script của mình
# TaskScheduler_Upgrade_Win11.ps1
# Intune Win32 app - Install command (Install behavior: System)
# Copy payload + register Scheduled Task (At startup + repeat)
<#
TaskScheduler_Upgrade_Win11.ps1
Intune Win32 app - Install command (Install behavior: System):
powershell.exe -ExecutionPolicy Bypass -NoProfile -File .\TaskScheduler_Upgrade_Win11.ps1
- Copy payload to C:\ProgramData\Win11Upgrade
- Register task 'Upgrade-Win11-25H2' (At startup + every 1 hour, SYSTEM)
- Task re-runs until build >= 26200, then the run script removes the task itself
#>
# ---------- FORCE 64-BIT POWERSHELL ----------
if ([Environment]::Is64BitOperatingSystem -and -not [Environment]::Is64BitProcess) {
$p = Start-Process "$env:WINDIR\SysNative\WindowsPowerShell\v1.0\powershell.exe" `
-ArgumentList "-ExecutionPolicy Bypass -NoProfile -File `"$PSCommandPath`"" -Wait -PassThru -WindowStyle Hidden
exit $p.ExitCode
}
# ---------- DECLARATIONS ----------
$TaskName = 'Upgrade_Windows_25H2_New' # must match Upgrade_Windows_11_Assistant.ps1
$OldTaskName = 'Upgrade_Windows_25H2' # old task (one-time 11:00 AM)
$WorkDir = 'C:\Temp' # must match Upgrade_Windows_11_Assistant.ps1
$RunScript = Join-Path $WorkDir 'Upgrade_Windows_11_Assistant.ps1'
$SrcDir = if ($PSScriptRoot) { $PSScriptRoot } else { (Get-Location).Path }
$LogFile = 'C:\Temp\Win11Upgrade_TaskSetup.log'
$TargetBuild = 26200
Start-Transcript -Path $LogFile -Append -Force | Out-Null
try {
# ---------- 1. Already 25H2+ -> nothing to do ----------
[int]$Build = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuild
Write-Host "Current build: $Build"
if ($Build -ge $TargetBuild) {
Write-Host "Already 25H2 or later. Exit."
exit 0
}
# ---------- 2. Cleanup old task + old files in C:\Temp ----------
foreach ($t in @($OldTaskName, $TaskName)) {
if (Get-ScheduledTask -TaskName $t -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $t -Confirm:$false
Write-Host "Existing task '$t' deleted."
}
}
Remove-Item 'C:\Temp\Upgrade_Windows_11_Assistant.ps1', 'C:\Temp\Windows11InstallationAssistant.exe' `
-Force -ErrorAction SilentlyContinue
# ---------- 3. Create folder + lock down ACL (users read-only) ----------
New-Item -Path $WorkDir -ItemType Directory -Force | Out-Null
icacls $WorkDir /inheritance:r /grant:r 'SYSTEM:(OI)(CI)F' 'Administrators:(OI)(CI)F' 'Users:(OI)(CI)RX' | Out-Null
# ---------- 4. Copy payload (from Intune package folder) ----------
Copy-Item -Path "Upgrade_Windows_11_Assistant.ps1" -Destination "C:\Temp" -Recurse -Force -ErrorAction SilentlyContinue
Copy-Item -Path "Windows11InstallationAssistant.exe" -Destination "C:\Temp" -Recurse -Force -ErrorAction SilentlyContinue
Write-Host "Copied: Upgrade_Windows_11_Assistant.ps1"
Write-Host "Copied: Windows11InstallationAssistant.exe"
Sleep 2
# ---------- 5. Define task ----------
$Action = New-ScheduledTaskAction -Execute "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -NonInteractive -WindowStyle Hidden -File `"$RunScript`""
# Trigger 1: at startup (delay 5 min for network)
$TrgStartup = New-ScheduledTaskTrigger -AtStartup
$TrgStartup.Delay = 'PT5M'
# Trigger 2: every 1 hour, starting in 2 minutes (indefinitely)
$TrgHourly = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(2) `
-RepetitionInterval (New-TimeSpan -Hours 2)
$Settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RunOnlyIfNetworkAvailable `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Minutes 30) `
-Priority 7
$Principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
# ---------- 6. Register + start now ----------
Register-ScheduledTask -TaskName $TaskName -Action $Action -Trigger $TrgStartup, $TrgHourly `
-Settings $Settings -Principal $Principal -Force | Out-Null
Write-Host "New task '$TaskName' created."
sleep 2
Start-ScheduledTask -TaskName $TaskName
Write-Host "Task '$TaskName' started."
exit 0
}
catch {
Write-Host "ERROR: $($_.Exception.Message)"
exit 1
}
finally {
Stop-Transcript | Out-Null
}
# Upgrade_Windows_11_Assistant.ps1
# Run by Scheduled Task as SYSTEM
# Build >= 26200 -> remove task | Setup running -> skip | Else -> launch Assistant
<#
Upgrade_Windows_11_Assistant.ps1
Run by Scheduled Task 'Upgrade-Win11-25H2' as SYSTEM (At startup + every 1 hour)
- Build >= 26200 (25H2) -> remove task + cleanup, exit
- Assistant/Setup running -> skip
- Already launched this boot -> skip (waiting for restart)
- Otherwise -> launch Windows 11 Installation Assistant
#>
# ---------- DECLARATIONS ----------
[string]$TaskName = 'Upgrade_Windows_25H2_New'
[string]$WorkDir = 'C:\Temp' # must match Install.ps1
[string]$LogDir = 'C:\Temp'
[string]$LogFilePath = Join-Path $LogDir "Win11Upgrade_$($env:COMPUTERNAME).log"
[string]$AssistantLogs = Join-Path $WorkDir 'AssistantLogs'
[string]$UpdaterBinary = Join-Path $WorkDir 'Windows11InstallationAssistant.exe'
[string]$Marker = Join-Path $WorkDir 'LastLaunch.txt'
[string]$UpdaterArguments = "/QuietInstall /SkipEULA /CopyLogs `"$AssistantLogs`""
[int]$TargetBuild = 26200
[int]$MinFreeGB = 20
# ---------- FUNCTIONS ----------
function Write-Log {
[CmdletBinding()]
param ([Parameter(Mandatory)][string]$Message)
try {
$dir = [System.IO.Path]::GetDirectoryName($LogFilePath)
if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
$DateTime = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
Add-Content -Value "$DateTime - $Message" -Path $LogFilePath
}
catch { Write-Error $_.Exception.Message }
}
function CheckIfElevated {
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Log "ERROR: Insufficient permissions. Run as Administrator/SYSTEM."
return $false
}
return $true
}
# ---------- MAIN ----------
try {
[int]$Build = (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuild
$FreeGB = [math]::Round((Get-PSDrive C).Free / 1GB, 1)
Write-Log "================================================="
Write-Log "Info: Script init - User: $env:USERNAME | Machine: $env:COMPUTERNAME"
Write-Log "Info: Current build: $Build | Free C: $FreeGB GB"
if (-not (CheckIfElevated)) { Write-Log "ERROR: Will terminate!"; exit 1 }
# 1. Already 25H2 or later -> cleanup and remove task
if ($Build -ge $TargetBuild) {
Write-Log "Info: OS is up to date (build $Build). Removing task '$TaskName' and installer."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue
Remove-Item $UpdaterBinary, $Marker -Force -ErrorAction SilentlyContinue
exit 0
}
# 2. Installer binary present?
if (-not (Test-Path $UpdaterBinary)) {
Write-Log "ERROR: File $UpdaterBinary does not exist!"
exit 1
}
# 3. Upgrade already in progress -> skip
$Running = Get-Process -Name Windows10UpgraderApp, SetupHost, setupprep -ErrorAction SilentlyContinue
if ($Running) {
Write-Log "Info: Upgrade in progress ($(($Running.Name | Select-Object -Unique) -join ', ')). Skip."
exit 0
}
# 4. Already launched in this boot (maybe pending restart) -> skip
$LastBoot = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime
if (Test-Path $Marker) {
$LastLaunch = [datetime]::Parse((Get-Content $Marker -Raw).Trim())
if ($LastLaunch -gt $LastBoot) {
Write-Log "Info: Already launched at $LastLaunch in this boot (last boot $LastBoot). Skip - waiting for restart."
exit 0
}
}
# 5. Disk space warning
if ($FreeGB -lt $MinFreeGB) { Write-Log "WARNING: Only $FreeGB GB free on C: (recommended >= $MinFreeGB GB)." }
# 6. Launch Installation Assistant (no -Wait: Assistant runs on its own, task finishes quickly)
if (!(Test-Path $AssistantLogs)) { New-Item -ItemType Directory -Path $AssistantLogs -Force | Out-Null }
(Get-Date).ToString('o') | Out-File $Marker -Force
Write-Log "Info: Installation in progress - launching: $UpdaterBinary $UpdaterArguments"
Start-Process -FilePath $UpdaterBinary -ArgumentList $UpdaterArguments -WindowStyle Hidden
exit 0
}
catch {
Write-Log -Message "ERROR: $($_.Exception.Message)"
exit 1
}
# Detection script cho Intune Win32 app
# Check task scheduler
$taskName = "Upgrade_Windows_25H2_New"
try {
# Attempt to get the scheduled task
$taskExists = Get-ScheduledTask -TaskName $taskName -ErrorAction Stop
if ($taskExists) {
Write-Host "Install success."
exit 0
}
} catch {
Write-Host "Task '$taskName' does not exist or an error occurred."
exit 1
}
Theo dõi tiến độ Setup
# Process liên quan đến upgrade
Get-Process Windows10UpgraderApp, SetupHost, setupprep -ErrorAction SilentlyContinue | Select-Object Name, Id, StartTime
# Phần trăm tiến độ (chỉ có khi SetupHost đang chạy)
Get-ItemProperty 'HKLM:\SYSTEM\Setup\MoSetup\Volatile' -ErrorAction SilentlyContinue | Select-Object SetupProgress, SetupPhase
# Trạng thái Scheduled Task
Get-ScheduledTask -TaskName "Upgrade_Windows_25H2" | Get-ScheduledTaskInfo | Format-List TaskName, LastRunTime, LastTaskResult, NextRunTime
Cách 3: Dùng WSUS
WSUS phù hợp khi bạn muốn duyệt từng bản update trước khi cho máy cài. Máy client được trỏ về WSUS bằng GPO.
Cấu hình GPO mình đang dùng
| Setting | Giá trị | Ý nghĩa |
|---|---|---|
| Configure Automatic Updates | Mode 4 | Tự tải từ WSUS và cài theo lịch |
| Scheduled install day / time | Every day / 12:00 | Cài lúc 12h trưa mỗi ngày |
| Install updates for other Microsoft products | Enabled | Cập nhật cả Office và sản phẩm Microsoft khác |
| Deadline for quality / feature updates | 3 ngày, grace 2 ngày | User có 3 ngày, sau đó thêm 2 ngày grace rồi bắt buộc restart |
| Restart reminder | Mỗi 2 giờ, cảnh báo trước 30 phút | Nhắc user restart trước khi bị ép |
Kiểm tra máy client đang trỏ WSUS nào
# WSUS server và reporting server
Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -ErrorAction SilentlyContinue | Select-Object WUServer, WUStatusServer, TargetReleaseVersion, TargetReleaseVersionInfo
# UseWUServer = 1 nghĩa là máy đang dùng WSUS
Get-ItemProperty 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' -ErrorAction SilentlyContinue | Select-Object UseWUServer, AUOptions, NoAutoUpdate
# Test kết nối tới WSUS (đổi tên server và port cho đúng: 8530 HTTP / 8531 HTTPS)
Test-NetConnection wsus.contoso.local -Port 8531
# Áp lại GPO
gpupdate /force
Nếu TargetReleaseVersionInfo đang khóa ở 24H2, máy sẽ không bao giờ thấy bản 25H2. Ngoài ra, khi máy đã lên phiên bản mới, nhớ sync và approve product của phiên bản đó (ví dụ Windows 11, version 25H2) trên WSUS, nếu không máy sẽ scan ra “found 0 updates” và thiếu patch.
Cách 4: Dùng Intune Windows Update Ring
Với máy quản lý bằng Intune, mình chia thành ba policy:
- Feature update policy — chỉ định phiên bản đích, ví dụ Windows 11, version 25H2. Đây là lớp chặn chính để máy không tự nhảy lên phiên bản cao hơn mong muốn.
- Quality update policy — có thể expedite bản vá bảo mật nếu máy đang thấp hơn một mốc patch nhất định.
- Update Ring — điều khiển deferral, deadline, giờ hoạt động và hành vi restart.
Cấu hình Update Ring tham khảo
| Setting | Giá trị |
|---|---|
| Microsoft product updates | Allow |
| Windows drivers | Allow |
| Quality / Feature update deferral (days) | 1 (chỉnh theo kế hoạch rollout) |
| Feature update uninstall period | 2 ngày |
| Servicing channel | General Availability channel |
| Automatic update behavior | Auto install at maintenance time |
| Active hours | 16:00 – 10:00 (chỉnh theo kế hoạch) |
| Option to pause Windows updates | Disable |
| Use deadline settings | Allow — Feature: 1, Quality: 1, Grace period: 1 |
| Auto reboot before deadline | No |
Nên rollout theo vòng: Pilot (IT) → nhóm nhỏ người dùng → toàn công ty, mỗi vòng cách nhau vài ngày để kịp phát hiện lỗi.
Bộ lệnh kiểm tra OS và log lỗi
Đây là phần Helpdesk nên chạy trước khi escalate ticket. Mở PowerShell bằng Run as Administrator.
1. Check OS hiện tại
reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion" /v DisplayVersion
wmic os get Caption,Version,BuildNumber
# Thay thế cho wmic (wmic đang bị Microsoft loại bỏ dần trên Windows 11)
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' | Select-Object ProductName, DisplayVersion, CurrentBuild, UBR
ProductName có thể vẫn ghi “Windows 10” trên máy Windows 11. Đừng dựa vào trường này, hãy xem CurrentBuild.
2. Check trạng thái Feature Update
# Gộp log Windows Update (ETW) thành file đọc được, mặc định ra Desktop
Get-WindowsUpdateLog -LogPath C:\Temp\WindowsUpdate.log
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UpdatePolicy\PolicyState"
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings"
# Pending reboot? (True = máy đang chờ restart)
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending'
Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired'
# Dung lượng trống ổ C
Get-PSDrive C | Select-Object @{N='FreeGB';E={[math]::Round($_.Free/1GB,1)}}
3. Check Windows Update errors
Get-WinEvent -LogName Microsoft-Windows-WindowsUpdateClient/Operational -MaxEvents 50 | Select-Object TimeCreated, Id, LevelDisplayName, Message | Format-List *
# Lọc lỗi trong WindowsUpdate.log đã gộp ở bước 2
Select-String -Path C:\Temp\WindowsUpdate.log -Pattern 'FAILED|0x8024|0x8007' | Select-Object -Last 100
# Lịch sử update kèm mã lỗi dạng hex (cần PSWindowsUpdate)
Get-WUHistory -Last 100 | Where-Object Result -eq 'Failed' | Select-Object Date, Title, KB, @{N='HResult';E={'0x{0:X8}' -f ($_.HResult -band 0xFFFFFFFF)}}
4. Check lỗi Feature Upgrade (Setup)
# Log lỗi của Windows Setup - dùng nháy đơn để PowerShell không hiểu $WINDOWS là biến
Get-Content 'C:\$WINDOWS.~BT\Sources\Panther\setuperr.log' -Tail 50
# Kết luận của SetupDiag sau lần upgrade fail
Get-ItemProperty 'HKLM:\SYSTEM\Setup\SetupDiag\Results' -ErrorAction SilentlyContinue | Format-List FailureData, FailureDetails, LastSetupOperation, LastSetupPhase
# Log của Installation Assistant
Get-Content 'C:\Windows\Logs\MoSetup\BlueBox.log' -Tail 100
# Có bị rollback không?
Get-Content 'C:\$WINDOWS.~BT\Sources\Rollback\setuperr.log' -Tail 40 -ErrorAction SilentlyContinue
5. Check component store (khi patch tháng fail lặp lại)
Select-String -Path C:\Windows\Logs\CBS\CBS.log -Pattern ', Error\s' | Select-Object -Last 60
DISM /Online /Cleanup-Image /ScanHealth
DISM /Online /Cleanup-Image /RestoreHealth
sfc /scannow
6. Reset Windows Update cache (bước cuối, khi các cách trên không hiệu quả)
net stop wuauserv
del /f /s /q %windir%\SoftwareDistribution\*
net start wuauserv
RebootPending hay PendingFileRenameOperations để "lừa" Windows Update. Làm vậy chỉ che trạng thái chờ reboot, không hoàn tất thao tác servicing bên dưới và có thể làm hỏng component store.Những lỗi mình đã gặp
| Mã lỗi | Triệu chứng | Nguyên nhân gốc | Cách xử lý |
|---|---|---|---|
0x80240016 |
Cumulative update fail lặp lại | Máy đang pending reboot từ update trước | Reboot rồi cài lại; script phải dừng và trả 3010 khi có pending reboot |
0x80070005 + 0x50010 |
Upgrade dừng ở bước Start Monitoring Driver, phase Downlevel | Agent EDR bật Anti-Tamper chặn Setup tạo service driver WinSetupMon |
Thêm exclusion/interop cho C:\$WINDOWS.~BT\Sources\SetupHost.exe hoặc chuyển máy sang group tắt Anti-Tamper trong lúc upgrade |
0x800736B3 / 0x800f0905 |
Patch tháng fail mãi, DISM RestoreHealth cũng không sửa được | Component store có package bị kẹt ở trạng thái Resolved | In-place upgrade bằng Installation Assistant để dựng lại component store |
0xC1800104 → 0x800705BB |
Setup dừng giữa chừng | Setup bị ngắt: user tắt máy, hoặc script chạy chồng lên nhau | Scheduled Task chạy lại khi khởi động, kiểm tra process trước khi chạy Assistant |
0xC190020E |
Setup thoát sau khoảng 1 tiếng | Thường là thiếu dung lượng, nhưng cũng có thể che lỗi khác | Kiểm tra ổ C và phân vùng Recovery; nếu đủ thì đọc thêm Panther log |
| Upgrade rollback sau reboot | Setup đạt 100% nhưng sau reboot vẫn là 24H2 | Lần reboot không đi vào Setup, ví dụ một update khác cũng đang chờ reboot | Không để update khác chen vào trong lúc Setup chờ reboot; chạy lại |
Thêm vài “bẫy” nhỏ nhưng mất nhiều thời gian:
$WINDOWSbị PowerShell hiểu là biến. Dùng nháy đơn'C:\$WINDOWS.~BT\...'hoặc escape bằng backtick.- Script báo thành công nhưng update vẫn Failed. Phải kiểm tra cột
Resultcủa từng update. - PSWindowsUpdate báo “Installed” nhưng build không đổi. Update mới chỉ được stage, cần reboot (đôi khi hai lần) mới commit.
- Copy file exe bị lỗi “being used by another process”. Assistant cũ vẫn đang chạy. Đừng kill process, chỉ copy khi file khác hash và cho script đi tiếp.
LastTaskResult = 267014không phải lỗi, chỉ có nghĩa là task bị dừng vì máy reboot.
Kết luận
Không có một cách Windows Update nào xử lý được 100% máy. Hãy dùng Intune Update Ring hoặc WSUS làm kênh chính, giữ PSWindowsUpdate và Installation Assistant làm công cụ chữa cháy cho các máy kẹt. Quan trọng nhất là đọc đúng log trước khi sửa: check build, check pending reboot, rồi mới đọc setuperr.log hoặc event log. Bước tiếp theo bạn nên làm là lưu bộ lệnh ở trên vào tài liệu nội bộ của team Helpdesk.
Còn bạn, máy trong công ty bạn hay fail Windows Update vì lỗi nào nhất?
Bình luận