Private/Invoke-WindowsUpdateReset.ps1
|
function Invoke-WindowsUpdateReset { <# .SYNOPSIS Performs the low-level Windows Update component reset operations on the local machine. .DESCRIPTION Script-block body extracted from Reset-WindowsUpdateComponent so it can be unit-tested independently of Invoke-RemoteOrLocal. Called locally as-is; passed as a serialised script block for remote execution via Invoke-RemoteOrLocal. .PARAMETER DoNetworkReset When $true, also resets the Winsock catalog and WinHTTP proxy (requires a reboot). #> [CmdletBinding()] param( [bool]$DoNetworkReset = $false ) $failures = [System.Collections.Generic.List[string]]::new() $notes = [System.Collections.Generic.List[string]]::new() # ---------------------------------------------------------------- # Step 1 — Stop services: BITS, wuauserv, appidsvc, cryptsvc # ---------------------------------------------------------------- $servicesToStop = @('BITS', 'wuauserv', 'appidsvc', 'cryptsvc') $stoppedServices = [System.Collections.Generic.List[string]]::new() foreach ($svcName in $servicesToStop) { try { $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue if ($svc -and $svc.Status -ne 'Stopped') { Stop-Service -Name $svcName -Force -ErrorAction Stop $stoppedServices.Add($svcName) } } catch { $failures.Add("Stop service '$svcName': $_") } } # Allow services to fully stop before filesystem operations Start-Sleep -Seconds 2 # ---------------------------------------------------------------- # Step 2 — Delete qmgr*.dat files # ---------------------------------------------------------------- $qmgrFolder = Join-Path -Path $env:ALLUSERSPROFILE ` -ChildPath 'Application Data\Microsoft\Network\Downloader' $qmgrCount = 0 if (Test-Path -Path $qmgrFolder -PathType Container) { $qmgrFiles = Get-ChildItem -Path $qmgrFolder -Filter 'qmgr*.dat' ` -Force -ErrorAction SilentlyContinue foreach ($f in $qmgrFiles) { try { Remove-Item -LiteralPath $f.FullName -Force -ErrorAction Stop $qmgrCount++ } catch { $failures.Add("Delete '$($f.FullName)': $_") } } } # ---------------------------------------------------------------- # Step 3 — Backup-rename SoftwareDistribution and Catroot2 # ---------------------------------------------------------------- $sdPath = Join-Path -Path $env:SystemRoot -ChildPath 'SoftwareDistribution' $sdBak = "${sdPath}.bak" $cr2Path = Join-Path -Path $env:SystemRoot -ChildPath 'System32\Catroot2' $cr2Bak = "${cr2Path}.bak" $sdBackupResult = '' $cr2BackupResult = '' foreach ($pair in @( [pscustomobject]@{ Src = $sdPath; Bak = $sdBak; ResultVar = 'sd' } [pscustomobject]@{ Src = $cr2Path; Bak = $cr2Bak; ResultVar = 'cr2' } )) { if (Test-Path -Path $pair.Src -PathType Container) { if (Test-Path -Path $pair.Bak) { try { Remove-Item -LiteralPath $pair.Bak -Recurse -Force -ErrorAction Stop } catch { $suffix = Get-Date -Format 'yyyyMMddHHmmss' $pair.Bak = "$($pair.Bak).$suffix" } } try { Rename-Item -LiteralPath $pair.Src -NewName ([System.IO.Path]::GetFileName($pair.Bak)) ` -ErrorAction Stop if ($pair.ResultVar -eq 'sd') { $sdBackupResult = $pair.Bak } else { $cr2BackupResult = $pair.Bak } } catch { $failures.Add("Backup '$($pair.Src)': $_") if ($pair.ResultVar -eq 'sd') { $sdBackupResult = "Skipped: $_" } else { $cr2BackupResult = "Skipped: $_" } } } else { if ($pair.ResultVar -eq 'sd') { $sdBackupResult = 'Skipped: folder not present' } else { $cr2BackupResult = 'Skipped: folder not present' } } } # ---------------------------------------------------------------- # Step 4 — Remove WindowsUpdate.log # ---------------------------------------------------------------- $wuLog = Join-Path -Path $env:SystemRoot -ChildPath 'WindowsUpdate.log' if (Test-Path -Path $wuLog -PathType Leaf) { try { Remove-Item -LiteralPath $wuLog -Force -ErrorAction Stop } catch { $failures.Add("Remove WindowsUpdate.log: $_") } } # ---------------------------------------------------------------- # Step 5 — Reset service SDDL for BITS and wuauserv # ---------------------------------------------------------------- $scPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\sc.exe' $defaultSddl = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)' foreach ($svcTarget in @('bits', 'wuauserv')) { $scResult = Invoke-NativeCommand -FilePath $scPath ` -ArgumentList @('sdset', $svcTarget, $defaultSddl) if ($scResult.ExitCode -ne 0) { $failures.Add("sc.exe sdset $svcTarget exited $($scResult.ExitCode): $($scResult.Output)") } } # ---------------------------------------------------------------- # Step 6 — Reregister Windows Update DLLs # ---------------------------------------------------------------- $regsvr32Path = Join-Path -Path $env:SystemRoot -ChildPath 'System32\regsvr32.exe' $dllNames = @( 'atl', 'urlmon', 'mshtml', 'shdocvw', 'browseui', 'jscript', 'vbscript', 'scrrun', 'msxml', 'msxml3', 'msxml6', 'actxprxy', 'softpub', 'wintrust', 'dssenh', 'rsaenh', 'gpkcsp', 'sccbase', 'slbcsp', 'cryptdlg', 'oleaut32', 'ole32', 'shell32', 'initpki', 'wuapi', 'wuaueng', 'wuaueng1', 'wucltui', 'wups', 'wups2', 'wuweb', 'qmgr', 'qmgrprxy', 'wucltux', 'muweb', 'wuwebv' ) $dllsRegistered = 0 $dllsFailed = 0 foreach ($dll in $dllNames) { $dllPath = Join-Path -Path $env:SystemRoot -ChildPath "System32\${dll}.dll" if (-not (Test-Path -LiteralPath $dllPath -PathType Leaf)) { $dllsFailed++ $failures.Add("DLL not found (non-fatal): $dllPath") continue } $regResult = Invoke-NativeCommand -FilePath $regsvr32Path ` -ArgumentList @('/s', $dllPath) if ($regResult.ExitCode -eq 0) { $dllsRegistered++ } else { $dllsFailed++ $failures.Add("regsvr32 failed for '${dll}.dll' (exit $($regResult.ExitCode)): $($regResult.Output)") } } # ---------------------------------------------------------------- # Steps 7 & 8 — Network reset (optional, gated by DoNetworkReset) # ---------------------------------------------------------------- $networkResetPerformed = $false $rebootRequired = $false $netshPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\netsh.exe' if ($DoNetworkReset) { $winsockResult = Invoke-NativeCommand -FilePath $netshPath ` -ArgumentList @('winsock', 'reset') if ($winsockResult.ExitCode -eq 0) { $networkResetPerformed = $true $rebootRequired = $true } else { $failures.Add("netsh winsock reset exited $($winsockResult.ExitCode): $($winsockResult.Output)") } $winhttpResult = Invoke-NativeCommand -FilePath $netshPath ` -ArgumentList @('winhttp', 'reset', 'proxy') if ($winhttpResult.ExitCode -ne 0) { $failures.Add("netsh winhttp reset proxy exited $($winhttpResult.ExitCode): $($winhttpResult.Output)") } } # ---------------------------------------------------------------- # Step 9 — Restart services: cryptsvc, appidsvc, wuauserv, BITS # ---------------------------------------------------------------- $servicesToStart = @('cryptsvc', 'appidsvc', 'wuauserv', 'BITS') $startedServices = [System.Collections.Generic.List[string]]::new() foreach ($svcName in $servicesToStart) { try { $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue if ($null -ne $svc) { Start-Service -Name $svcName -ErrorAction Stop $startedServices.Add($svcName) } } catch { $failures.Add("Start service '$svcName': $_") } } # ---------------------------------------------------------------- # Step 10 — Trigger detection: wuauclt, fall back to usoclient # ---------------------------------------------------------------- $wuaucltPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\wuauclt.exe' $usoclientPath = Join-Path -Path $env:SystemRoot -ChildPath 'System32\usoclient.exe' if (Test-Path -LiteralPath $wuaucltPath -PathType Leaf) { $wuaucltResult = Invoke-NativeCommand -FilePath $wuaucltPath ` -ArgumentList @('/resetauthorization', '/detectnow') if ($wuaucltResult.ExitCode -ne 0) { $notes.Add("wuauclt exited $($wuaucltResult.ExitCode); attempting usoclient fallback.") if (Test-Path -LiteralPath $usoclientPath -PathType Leaf) { $usoResult = Invoke-NativeCommand -FilePath $usoclientPath ` -ArgumentList @('StartScan') if ($usoResult.ExitCode -ne 0) { $failures.Add("usoclient StartScan exited $($usoResult.ExitCode): $($usoResult.Output)") } else { $notes.Add('Detection triggered via usoclient StartScan.') } } else { $notes.Add('usoclient.exe not found; detection trigger skipped.') } } } else { $notes.Add('wuauclt.exe not found on this OS build; falling back to usoclient StartScan.') if (Test-Path -LiteralPath $usoclientPath -PathType Leaf) { $usoResult = Invoke-NativeCommand -FilePath $usoclientPath ` -ArgumentList @('StartScan') if ($usoResult.ExitCode -ne 0) { $failures.Add("usoclient StartScan exited $($usoResult.ExitCode): $($usoResult.Output)") } else { $notes.Add('Detection triggered via usoclient StartScan.') } } else { $notes.Add('Neither wuauclt.exe nor usoclient.exe found; detection trigger skipped.') } } # ---------------------------------------------------------------- # Determine overall status # ---------------------------------------------------------------- $status = if ($failures.Count -eq 0) { 'Succeeded' } else { 'PartialSuccess' } return [PSCustomObject]@{ Status = $status ServicesStopped = @($stoppedServices) ServicesStarted = @($startedServices) SoftwareDistributionBackup = $sdBackupResult Catroot2Backup = $cr2BackupResult QmgrFilesDeleted = $qmgrCount DllsReregistered = $dllsRegistered DllsFailed = $dllsFailed NetworkResetPerformed = $networkResetPerformed RebootRequired = $rebootRequired Failures = @($failures) Notes = @($notes) } } |