Sometimes Microsoft releases a bad update via Windows Update. It might be 3035583 that has been released multitple times to to push Windows 10 nag prompts to users. Or 3097877 that causes Outlook to crash repeatedly.
Here is a PowerShell script that uses wusa.exe to uninstall an update, then PowerShell to hide that same update. It will even check for superseded updates with the same number and hide those. The script was designed to run from the MaxFocus dashboard but can also be run standalone.
Copy the text below and save as WindowsUpdate.UninstallAndHideUpdates.ps1.
Provide as the first parameter a list of one or more numeric KB IDs to be uninstalled and hidden, separated by commas without spaces. For example:
.\WindowsUpdate.UninstallAndHideUpdates 2952664,2976978,3035583
.\WindowsUpdate.UninstallAndHideUpdates 3097877
As always, use at your own risk!
These screen shots show the script deployed as an Automatic Task to MaxFocus, with results.
And here’s the script:
<# .Synopsis Check whether an update is installed and if so, uninstall it. Check whether the same update is hidden and if not, hide it. Note that the check for pending updates can take several minutes. Copyright (c) 2019 by MCB Systems. All rights reserved. Free for personal or commercial use. May not be sold. No warranties. Use at your own risk. .Notes Name: MCB.WindowsUpdate.UninstallAndHideUpdates.ps1 Author: Mark Berry, MCB Systems Created: 10/11/2015 Last Edit: 01/26/2019 Adapted from:PowerShell: Uninstall windows hotfixes(updates)http://superuser.com/a/922921/171670 Changes: 10/12/2015 Handle multiple updates in one execution. 01/26/2019 Add $HideOnly parameter to allow skipping uninstall. #> param( # This parameter takes an array of KB Numbers. Simply add the numbers # separated by commas but no spaces, e.g. 2952664,2976978,3035583. [Parameter(Mandatory = $true, Position = 0, ValueFromPipelineByPropertyName = $true)] [String[]]$KBNumbers="", # Set this to $true to only hide update(s), not uninstall them. [Parameter(Mandatory = $false, Position = 1, ValueFromPipelineByPropertyName = $true)] [Boolean]$HideOnly=$false, # The following $LogFile parameter is required by LogigNow Remote Management # and can be omitted when running the script directly. [Parameter(Mandatory = $false, Position = 2, ValueFromPipelineByPropertyName = $true)] [String]$LogFile="" ) [Boolean]$ErrFound = $false $ComputerName = $env:COMPUTERNAME Foreach ($KBNumber in $KBNumbers) { try { 0 + $KBNumber | Out-Null } catch{ "Parameter error: '$KBNumber' is not allowed. Specify one or more numeric KBNumbers separated by commas but no spaces. Do not precede numbers with 'KB'." "" "Script execution failed" $ExitCode = 1001 # Cause script to report failure in GFI dashboard "" "Local Machine Time: " + (Get-Date -Format G) "Exit Code: " + $ExitCode Exit $ExitCode } } "Computer Name: " + $ComputerName "" if ($HideOnly -eq $false) { # We do want to uninstall update(s) "-------------------------------------------------------------------------------" '$HideOnly = $false, so proceed with Step 1: Uninstall update(s)' "-------------------------------------------------------------------------------" #--------------------------------------------------------------------------------- # Uninstall the update # Adapted from http://techibee.com/powershell/powershell-uninstall-windows-hotfixesupdates/1084 #--------------------------------------------------------------------------------- # Note that the list returned by wmic includes the "KB" prefix but wusa wants the number only $hotfixes = Get-WmiObject -ComputerName $ComputerName -Class Win32_QuickFixEngineering | select hotfixid Foreach ($KBNumber in $KBNumbers) { "Checking whether update $KBNumber needs to be uninstalled" $KBID = "KB"+$KBNumber if($hotfixes -match $KBID) { Write-host "Found update $KBNumber. Uninstalling." $UninstallString = "cmd.exe /c wusa.exe /uninstall /KB:$KBNumber /quiet /norestart" "Executing '($UninstallString)'" ([WMICLASS]"\\$ComputerName\ROOT\CIMV2:win32_process").Create($UninstallString) | out-null while (@(Get-Process wusa -computername $ComputerName -ErrorAction SilentlyContinue).Count -ne 0) { Start-Sleep 3 Write-Host "Waiting for update removal to finish ..." } Write-Host "Completed the uninstallation of update $KBNumber" } else { Write-Host "Update $KBNumber not installed so no uninstall needed" } "-----------" } # Foreach $KBNumber #--------------------------------------------------------------------------------- # Hide (block) the update # Adapted from http://superuser.com/a/922921/171670. #--------------------------------------------------------------------------------- } else { "-------------------------------------------------------------------------------" '$HideOnly = $true, so skipping Step 1: Uninstall update(s)' "-------------------------------------------------------------------------------" } "" "-------------------------------------------------------------------------------" "Step 2: Hide update(s)" "-------------------------------------------------------------------------------" "Searching for updates with IsInstalled=0, including superseded updates" "" try { #Get all pending updates in $SearchResult. $UpdateSession = New-Object -ComObject Microsoft.Update.Session $UpdateSearcher = $UpdateSession.CreateUpdateSearcher() # If the update has been re-released, it may supersede a previous update. # I had a case where it kept re-showing the new update after hiding it. # Including and (re-)hiding superseded updates will hopefully force # all updates related to this KBNumber to be hidden. $UpdateSearcher.IncludePotentiallySupersededUpdates = $true # Available search criteria: https://msdn.microsoft.com/en-us/library/windows/desktop/aa386526%28v=vs.85%29.aspx $SearchResult = $UpdateSearcher.Search("IsInstalled=0") Foreach ($KBNumber in $KBNumbers) { "Checking whether update $KBNumber needs to be hidden" [Boolean]$KBListed = $false Foreach ($Update in $SearchResult.updates) { Foreach ($KBArticleID in $Update.KBArticleIDs) { # Next line is for debugging # Write-Host "$KBArticleID, $($Update.IsHidden), $($Update.title)" if ($KBArticleID -eq $KBNumber) { $KBListed = $true if ($Update.IsHidden -eq $false) { Write-Host "Hiding update $KBNumber (UpdateID $($Update.Identity.UpdateID), deployed $($Update.LastDeploymentChangeTime.ToString('MM/dd/yyyy')))" $Update.IsHidden = $true } else { Write-Host "Update $KBNumber (UpdateID $($Update.Identity.UpdateID), deployed $($Update.LastDeploymentChangeTime.ToString('MM/dd/yyyy'))) is already hidden" } # if $Update.IsHidden } # if $KBArticleID -eq $KBNumber } # Foreach $KBArticleID } # Foreach $Update if ($KBListed -eq $false) { Write-Host "Update $KBNumber was not found searching Windows Update" } "-----------" } # Foreach $KBNumber "" $objAutoUpdateSettings = (New-Object -ComObject "Microsoft.Update.AutoUpdate").Settings $objSysInfo = New-Object -ComObject "Microsoft.Update.SystemInfo" if ($objSysInfo.RebootRequired) { "A reboot is required to complete the process" } else { "No reboot is required" } "" "Script execution succeeded" $ExitCode = 0 } catch { "" $error[0] "" "Hiding update(s) failed" $ExitCode = 1001 # Cause script to report failure in GFI dashboard } "" "Local Machine Time: " + (Get-Date -Format G) "Exit Code: " + $ExitCode Exit $ExitCode
Tnx, working great!
Pingback: Powershell: Uninstall and Hide Windows Updates | Tano's Blog