I am trying to get the uninstall paths of a set of applications and uninstall them. So far i an get the list of uninstall paths. but i am struggling to actually uninstall the programs.
My code so far is.
$app = @("msi1", "msi2", "msi3", "msi4")
$Regpath = @(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
)
foreach ($apps in $app){
$UninstallPath = Get-ItemProperty $Regpath | where {$_.displayname -like "*$apps*"} | Select-Object -Property UninstallString
$UninstallPath.UninstallString
#Invoke-Expression UninstallPath.UninstallString
#start-process "msiexec.exe" -arg "X $UnistallPath /qb" - wait
}
this will return the following results:
MsiExec.exe /X{F17025FB-0401-47C9-9E34-267FBC619AAE}
MsiExec.exe /X{20DC0ED0-EA01-44AB-A922-BD9932AC5F2C}
MsiExec.exe /X{29376A2B-2D9A-43DB-A28D-EF5C02722AD9}
MsiExec.exe /X{18C9B6D0-DCDC-44D8-9294-0ED24B080F0C}
Im struggling to find away to execute these uninstall paths and actually uninstall the MSIs.
I have tried to use Invoke-Expression $UninstallPath.UninstallString but it just displays the windows installer and gives me the option for msiexec.
I have also tried to use start-process "msiexec.exe" -arg "X $UnistallPath /qb" - wait however this gives the same issue.
Note:
PackageManagementmodule'sGet-PackageandUninstall-Packagecmdlets.Uninstall-Packagesupports uninstalling MSI-installed software (-ProviderName msi), but seemingly not "programs" (-ProviderName Programs); I'm unclear on whether uninstallation of Windows Update packages (-ProviderName msu) is supported.Note, however, that these providers are only (directly) available in Windows PowerShell[1] - by contrast, PowerShell (Core) as of v7.3.3 lacks these package providers altogether, and it's unclear (to me) whether they will ever be added.
Problem:
The uninstallation command lines stored in the
UninstallString/QuietUninstallStringregistry values[2] are designed for no-shell / from-cmd.exeinvocations.[3]They therefore can fail from PowerShell if you pass them to
Invoke-Expression, namely if they contain unquoted characters that have no special meaning outside shells / tocmd.exe, but are metacharacters in PowerShell, which applies to{and}in your case.Solutions:
You have two options:
(a) Simply pass the uninstallation string as-is to
cmd /cmsiexec.exedirectly from PowerShell or directly fromcmd.exe- calling viacmd /cresults in synchronous execution ofmsiexec, which is desirable.(b) Split the uninstallation string into executable and argument list, which allows you to call the command via
Start-Process, which can give you more control over the invocation.-Waitswitch to ensure that the installation completes before your script continues.Note:
The following commands assume that the uninstall string is contained in variable
$UninstallString(the equivalent of$UninstallPath.UninstallStringin your code):Situationally appending options to the command line isn't as straightforward as just appending, say,
' /quiet /norestart', because that won't work if the command line is merely an unquoted executable path without spaces, e.g.,C:\Program Files\WinRAR\uninstall.exe- see this answer for a solution.Implementation of (a):
The automatic
$LASTEXITCODEvariable can then be queried for the command line's exit code.Implementation of (b):
You could also add
-NoNewWindowto prevent console program-based uninstallation command lines from running in a new console window, but note that the only way to capture their stdout / stderr output viaStart-Processis to redirect them to files, using the-RedirectStandardOutput/-RedirectStandardErrorparameters.Edition-specific / future improvements:
The
Start-Process-based method is cumbersome for two reasons:You cannot pass whole command lines and must instead specify the executable and arguments separately.
In Windows PowerShell (whose latest and final version is 5.1) you cannot pass an empty string or array to the (positionally implied)
-ArgumentListparameter (hence the need for two separate calls above).[1] If you don't mind the extra overhead, you can (temporarily) import the Windows PowerShell
PackageManagementmodule even from PowerShell (Core), using the Windows PowerShell compatibility feature:Import-Module -UseWindowsPowerShell PackageManagement.[2] As shown in your question, they are stored in the
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Uninstall(64-bit applications) andHKEY_LOCAL_MACHINE\Wow6432Node \Software\Microsoft\Windows\CurrentVersion\Uninstall(32-bit applications) registry keys, and possibly also - for user-specific installations - in theirHKEY_CURRENT_USERcounterparts; given thatHKEY_CURRENT_USERonly refers to the current user, looking for other users' user-specific installations would require more work.[3] Hypothetically, it is possible to author valid no-shell command lines that break when called from
cmd.exe(e.g.foo.exe a&borfoo.exe "A \"B & C\ D"), but that rarely, if ever, happens in practice.