Are you saying that apps are not found?
First, you should use wildcard pattern here instead of regex, for example:
Where-Object { $_.DisplayName -like "*$app*"}
Secondly immediately after that instead of if($RegApp)
you should verify the result is singular:
$Count = ($RegApp | Measure-Object).Count
if ($Count -gt 1)
{
# This will happen when multiple registry entries are found,
# in which case a user must be precise on what to remove
Write-Error "Search pattern is too loose, please update search string for '$App'"
}
elseif ($Count -eq 1)
{
# Uninstall code goes here
}
else
{
# This error or case may also mean that search string is too strict!
# not necessarily that app is not found
Write-Error "Unable to find app '$App', search string might be too strict"
}
Error message is important because “not found” or “search invalid” are 2 very different things,
you should inform the user how to fix the problem.
In this case $RegApp
from $AppstoRemove
might be too strict, and this may be the reason why it is not found.
Having this construct in place, next step is to implement multiple registry locations to handle more cases:
Case, 64 bit system programs:
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
Case, 32 bit system programs:
"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
Case, programs installed in user profile:
"$HCU\Software\Microsoft\Windows\CurrentVersion\Uninstall"
Where $HCU
is registry root key of the user, aka. “Current user”.
To learn this key you must not loop trough registry HKCU hive, instead your function should implement -User
parameter so that specific user key is then looked in HKCU hive.
This is important because multiple users may have same program installed each on it’s own.
for example your function or script starts with:
param(
[Parameter()]
$User
)
When you do this you should combine result into a variable, ex:
$Keys = @(
"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
"$HCU\Software\Microsoft\Windows\CurrentVersion\Uninstall"
)
And then handle all keys:
foreach ($App in $AppstoRemove) {
foreach ($Key in $Keys)
{
$RegApp = Get-Itemproperty $Key |
Select-Object DisplayName, DisplayVersion, UninstallString |
Where-Object { $_.DisplayName -like"*$app*"}
# the rest of code...
}
}
Keep in mind that in double foreach loop sample above you must not show errors until all keys have been processed to avoid spam or misinformation.
How do you learn registry key that is specifyed by -User
parameter?
param(
[Parameter()]
$User
)
$NTAccount = New-Object -TypeName System.Security.Principal.NTAccount($User)
$HKU = $NTAccount.Translate([System.Security.Principal.SecurityIdentifier]).ToString()
You have the idea, now rewrite your function according to all this.
EDIT:
There is another issue with your code:
if($RegApp.Uninstallstring -match '^msiexec') {
$UninstallString = "$($RegApp.UninstallString -replace '/I', '/X' ) /v /qn /norestart"
} else {
$UninstallString = $RegApp.UninstallString
}
This is wrong because if uninstall string does not start with “msiexec” you later populate $exe
variable anyway, which cant bring any good beyond odd error messages.
Because uninstall string may be path only or something completely unusual, in which case you need to handle that case by parsing the string and implementing alternative methods to uninstall program.