1<#
2.SYNOPSIS
3 Resets the windows update component.
4.DESCRIPTION
5 Detects and resets a corrupted windows update component.
6 Detection is done by testing the eventlog with the specified parameters.
7 Reset is performed by resetting the windows update component to its initial state.
8 The specified eventlog is backed up and cleared in order not to trigger the detection again before the reset step.
9 The backup of the specified eventlog is stored in 'SystemRoot\Temp' folder.
10 Defaults are configured for the ESENT '623' error.
11.PARAMETER Action
12 Specifies the action to be performed. Available actions are: ('DetectAndReset', 'Detect', 'Reset','ResetStandalone').
13 'DetectAndReset' - Performs detection and then performs a reset if necessary.
14 'Detect' - Performs detection and returns the result.
15 'Reset' - Performs a reset and flushes the specified EventLog.
16 'ResetStandalone' - Performs a reset only.
17.PARAMETER LogName
18 Specifies the LogName to search. Default is: 'Application'
19.PARAMETER Source
20 Specifies the Source to search. Default is: 'ESENT'
21.PARAMETER EventID
22 Specifies the EventID to search. Default is: '623'
23.PARAMETER EntryType
24 Specifies the Entry Type to search. Available options are: ('Information','Warning','Error'). Default is: 'Error'.
25.PARAMETER LimitDays
26 Specifies the number of days from the current date to limit the search to. Default is: 3.
27.PARAMETER Threshold
28 Specified the numbers of events after which this functions returns $true. Default is: 3.
29.EXAMPLE
30 Reset-WindowsUpdate.ps1 -Action 'Detect' -LogName 'Application' -Source 'ESENT' -EventID '623' -EntryType 'Error' -LimitDays 3 -Threshold 3
31.INPUTS
32 None.
33.OUTPUTS
34 System.String. This script returns Compliant, Non-Compliant, Reset or Error Message
35.NOTES
36 Created by Ioan Popovici
37 This script can be called directly.
38.LINK
39 https://MEMZ.one/Reset-WindowsUpdate
40.LINK
41 https://MEMZ.one/Reset-WindowsUpdate-CHANGELOG
42.LINK
43 https://MEMZ.one/Reset-WindowsUpdate-GIT
44.LINK
45 https://MEM.Zone/ISSUES
46.COMPONENT
47 Windows Update
48.FUNCTIONALITY
49 Reset Windows Update component
50#>
51
52## Set script requirements
53#Requires -Version 2.0
54
55##*=============================================
56##* VARIABLE DECLARATION
57##*=============================================
58#region VariableDeclaration
59
60## Get script parameters
61Param (
62 [Parameter(Mandatory=$true,Position=0)]
63 [ValidateNotNullorEmpty()]
64 [ValidateSet("DetectAndReset","Detect","Reset","ResetStandalone")]
65 [string]$Action,
66 [Parameter(Mandatory=$false,Position=1)]
67 [ValidateNotNullorEmpty()]
68 [string]$LogName = "Application",
69 [Parameter(Mandatory=$false,Position=2)]
70 [ValidateNotNullorEmpty()]
71 [string]$Source = "ESENT",
72 [Parameter(Mandatory=$false,Position=3)]
73 [ValidateNotNullorEmpty()]
74 [string]$EventID = "623",
75 [Parameter(Mandatory=$false,Position=4)]
76 [ValidateNotNullorEmpty()]
77 [string]$EntryType = "Error",
78 [Parameter(Mandatory=$false,Position=5)]
79 [ValidateNotNullorEmpty()]
80 [int]$LimitDays = 3,
81 [Parameter(Mandatory=$false,Position=6)]
82 [ValidateNotNullorEmpty()]
83 [int]$Threshold = 3
84)
85
86#endregion
87##*=============================================
88##* END VARIABLE DECLARATION
89##*=============================================
90
91##*=============================================
92##* FUNCTION LISTINGS
93##*=============================================
94#region FunctionListings
95
96#region Function Test-EventLogCompliance
97Function Test-EventLogCompliance {
98<#
99.SYNOPSIS
100 Tests the EventLog compliance for specific events.
101.DESCRIPTION
102 Tests the EventLog compliance by getting events and returning a Non-Compliant statement after a specified threshold is reached.
103.PARAMETER LogName
104 Specifies the LogName to search.
105.PARAMETER Source
106 Specifies the Source to search.
107.PARAMETER EventID
108 Specifies the EventID to search.
109.PARAMETER EntryType
110 Specifies the Entry Type to search. Available options are: ('Information','Warning','Error'). Default is: 'Error'.
111.PARAMETER LimitDays
112 Specifies the number of days from the current date to limit the search to.
113 Default is: 1.
114.PARAMETER Threshold
115 Specifed the numbers of events after which this functions returns $true.
116.EXAMPLE
117 Test-EventLogCompliance -LogName 'Application' -Source 'ESENT' -EventID '623' -EntryType 'Error' -LimitDays 3 -Threshold 3
118.INPUTS
119 None.
120.OUTPUTS
121 System.Boolean.
122.NOTES
123 This function can typically be called directly.
124.LINK
125 https://MEM.Zone
126.LINK
127 https://MEM.Zone/GIT
128.COMPONENT
129 WindowsUpdate
130.FUNCTIONALITY
131 Test
132#>
133 [CmdletBinding()]
134 Param (
135 [Parameter(Mandatory=$true,Position=0)]
136 [ValidateNotNullorEmpty()]
137 [string]$LogName,
138 [Parameter(Mandatory=$true,Position=1)]
139 [ValidateNotNullorEmpty()]
140 [string]$Source,
141 [Parameter(Mandatory=$true,Position=2)]
142 [ValidateNotNullorEmpty()]
143 [string]$EventID,
144 [Parameter(Mandatory=$false,Position=3)]
145 [ValidateSet('Information','Warning','Error')]
146 [string]$EntryType = 'Error',
147 [Parameter(Mandatory=$false,Position=4)]
148 [ValidateNotNullorEmpty()]
149 [int]$LimitDays = 1,
150 [Parameter(Mandatory=$true,Position=5)]
151 [ValidateNotNullorEmpty()]
152 [int]$Threshold
153 )
154
155 Try {
156
157 ## Set day limit by subtracting number of days from the current date
158 $After = $((Get-Date).AddDays( - $LimitDays ))
159
160 ## Get events and test threshold
161 $Events = Get-EventLog -ComputerName $env:COMPUTERNAME -LogName $LogName -Source $Source -EntryType $EntryType -After $After -ErrorAction 'Stop' | Where-Object { $_.EventID -eq $EventID }
162
163 If ($Events.Count -ge $Threshold) {
164 $Compliance = 'Non-Compliant'
165 }
166 Else {
167 $Compliance = 'Compliant'
168 }
169 }
170 Catch {
171
172 ## Set result as 'Compliant' if no matches are found
173 If ($($_.Exception.Message) -match 'No matches found') {
174 $Compliance = 'Compliant'
175 }
176 Else {
177 $Compliance = "Eventlog [$EventLog] compliance test error. $($_.Exception.Message)"
178 }
179 }
180 Finally {
181
182 ## Return Compliance result
183 Write-Output -InputObject $Compliance
184 }
185}
186#endregion
187
188#region Function Backup-EventLog
189Function Backup-EventLog {
190<#
191.SYNOPSIS
192 Backs-up an Event Log.
193.DESCRIPTION
194 Backs-up an Event Log using the BackUpEventLog Cim method.
195.PARAMETER LogName
196 Specifies the event log to backup.
197.PARAMETER BackupPath
198 Specifies the Backup Path. Default is: '$env:SystemRoot\Temp'.
199.PARAMETER BackupName
200 Specifies the Backup name. Default is: 'yyyy-MM-dd_HH-mm-ss_$env:ComputerName_$LogName'.
201.EXAMPLE
202 Backup-EventLog -LogName 'Application' -BackupPath 'C:\MEMZone' -BackupName '1980-09-09_10-10-00_MEMZoneBlog_Application'
203.INPUTS
204 System.String.
205.OUTPUTS
206 None. This function has no outputs.
207.NOTES
208 This function can typically be called directly.
209.LINK
210 https://MEM.Zone
211.LINK
212 https://MEM.Zone/GIT
213.COMPONENT
214 EventLog
215.FUNCTIONALITY
216 Backup
217#>
218 [CmdletBinding()]
219 Param (
220 [Parameter(Mandatory=$true,Position=0)]
221 [ValidateNotNullorEmpty()]
222 [string]$LogName,
223 [Parameter(Mandatory=$false,Position=1)]
224 [ValidateNotNullorEmpty()]
225 [string]$BackupPath,
226 [Parameter(Mandatory=$false,Position=2)]
227 [ValidateNotNullorEmpty()]
228 [string]$BackupName
229 )
230
231 Begin {
232
233 ## Setting variables
234 [int]$PowerShellVersion = $PSVersionTable.PSVersion.Major
235 [datetime]$Date = $(Get-Date -f 'yyyy-MM-dd_HH-mm-ss')
236 # Setting optional parameters
237 If (-not $BackupPath) {
238 $BackupPath = $(Join-Path -Path $env:SystemRoot -ChildPath '\Temp')
239 }
240 If (-not $BackupFileName) {
241 [string]$BackUpFileName = "{0}_{1}_{2}.evtx" -f $Date, $env:COMPUTERNAME, $LogName
242 }
243 # Setting backup arguments
244 [hashtable]$BackupArguments = @{ ArchiveFileName = (Join-Path -Path $BackupPath -ChildPath $BackUpFileName) }
245 }
246 Process {
247 Try {
248
249 If ($PowerShellVersion -eq 2) {
250
251 ## Get event log
252 $EventLog = Get-WmiObject -Class 'Win32_NtEventLogFile' -Filter "LogFileName = '$LogName'" -ErrorAction 'SilentlyContinue'
253
254 If (-not $EventLog) { Throw "EventLog [$LogName] not found." }
255
256 ## Backup event log
257 $BackUp = $EventLog | Invoke-WmiMethod -Name 'BackupEventLog' -ArgumentList $BackupArguments -ErrorAction 'SilentlyContinue'
258
259 If ($BackUp.ReturnValue -ne 0) { Throw "Backup returned error [$($BackUp.ReturnValue)]." }
260 }
261 ElseIf ($PowerShellVersion -ge 3) {
262
263 ## Get event log
264 $EventLog = Get-CimInstance -ClassName 'Win32_NtEventLogFile' -Filter "LogFileName = '$LogName'" -ErrorAction 'SilentlyContinue'
265
266 If (-not $EventLog) { Throw 'EventLog not found.' }
267
268 ## Backup event log
269 $BackUp = $EventLog | Invoke-CimMethod -Name 'BackupEventLog' -Arguments $BackupArguments -ErrorAction 'SilentlyContinue'
270
271 If ($BackUp.ReturnValue -ne 0) { Throw "Backup returned error [$($BackUp.ReturnValue)]." }
272 }
273 Else {
274 Throw "PowerShell version [$PowerShellVersion] not supported."
275 }
276 }
277 Catch {
278 Write-Output -InputObject "Backup EventLog [$LogName] error. $_"
279 }
280 }
281 End {
282 }
283}
284#endregion
285
286#region Function Reset-WindowsUpdate
287Function Reset-WindowsUpdate {
288<#
289.SYNOPSIS
290 Resets the windows update component.
291.DESCRIPTION
292 Resets the windows update component to its initial state.
293.EXAMPLE
294 Reset-WindowsUpdate
295.INPUTS
296 None.
297.OUTPUTS
298 System.String. This script returns Compliant, Non-Compliant, Reset or Error Message
299.NOTES
300 This function can typically be called directly.
301.LINK
302 https://MEM.Zone
303.LINK
304 https://MEM.Zone/GIT
305.COMPONENT
306 WindowsUpdate
307.FUNCTIONALITY
308 Repair
309#>
310
311 Try {
312
313 ## Variable declaration
314 # Setting Paths
315 [string]$PathRegsvr = (Join-Path -Path $env:SystemRoot -ChildPath '\System32\Regsvr32.exe')
316 [string]$PathDataStore = (Join-Path -Path $env:SystemRoot -ChildPath '\SoftwareDistribution\DataStore')
317 [string]$PathCatroot2 = (Join-Path -Path $env:SystemRoot -ChildPath '\System32\Catroot2')
318 [string]$PathQMGRFiles = (Join-Path -Path $env:AllUsersProfile -ChildPath '\Application Data\Microsoft\Network\Downloader\qmgr*.dat')
319 # Setting dll names
320 [string[]]$Dlls = @(
321 'atl', 'urlmon', 'mshtml', 'shdocvw', 'browseui', 'jscript', 'vbscript','scrrun', 'msxml3', 'msxml6', 'actxprxy', 'softpub'
322 , 'wintrust', 'dssenh', 'rsaenh', 'cryptdlg', 'oleaut32', 'ole32', 'shell32', 'wuapi', 'wuaueng', 'wups', 'wups2', 'qmgr'
323 )
324 # Setting security descriptors
325 [string]$SecurityDescriptors = 'D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)'
326 # Setting registry keys
327 [string]$UpdateRegistryKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate'
328 [string[]]$UpdateRegistryProperties = @('AccountDomainSid', 'PingID', 'SusClientId')
329
330 ## Stop the windows update service
331 Stop-Service -Name 'wuauserv', 'BITS', 'cryptsvc' -ErrorAction 'SilentlyContinue'
332
333 ## Wait for the windows update service to stop
334 # Setting Loop index to 12 (one minute)
335 [int]$Loop = 1
336 While ($StatusWuaService -ne 'Stopped') {
337
338 # Waiting 10 seconds
339 Start-Sleep -Seconds 10
340
341 # Get windows update service status
342 [string]$StatusWuaService = (Get-Service -Name 'wuauserv').Status
343
344 # Try to kill process if service has not stopped within 4 minutes
345 If ($Loop -eq 24) {
346
347 # Use powershell legacy
348 If ($PowerShellVersion -eq 2) {
349 # Get update service PID
350 [string]$PID = Get-WmiObject -Class 'Win32_Service' -Filter "Name = 'wuauserv'" | Select-Object -ExpandProperty 'ProcessId' -ErrorAction 'SilentlyContinue'
351 # Kill process if PID is found
352 If ($PID -and $PID -ne '0') {
353 Start-Process -FilePath 'taskkill.exe' -ArgumentList "/f /pid $PID" -Wait -ErrorAction 'SilentlyContinue'
354 }
355 }
356
357 # Use current powershell
358 ElseIf ($PowerShellVersion -ge 3) {
359 # Get update service PID
360 [string]$PID = Get-CimInstance -ClassName 'Win32_Service' -Filter "Name = 'wuauserv'" | Select-Object -ExpandProperty 'ProcessId' -ErrorAction 'SilentlyContinue'
361 # Kill process if PID is found
362 If ($PID -and $PID -ne '0') {
363 Stop-Process -ID $PID -Force -ErrorAction 'SilentlyContinue'
364 }
365 }
366 }
367
368 # Throw error if service has not stopped within 5 minutes
369 If ($Loop -ge 30) {
370 Throw 'Failed to stop WuaService within 5 minutes.'
371 }
372
373 # Incrementing loop index
374 $Loop++
375 }
376
377 ## Remove QMGR Data file
378 Remove-Item -Path $PathQMGRFiles -Force -ErrorAction 'Stop' | Out-Null
379
380 ## Remove catalog root folder
381 Remove-Item -Path $PathCatroot2 -Recurse -Force -ErrorAction 'Stop' | Out-Null
382
383 ## Remove the Windows update DataStore
384 Remove-Item -Path $PathDataStore -Recurse -Force -ErrorAction 'Stop' | Out-Null
385
386 ## Remove WSUS client registry entries
387 ForEach ($PropertyName in $UpdateRegistryProperties) {
388 Remove-ItemProperty -Path $UpdateRegistryKey -Name $PropertyName -ErrorAction 'SilentlyContinue'
389 }
390
391 ## Reset BITS and wuaserv services security descriptors to default
392 Start-Process -FilePath 'sc.exe' -ArgumentList "sdset BITS $SecurityDescriptors"
393 Start-Process -FilePath 'sc.exe' -ArgumentList "sdset wuauserv $SecurityDescriptors"
394
395 ## Re-registr dlls
396 Set-Location -Path $(Join-Path -Path $env:systemroot -ChildPath 'System32')
397 ForEach ($Dll in $Dlls) {
398 $DllName = $Dll + '.dll'
399 Start-Process -FilePath $PathRegsvr -ArgumentList "/s $DllName" -Wait -ErrorAction 'SilentlyContinue'
400 }
401
402 ## Clear the BITS queue
403 Get-BitsTransfer -AllUsers | Remove-BitsTransfer
404
405 ## Start services
406 Start-Service -Name 'wuauserv', 'BITS', 'cryptsvc' -ErrorAction 'SilentlyContinue'
407
408 ## Start ConfigMgr Client software update scan
409 $null = Invoke-CimMethod -Namespace 'Root\ccm' -ClassName 'SMS_CLIENT' -MethodName 'TriggerSchedule' -Arguments @{SScheduleID = '{00000000-0000-0000-0000-000000000108}'} -ErrorAction 'SilentlyContinue'
410
411 ## Set result to 'Reset'
412 [string]$RepairWuDatastore = 'Reset'
413 }
414 Catch {
415 [string]$RepairWuDatastore = "Windows Update reset failed [$($_.Exception.Message)]."
416 }
417 Finally {
418
419 ## Return result
420 Write-Output -InputObject $RepairWuDatastore
421 }
422}
423#endregion
424
425#endregion
426##*=============================================
427##* END FUNCTION LISTINGS
428##*=============================================
429
430##*=============================================
431##* SCRIPT BODY
432##*=============================================
433#region ScriptBody
434
435Switch ($Action) {
436 'DetectAndReset' {
437
438 ## Get machine compliance
439 [string]$ESENTError623 = Test-EventLogCompliance -LogName $LogName -Source $Source -EventID $EventID -EntryType $EntryType -LimitDays $LimitDays -Threshold $Threshold
440
441 ## Start processing if compliance test returns 'Non-Compliant'
442 If ($ESENTError623 -eq 'Non-Compliant') {
443
444 # Backup EventLog
445 $null = Backup-EventLog -LogName $LogName -ErrorAction 'SilentlyContinue'
446
447 Try {
448
449 # Clear EventLog
450 $null = Clear-EventLog -LogName $LogName -ErrorAction 'Stop'
451
452 # Reset Windows update component if clear eventlog is successful
453 Reset-WindowsUpdate
454 }
455 Catch {
456 Write-Output -InputObject "No reset possible. Clear EventLog [$LogName] error. $($_.Exception.Message)"
457 }
458 }
459 Else {
460 Write-Output -InputObject $ESENTError623
461 }
462 }
463 'Detect' {
464
465 ## Get machine compliance and return it
466 [string]$ESENTError623 = Test-EventLogCompliance -LogName $LogName -Source $Source -EventID $EventID -EntryType $EntryType -LimitDays $LimitDays -Threshold $Threshold
467 Write-Output -InputObject $ESENTError623
468 }
469 'Reset' {
470
471 ## Backup EventLog
472 $null = Backup-EventLog -LogName $LogName -ErrorAction 'SilentlyContinue'
473
474 Try {
475
476 ## Clear EventLog
477 $null = Clear-EventLog -LogName $LogName -ErrorAction 'Stop'
478
479 ## Reset windows update component if clear eventlog is successful
480 Reset-WindowsUpdate
481 }
482 Catch {
483 Write-Output -InputObject "No reset possible. Clear EventLog [$LogName] error. $($_.Exception.Message)"
484 }
485 }
486 'ResetStandalone' {
487
488 ## Reset windows update component
489 Reset-WindowsUpdate -ErrorAction 'SilentlyContinue'
490 }
491}
492
493#endregion
494##*=============================================
495##* END SCRIPT BODY
496##*=============================================