Reset-WindowsUpdate function

Discussion in 'Scripting' started by GodHand, Jun 5, 2019.

  1. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    465
    555
    10
    #1 GodHand, Jun 5, 2019
    Last edited: Sep 26, 2019
    Updated 09-26-19

    - Works with the latest Windows 10 builds and does not revert custom policy settings set in Group Policy.
    - Added the option to restart the device to clear additional system cache reserves and to apply the default service discretionary access control lists DACLs.
    - Optimized its code.

    Code:
    Function Reset-WindowsUpdate
    {
    <#
        .SYNOPSIS
            Cleans-up Windows Update containers and resets all dependent services to their default state.
      
        .DESCRIPTION
            Stops all Windows Update dependent services and cleans all directories used by Windows Update.
            Regenerates the default file structure that Windows requires for proper Windows Update Service access and authentication.
            Returns all dependent files, discretionary access control lists and permissions back to their default state.
            Restarts all Windows Update dependent services that were stopped at the beginning of the reset process.
            Allows for the system to be rebooted after the reset to clear cache storage reserves and apply the default service discretionary access control lists.
      
        .EXAMPLE
            PS C:\> Reset-WindowsUpdate
      
        .NOTES
            Does not reset any Group Policy settings applied in the registry.
            Works with the latest Windows 10 builds and does not revert custom policy settings set in Group Policy.
            It is highly recommended to restart the system after running the reset to clear additional system cache reserves and to apply the default service discretionary access control lists DACLs.
    #>
        
        [CmdletBinding(ConfirmImpact = 'High',
                       SupportsShouldProcess = $true)]
        Param ()
        
        Begin
        {
            If (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
            {
                Write-Warning "This script requires elevated access. Please relaunch Reset-WindowsUpdate with administrator permissions."; Break
            }
            $DefaultErrorAction = $ErrorActionPreference
            $DefaultProgressPreference = $ProgressPreference
            $ErrorActionPreference = 'SilentlyContinue'
            $ProgressPreference = 'SilentlyContinue'
            $Services = @('BITS', 'wuauserv', 'AppIDSvc', 'CryptSvc')
            $ResetItem = [Ordered]@{
                PathPending    = "$Env:SystemRoot\WinSxS\pending.xml"; NewPending = 'pending.xml.bak'
                PathSoftware   = "$Env:SystemRoot\SoftwareDistribution"; NewSoftware = 'SoftwareDistribution.bak'
                PathCatroot    = "$Env:SystemRoot\System32\Catroot2"; NewCatroot = 'Catroot2.bak'
                PathUpdateLog  = "$Env:SystemRoot\WindowsUpdate.log"; NewUpdateLog = 'WindowsUpdate.log.bak'
            }
        }
        Process
        {
            If ($PSCmdlet.ShouldProcess("$Env:COMPUTERNAME, Reset Windows Update."))
            {
                Clear-Host
                
                Write-Host "Stopping the BITS, wuauserv, AppIDSvc and CryptSvc Services..." -NoNewline -ForegroundColor Cyan
                ForEach ($Service In $Services)
                {
                    If ((Get-Service -Name $Service).Status -ne 'Stopped')
                    {
                        $Retries = 5
                        While ($Retries -gt 0 -and ((Get-Service -Name $Service).Status -ne 'Stopped'))
                        {
                            Stop-Service -Name $Service -Force -Confirm:$false
                            Start-Sleep -Seconds 1
                            If ((Get-Service -Name $Service).Status -eq 'Running') { Start-Sleep -Seconds 5 }
                            $Retries--
                        }
                    }
                }
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                Write-Host "Resetting Update Containers..." -NoNewline -ForegroundColor Cyan
                @("$Env:ALLUSERSPROFILE\Application Data\Microsoft\Network\Downloader\qmgr*.dat", "$Env:ALLUSERSPROFILE\Microsoft\Network\Downloader\qmgr*.dat", "$Env:SystemRoot\WinSxS\pending.xml.bak",
                    "$Env:SystemRoot\SoftwareDistribution.bak", "$Env:SystemRoot\System32\Catroot2.bak", "$Env:SystemRoot\WindowsUpdate.log.bak", (Get-ChildItem -Path $Env:SystemRoot\Logs\WindowsUpdate\* -Force)) | Remove-Item -Recurse -Force
                
                (Get-Item -Path @("$Env:SystemRoot\SoftwareDistribution", "$Env:SystemRoot\System32\Catroot2", "$Env:SystemRoot\WindowsUpdate.log") -Force).Attributes = 0x80
                
                If (Get-WindowsOptionalFeature -Online | Where-Object -Property State -EQ Pending)
                {
                    Start-Process -FilePath TAKEOWN -ArgumentList ('/F "{0}" /A' -f $ResetItem.PathPending) -WindowStyle Hidden -Wait
                    (Get-Item -Path $ResetItem.PathPending -Force).Attributes = 0x80
                    Rename-Container -Path $ResetItem.PathPending -NewName $ResetItem.NewPending
                }
                Rename-Container -Path $ResetItem.PathSoftware -NewName $ResetItem.NewSoftware
                Rename-Container -Path $ResetItem.PathCatroot -NewName $ResetItem.NewCatroot
                Rename-Container -Path $ResetItem.PathUpdateLog -NewName $ResetItem.NewUpdateLog
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                Write-Host "Resetting the wuauserv and BITS Discretionary Access Control Lists..." -NoNewline -ForegroundColor Cyan
                Start-Process -FilePath SC.EXE -ArgumentList ('SDSET wuauserv D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)') -WindowStyle Hidden -Wait
                Start-Process -FilePath SC.EXE -ArgumentList ('SDSET BITS D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;AU)(A;;CCLCSWRPWPDTLOCRRC;;;PU)') -WindowStyle Hidden -Wait
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                Write-Host "Re-registering Update Modules..." -NoNewline -ForegroundColor Cyan
                @('atl.dll', 'urlmon.dll', 'mshtml.dll', 'shdocvw.dll', 'browseui.dll', 'jscript.dll', 'vbscript.dll', 'scrrun.dll', 'msxml.dll', 'msxml3.dll', 'msxml6.dll', 'actxprxy.dll', 'softpub.dll', 'wintrust.dll',
                    'dssenh.dll', 'rsaenh.dll', 'gpkcsp.dll', 'sccbase.dll', 'slbcsp.dll', 'cryptdlg.dll', 'oleaut32.dll', 'ole32.dll', 'shell32.dll', 'initpki.dll', 'wuapi.dll', 'wuaueng.dll', 'wuaueng1.dll', 'wucltui.dll',
                    'wups.dll', 'wups2.dll', 'wuweb.dll', 'qmgr.dll', 'qmgrprxy.dll', 'wucltux.dll', 'muweb.dll', 'wuwebv.dll', 'wudriver.dll') | ForEach-Object -Process { Start-Process -FilePath REGSVR32 -ArgumentList ('/S "{0}"' -f "$Env:SystemRoot\System32\$($_)") -WindowStyle Hidden -Wait }
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                Write-Host "Removing Windows Update Client Settings..." -NoNewline -ForegroundColor Cyan
                Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" -Name AccountDomainSid -Force
                Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" -Name PingID -Force
                Remove-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate" -Name SusClientId -Force
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                Write-Host "Restarting the BITS, wuauserv, AppIDSvc and CryptSvc Services..." -NoNewline -ForegroundColor Cyan
                Start-Process -FilePath NETSH -ArgumentList ('WINSOCK RESET') -WindowStyle Hidden -Wait
                Start-Process -FilePath NETSH -ArgumentList ('WINHTTP RESET PROXY') -WindowStyle Hidden -Wait
                Clear-DnsClientCache
                Get-BitsTransfer | Remove-BitsTransfer
                Get-Service -Name BITS, wuauserv, AppIDSvc, CryptSvc, DcomLaunch, TrustedInstaller | Where-Object -Property StartType -NE Automatic | Set-Service -StartupType Automatic
                ForEach ($Service In $Services)
                {
                    If ((Get-Service -Name $Service).Status -ne 'Running')
                    {
                        $Retries = 5
                        While ($Retries -gt 0 -and ((Get-Service -Name $Service).Status -ne 'Running'))
                        {
                            Start-Service -Name $Service -Confirm:$false
                            Start-Sleep -Seconds 1
                            If ((Get-Service -Name $Service).Status -ne 'Running') { Start-Sleep -Seconds 5 }
                            $Retries--
                        }
                    }
                }
                Write-Host "[Complete]" -ForegroundColor Cyan
                
                $Wshell = New-Object -ComObject Wscript.Shell
                $Popup = $Wshell.Popup("Restart $($Env:COMPUTERNAME) to complete the reset?", 10, "Restart Device", 4 + 32)
                If ($Popup -eq 6)
                {
                    $Restart = 15
                    ForEach ($Count In (1 .. $Restart))
                    {
                        Write-Progress -Id 1 -Activity "Restarting $($Env:COMPUTERNAME) to complete the reset" -Status "Restarting in $Restart seconds, $($Restart - $Count) seconds left" -PercentComplete (($Count / $Restart) * 100)
                        Start-Sleep -Seconds 1
                    }
                    Restart-Computer
                }
            }
        }
        End
        {
            [void][Runtime.InteropServices.Marshal]::ReleaseComObject($WShell)
            $ErrorActionPreference = $DefaultErrorAction
            $ProgressPreference = $DefaultProgressPreference
        }
    }
    
    Function Rename-Container
    {
        [CmdletBinding()]
        Param
        (
            [string]$Path,
            [string]$NewName
        )
        
        If (Test-Path -Path $Path) { Rename-Item -Path $Path -NewName $NewName -Force }
    }
    You can simply copy this entire function, and paste it in PowerShell ISE and run it with Reset-WindowsUpdate, or add it to any personal PowerShell module you have so you can run it from anywhere at any time.
     
  2. pf100

    pf100 MDL Expert

    Oct 22, 2010
    1,685
    2,451
    60
    Works great. Thanks.
     
  3. GodHand

    GodHand MDL Senior Member

    Jul 15, 2016
    465
    555
    10
    Updated.