Find an MSI by the ProductName and uninstall it with Powershell

PowerShell

 

I’ve come across some interesting scenarios during the years. One of them is that some clients want to retain the auto-update capabilities of a package.

But in this case, you end up with a problem during uninstall because the Product Code of the main package, will be replaced during update, so the users who want to uninstall it via Software Center will have a little bit of a surprise with this.

However, I’ve found this Powershell script on technet: Search-Registry: Find Keys, Value Names, and Value Data in the Registry.

With this script, you can search for a certain value in the registry and get the registry key where that is present.

This is cool because, as we know, the apps that you see in “Add/Remove programs” are stored in:

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\ – For 32bit applications

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\ – For 64bit applications

 

Let’s say we have an MSI with the ProductName=”ThisIsMyMSI” and we know that this is a 32bit application. With this in mind we can change the script to:

 


function Search-Registry {
<#
.SYNOPSIS
Searches registry key names, value names, and value data (limited).

.DESCRIPTION
This function can search registry key names, value names, and value data (in a limited fashion). It outputs custom objects that contain the key and the first match type (KeyName, ValueName, or ValueData).

.EXAMPLE
Search-Registry -Path HKLM:\SYSTEM\CurrentControlSet\Services\* -SearchRegex "svchost" -ValueData

.EXAMPLE
Search-Registry -Path HKLM:\SOFTWARE\Microsoft -Recurse -ValueNameRegex "ValueName1|ValueName2" -ValueDataRegex "ValueData" -KeyNameRegex "KeyNameToFind1|KeyNameToFind2"

#>
[CmdletBinding()]
param(
[Parameter(Mandatory, Position=0, ValueFromPipelineByPropertyName)]
[Alias("PsPath")]
# Registry path to search
[string[]] $Path,
# Specifies whether or not all subkeys should also be searched
[switch] $Recurse,
[Parameter(ParameterSetName="SingleSearchString", Mandatory)]
# A regular expression that will be checked against key names, value names, and value data (depending on the specified switches)
[string] $SearchRegex,
[Parameter(ParameterSetName="SingleSearchString")]
# When the -SearchRegex parameter is used, this switch means that key names will be tested (if none of the three switches are used, keys will be tested)
[switch] $KeyName,
[Parameter(ParameterSetName="SingleSearchString")]
# When the -SearchRegex parameter is used, this switch means that the value names will be tested (if none of the three switches are used, value names will be tested)
[switch] $ValueName,
[Parameter(ParameterSetName="SingleSearchString")]
# When the -SearchRegex parameter is used, this switch means that the value data will be tested (if none of the three switches are used, value data will be tested)
[switch] $ValueData,
[Parameter(ParameterSetName="MultipleSearchStrings")]
# Specifies a regex that will be checked against key names only
[string] $KeyNameRegex,
[Parameter(ParameterSetName="MultipleSearchStrings")]
# Specifies a regex that will be checked against value names only
[string] $ValueNameRegex,
[Parameter(ParameterSetName="MultipleSearchStrings")]
# Specifies a regex that will be checked against value data only
[string] $ValueDataRegex
)

begin {
switch ($PSCmdlet.ParameterSetName) {
SingleSearchString {
$NoSwitchesSpecified = -not ($PSBoundParameters.ContainsKey("KeyName") -or $PSBoundParameters.ContainsKey("ValueName") -or $PSBoundParameters.ContainsKey("ValueData"))
if ($KeyName -or $NoSwitchesSpecified) { $KeyNameRegex = $SearchRegex }
if ($ValueName -or $NoSwitchesSpecified) { $ValueNameRegex = $SearchRegex }
if ($ValueData -or $NoSwitchesSpecified) { $ValueDataRegex = $SearchRegex }
}
MultipleSearchStrings {
# No extra work needed
}
}
}

process {
foreach ($CurrentPath in $Path) {
Get-ChildItem $CurrentPath -Recurse:$Recurse |
ForEach-Object {
$Key = $_

if ($KeyNameRegex) {
Write-Verbose ("{0}: Checking KeyNamesRegex" -f $Key.Name)

if ($Key.PSChildName -match $KeyNameRegex) {
Write-Verbose " -> Match found!"
return [PSCustomObject] @{
Key = $Key
Reason = "KeyName"
}
}
}

if ($ValueNameRegex) {
Write-Verbose ("{0}: Checking ValueNamesRegex" -f $Key.Name)

if ($Key.GetValueNames() -match $ValueNameRegex) {
Write-Verbose " -> Match found!"
return [PSCustomObject] @{
Key = $Key
Reason = "ValueName"
}
}
}

if ($ValueDataRegex) {
Write-Verbose ("{0}: Checking ValueDataRegex" -f $Key.Name)

if (($Key.GetValueNames() | % { $Key.GetValue($_) }) -match $ValueDataRegex) {
Write-Verbose " -> Match!"
return [PSCustomObject] @{
Key = $Key
Reason = "ValueData"
}
}
}
}
}
}
}

$RequestedKey = (Search-Registry -Path HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* -SearchRegex "ThisIsMyMSI" -ValueData | Select -ExpandProperty "Key")

$NewVariable = $RequestedKey -replace [Regex]::Escape("HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"), ""

If (($NewVariable -ne $null) -or ($NewVariable -ne "")){
$Execute = "msiexec.exe"
$parameters = "/x $NewVariable /qb /l*v C:\Windows\Temp\MyAppUninstall.log"

(Start-Process -FilePath $Execute -ArgumentList $parameters -Wait -Passthru).ExitCode
}

 

So what do we do here exactly? We are searching in the 32bit uninstall registry for a value of ThisIsMyMSI. The script will return the key where this is stored. Because this is an MSI, the Key is usually the ProductCode, so we can then call msiexec /x Key.

 

You could say that we can use the UninstallString, but in many cases the UninstallString is something like msiexec /I something, because it offers you a GUI to select what you want to do with the package.

 

Of course you don’t have to modify the actual script, and save it as Functions.ps1 and just add it in another script and call the Search-Registry function, this is just an example. It’s up to you.

Leave a comment

Your email address will not be published. Required fields are marked *

12 − ten =