-
Notifications
You must be signed in to change notification settings - Fork 903
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
1 changed file
with
168 additions
and
136 deletions.
There are no files selected for viewing
304 changes: 168 additions & 136 deletions
304
src/chocolatey.resources/helpers/functions/Get-UninstallRegistryKey.ps1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,136 +1,168 @@ | ||
# Copyright © 2011 - Present RealDimensions Software, LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
function Get-UninstallRegistryKey { | ||
<# | ||
.SYNOPSIS | ||
Retrieve registry key(s) for system-installed applications from an | ||
exact or wildcard search. | ||
.DESCRIPTION | ||
This function will attempt to retrieve a matching registry key for an | ||
already installed application, usually to be used with a | ||
chocolateyUninstall.ps1 automation script. | ||
The function also prevents `Get-ItemProperty` from failing when | ||
handling wrongly encoded registry keys. | ||
.NOTES | ||
Available in 0.9.10+. If you need to maintain compatibility with pre | ||
0.9.10, please add the following to your nuspec: | ||
~~~xml | ||
<dependencies> | ||
<dependency id="chocolatey-uninstall.extension" /> | ||
</dependencies> | ||
~~~ | ||
.INPUTS | ||
String | ||
.OUTPUTS | ||
This function searches registry objects and returns PSCustomObject of | ||
the matched key's properties. | ||
Retrieve properties with dot notation, for example: | ||
`$key.UninstallString` | ||
.PARAMETER SoftwareName | ||
Part or all of the Display Name as you see it in Programs and Features. | ||
It should be enough to be unique. | ||
If the display name contains a version number, such as "Launchy 2.5", | ||
it is recommended you use a fuzzy search `"Launchy*"` (the wildcard `*`) | ||
as if the version is upgraded or autoupgraded, suddenly the uninstall | ||
script will stop working and it may not be clear as to what went wrong | ||
at first. | ||
.EXAMPLE | ||
> | ||
# Software name in Programs and Features is "Gpg4Win (2.3.0)" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "Gpg4win*" | ||
$key.DisplayName | ||
.EXAMPLE | ||
> | ||
# Software name is "Launchy 2.5" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "Launchy*" | ||
$key.UninstallString | ||
.EXAMPLE | ||
> | ||
# Software name is "Mozilla Firefox" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "Mozilla Firefox" | ||
$key.UninstallString | ||
.LINK | ||
Install-ChocolateyPackage | ||
.LINK | ||
Install-ChocolateyInstallPackage | ||
.LINK | ||
Uninstall-ChocolateyPackage | ||
#> | ||
[CmdletBinding()] | ||
param( | ||
[parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] | ||
[string] $softwareName, | ||
[parameter(ValueFromRemainingArguments = $true)][Object[]] $ignoredArguments | ||
) | ||
|
||
Write-FunctionCallLogMessage -Invocation $MyInvocation -Parameters $PSBoundParameters | ||
|
||
if ($softwareName -eq $null -or $softwareName -eq '') { | ||
throw "$SoftwareName cannot be empty for Get-UninstallRegistryKey" | ||
} | ||
|
||
$ErrorActionPreference = 'Stop' | ||
$local_key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
$machine_key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
$machine_key6432 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
|
||
Write-Verbose "Retrieving all uninstall registry keys" | ||
[array]$keys = Get-ChildItem -Path @($machine_key6432, $machine_key, $local_key) ` | ||
-ErrorAction SilentlyContinue | ||
|
||
Write-Debug "Error handling check: Get-ItemProperty will fail if a registry key is written incorrectly." | ||
Write-Debug "If such a key is found, loop up to 10 times to try to bypass all badKeys" | ||
[int]$maxAttempts = 10 | ||
for ([int]$attempt = 1; $attempt -le $maxAttempts; $attempt++) { | ||
[bool]$success = $FALSE | ||
|
||
try { | ||
[array]$foundKey = Get-ItemProperty -Path $keys.PsPath ` | ||
-ErrorAction SilentlyContinue ` | ||
| Where-Object { $_.DisplayName -like $softwareName } | ||
$success = $TRUE | ||
} catch { | ||
Write-Debug "Found bad key." | ||
foreach ($key in $keys){ try { Get-ItemProperty $key.PsPath > $null } catch { $badKey = $key.PsPath }} | ||
Write-Verbose "Skipping bad key: $($key.PsPath)" | ||
[array]$keys = Get-ChildItem -Path @($machine_key6432, $machine_key, $local_key) ` | ||
-ErrorAction SilentlyContinue ` | ||
| Where-Object { $badKey -NotContains $_.PsPath } | ||
} | ||
|
||
if ($success) { break; } | ||
} | ||
|
||
return $foundKey | ||
} | ||
|
||
Set-Alias Get-InstallRegistryKey Get-UninstallRegistryKey | ||
# Copyright © 2011 - Present RealDimensions Software, LLC | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
function Get-UninstallRegistryKey { | ||
<# | ||
.SYNOPSIS | ||
Retrieve registry key(s) for system-installed applications from an | ||
exact or wildcard search. | ||
.DESCRIPTION | ||
This function will attempt to retrieve a matching registry key for an | ||
already installed application, usually to be used with a | ||
chocolateyUninstall.ps1 automation script. | ||
The function also prevents `Get-ItemProperty` from failing when | ||
handling wrongly encoded registry keys. | ||
.NOTES | ||
Available in 0.9.10+. If you need to maintain compatibility with pre | ||
0.9.10, please add the following to your nuspec (check for minimum | ||
version): | ||
~~~xml | ||
<dependencies> | ||
<dependency id="chocolatey-core.extension" version="1.1.0" /> | ||
</dependencies> | ||
~~~ | ||
.INPUTS | ||
String | ||
.OUTPUTS | ||
This function searches registry objects and returns an array | ||
of PSCustomObject with the matched key's properties. | ||
Retrieve properties with dot notation, for example: | ||
`$key.UninstallString` | ||
.PARAMETER SoftwareName | ||
Part or all of the Display Name as you see it in Programs and Features. | ||
It should be enough to be unique. | ||
The syntax follows the rules of the PowerShell `-like` operator, so the | ||
`*` character is interpreted as a wildcard, which matches any (zero or | ||
more) characters. | ||
If the display name contains a version number, such as "Launchy (2.5)", | ||
it is recommended you use a fuzzy search `"Launchy (*)"` (the wildcard | ||
`*`) so if Launchy auto-updates or is updated outside of Chocolatey, the | ||
uninstall script will not fail. | ||
Take care not to abuse fuzzy/glob pattern searches. Be conscious of | ||
programs that may have shared or common root words to prevent | ||
overmatching. For example, "SketchUp*" would match two keys with | ||
software names "SketchUp 2016" and "SketchUp Viewer" that are different | ||
programs released by the same company. | ||
.PARAMETER IgnoredArguments | ||
Allows splatting with arguments that do not apply. Do not use directly. | ||
.EXAMPLE | ||
> | ||
# Version match: Software name is "Gpg4Win (2.3.0)" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "Gpg4win (*)" | ||
$key.UninstallString | ||
.EXAMPLE | ||
> | ||
# Fuzzy match: Software name is "Launchy 2.5" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "Launchy*" | ||
$key.UninstallString | ||
.EXAMPLE | ||
> | ||
# Exact match: Software name in Programs and Features is "VLC media player" | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "VLC media player" | ||
$key.UninstallString | ||
.EXAMPLE | ||
> | ||
# Version match: Software name is "SketchUp 2016" | ||
# Note that the similar software name "SketchUp Viewer" would not be matched. | ||
[array]$key = Get-UninstallRegistryKey -SoftwareName "SketchUp [0-9]*" | ||
$key.UninstallString | ||
.LINK | ||
Install-ChocolateyPackage | ||
.LINK | ||
Install-ChocolateyInstallPackage | ||
.LINK | ||
Uninstall-ChocolateyPackage | ||
#> | ||
[CmdletBinding()] | ||
param( | ||
[parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)] | ||
[string] $softwareName, | ||
[parameter(ValueFromRemainingArguments = $true)][Object[]] $ignoredArguments | ||
) | ||
|
||
Write-FunctionCallLogMessage -Invocation $MyInvocation -Parameters $PSBoundParameters | ||
|
||
if ($softwareName -eq $null -or $softwareName -eq '') { | ||
throw "$SoftwareName cannot be empty for Get-UninstallRegistryKey" | ||
} | ||
|
||
$ErrorActionPreference = 'Stop' | ||
$local_key = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
$machine_key = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
$machine_key6432 = 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' | ||
|
||
Write-Verbose "Retrieving all uninstall registry keys" | ||
[array]$keys = Get-ChildItem -Path @($machine_key6432, $machine_key, $local_key) -ErrorAction SilentlyContinue | ||
Write-Debug "Registry uninstall keys on system: $($keys.Count)" | ||
|
||
Write-Debug "Error handling check: `'Get-ItemProperty`' fails if a registry key is encoded incorrectly." | ||
[int]$maxAttempts = $keys.Count | ||
for ([int]$attempt = 1; $attempt -le $maxAttempts; $attempt++) { | ||
[bool]$success = $false | ||
|
||
$keyPaths = $keys | Select-Object -ExpandProperty PSPath | ||
try { | ||
[array]$foundKey = Get-ItemProperty -Path $keyPaths -ErrorAction Stop | ? { $_.DisplayName -like $softwareName } | ||
$success = $true | ||
} catch { | ||
Write-Debug "Found bad key." | ||
foreach ($key in $keys){ | ||
try { | ||
Get-ItemProperty $key.PsPath > $null | ||
} catch { | ||
$badKey = $key.PsPath | ||
} | ||
} | ||
Write-Verbose "Skipping bad key: $badKey" | ||
[array]$keys = $keys | ? { $badKey -NotContains $_.PsPath } | ||
} | ||
|
||
if ($success) { break; } | ||
|
||
if ($attempt -ge 10) { | ||
Write-Warning "Found 10 or more bad registry keys. Run command again with `'--verbose --debug`' for more info." | ||
Write-Debug "Each key searched should correspond to an installed program. It is very unlikely to have more than a few programs with incorrectly encoded keys, if any at all. This may be indicative of one or more corrupted registry branches." | ||
} | ||
} | ||
|
||
if ($foundKey -eq $null -or $foundkey.Count -eq 0) { | ||
Write-Warning "No registry key found based on '$softwareName'" | ||
} | ||
|
||
Write-Debug "Found $($foundKey.Count) uninstall registry key(s) with SoftwareName:`'$SoftwareName`'"; | ||
|
||
return $foundKey | ||
} | ||
|
||
Set-Alias Get-InstallRegistryKey Get-UninstallRegistryKey |