Uninstall and Hide Windows Updates

Mark Berry November 11, 2015

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.

UninstallHide1

UninstallHide2

UninstallHide3

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) 2015 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.AutoUpdate.UninstallAndHideUpdates.ps1
    Author:     Mark Berry, MCB Systems
    Created:    10/11/2015
    Last Edit:  10/12/2015

    Adapted from: 
    http://techibee.com/powershell/powershell-uninstall-windows-hotfixesupdates/1084
    http://superuser.com/a/922921/171670

    Changes:
    10/12/2015 Handle multiple updates in one execution.
#>

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="",

  # The following $LogFile parameter is required by LogigNow Remote Management
  # and can be omitted when running the script directly.
  [Parameter(Mandatory = $false,
                    Position = 1,
                    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
""
"-------------------------------------------------------------------------------"
"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.
#---------------------------------------------------------------------------------
""
"-------------------------------------------------------------------------------"
"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


2 Comments

  1. Rick   |  June 12, 2016 at 3:08 am

    Tnx, working great!

  2. Powershell: Uninstall and Hide Windows Updates | Tano's Blog   |  September 12, 2016 at 5:04 am

    […] Uninstall and Hide Windows Updates […]

Leave a Reply





*