129 lines
4.8 KiB
PowerShell
129 lines
4.8 KiB
PowerShell
#Requires -Version 2.0
|
|
param(
|
|
[Parameter(Mandatory=$true)]
|
|
[array]$KB
|
|
)
|
|
|
|
## Set the client ID
|
|
$ClientID = "NinjaRMM Community Hotfix Uninstaller"
|
|
|
|
## Create a new Update Session and set the ClientApplicationID
|
|
$UpdateSession = New-Object -ComObject Microsoft.Update.Session
|
|
$UpdateSession.ClientApplicationID = $ClientID
|
|
|
|
## Create the a searcher
|
|
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
|
|
|
|
## Search all historical updates
|
|
## This method seems to list all updates that are not recent.
|
|
## Not sure yet what qualifies as recent.
|
|
$HistoricalResults = $UpdateSearcher.QueryHistory(0,$UpdateSearcher.GetTotalHistoryCount())
|
|
|
|
## Search all current/pending updates
|
|
## This method lists all updates that were either recently installed or are pending installation,
|
|
## as well as recent updates that failed. Again, not sure what qualifies as recent.
|
|
$CurrentResults = $UpdateSearcher.Search("IsInstalled=1")
|
|
|
|
## Make an empty array to put our own update objects into
|
|
$Updates = @()
|
|
|
|
## Get all successfully installed historical updates and add them to $Updates
|
|
Foreach ( $update in $HistoricalResults ) {
|
|
|
|
## An Operation code of 1 means the update is installed
|
|
## A result code of 2 means the installation was successful and without errors
|
|
If ( ($update.Operation -eq 1) -and ($update.ResultCode -eq 2) ) {
|
|
|
|
## Create a new object to hold some properties about the update
|
|
$u = New-Object -Type PSObject -Property @{
|
|
'Title' = $update.Title
|
|
'Description' = $update.Description
|
|
'SupportUrl' = $update.SupportUrl
|
|
'UpdateId' = $update.UpdateIdentity.UpdateID
|
|
'RevisionNumber' = $update.UpdateIdentity.RevisionNumber
|
|
}
|
|
|
|
## Add our custom object to $Updates.
|
|
$Updates += $u
|
|
}
|
|
}
|
|
|
|
## Get all installed current updates
|
|
For ( $i=13; $i -le ($CurrentResults.Updates.Count - 1); $i++ ) {
|
|
$update = $CurrentResults.Updates.Item($i)
|
|
|
|
## This method of searching has a unique property called IsUninstallable.
|
|
## I'm not sure exactly how this is used by Windows, but we'll check for it here
|
|
## as it may make the uninstall process less error-prone.
|
|
If ( $update.IsUninstallable -eq 1 ) {
|
|
|
|
## Create a new object to hold some properties about the update
|
|
$u = New-Object -Type PSObject -Property @{
|
|
'Title' = $update.Title
|
|
'Description' = $update.Description
|
|
'SupportUrl' = $update.SupportUrl
|
|
'UpdateId' = $update.Identity.UpdateID
|
|
'RevisionNumber' = $update.Identity.RevisionNumber
|
|
}
|
|
|
|
## Add our custom object to $Updates.
|
|
$Updates += $u
|
|
}
|
|
}
|
|
|
|
## Uninstall by KB number using WUSA.exe
|
|
Foreach ( $kbTxt in $KB ) {
|
|
|
|
## Make sure the KB number we're using is in uppercase so it matches correctly
|
|
$kbTxt = $kbTxt.ToUpper()
|
|
|
|
## This will remove the "KB" prefix that may have been added.
|
|
## When we invoke wusa.exe we need to specify the KB number without "KB" in front.
|
|
$kbNum = $kbTxt -replace 'KB', ''
|
|
|
|
## Uninstall the hotfix
|
|
## Note: This exact method to uninstall a hotfix doesn't work in Windows 10
|
|
## If you omit the /quiet switch, it will *say* it uninstalled the update, but it never gets uninstalled.
|
|
Foreach ( $update in $Updates ) {
|
|
|
|
If ( $update.Title -like "*${kbTxt}*" ) {
|
|
|
|
## Reverse the comment on the following 2 lines during testing.
|
|
#Start-Process "wusa.exe" -ArgumentList "/uninstall /kb:${kbNum} /quiet /norestart" -Wait }
|
|
Write-Output $update.Title
|
|
}
|
|
}
|
|
}
|
|
|
|
## Query DISM for any packages it has installed to see if our hotfix is there
|
|
## This method is probably more for Windows 10 endpoints,
|
|
## but we'll target Vista and up just to cover the bases.
|
|
If ( ([System.Environment]::OSVersion.Version).Major -ge 6 ) {
|
|
|
|
Foreach ( $kbTxt in $KB ) {
|
|
|
|
## Make sure the KB number we're using is in uppercase so it matches correctly
|
|
$kbTxt = $kbTxt.ToUpper()
|
|
|
|
## We're using DISM.exe directly to maintain compatibility with older PowerShell versions
|
|
## that don't have the newer DISM cmdlets.
|
|
$DISMPackages = ( DISM.exe /Online /Get-Packages | findstr "${kbTxt}" )
|
|
|
|
## Cleanup the packages, if any are found.
|
|
If ( $DISMPackages ) { $DISMPackages.Replace('Package Identity : ','').Trim() }
|
|
|
|
## Uninstall each package.
|
|
Foreach ( $package in $DISMPackages ) {
|
|
|
|
## Reverse the comment on the following 2 lines during testing.
|
|
#Start-Process "DISM.exe" -ArgumentList "/Online /Remove-Package /PackageName:${package} /quiet /norestart" -Wait
|
|
Write-Host $package
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
## TODO:
|
|
## Search for all manually installed hotfixes/updates
|
|
## I guess a good place to start is MSI, MSU, and CAB packages installed by a user.
|
|
## (possibly by searching for MSI property strings of installed packages...??) |