How can I uninstall an application using PowerShell?
Asked Answered
L

15

152

Is there a simple way to hook into the standard 'Add or Remove Programs' functionality using PowerShell to uninstall an existing application? Or to check if the application is installed?

Luht answered 22/9, 2008 at 7:4 Comment(0)
I
177
$app = Get-WmiObject -Class Win32_Product | Where-Object { 
    $_.Name -match "Software Name" 
}

$app.Uninstall()

Edit: Rob found another way to do it with the Filter parameter:

$app = Get-WmiObject -Class Win32_Product `
                     -Filter "Name = 'Software Name'"
Iago answered 22/9, 2008 at 7:17 Comment(11)
This is pretty much it, I would say that it may be better to use IdentifyingNumber rather than the name, just in case.Bickart
After a bit of research you can also use the -filter clause of Get-WmiObject: $app = Get-WmiObject -Class Win32_Product -filter "select * from Win32_Product WHERE name = 'Software Name'"Luht
Note that looking at WMI will only work for products that were installed via an MSI.Outmoded
This WMI class takes FOREVER to enumerate. I suggest Jeff that you update your code to include Rob's tip.Bulbul
This WMI class is only available after being installed. Its not default part of XP or Windows 2003. Its default on Vista/2008Modernism
If you get a "generic failure", this hotfix support.microsoft.com/kb/970553 may helpNs
using measure-command, I've found that using get-wmiobject -query takes about 14.5 seconds; using get-wmiobject -filter takes 14.3 seconds; using get-wmiobject | where {} takes 16.5 seconds. These are all pretty hair-pulling, not to mention that there's barely any appreciable difference between them. Is there any other way to speed this up?Drynurse
the -Filter-Parameter doesn't work for full queries. You should choos the -Query-Parameter for full queries.Writein
You can dramatically speed up your experience by assigning the result (without the filter) to a powershell variable, then filter the contents of the saved variable all you want. That way you only pay the slow execution time once.Kiss
(gwmi Win32_Product | ? Name -eq "Software").uninstall() A little code golf.Prototrophic
Dont use this! its slow and returns incomplete data! query the registry instead. $Apps = @() $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall*" # 32 Bit $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall*" # 64 BitImprovement
B
61

EDIT: Over the years this answer has gotten quite a few upvotes. I would like to add some comments. I have not used PowerShell since, but I remember observing some issues:

  1. If there are more matches than 1 for the below script, it does not work and you must append the PowerShell filter that limits results to 1. I believe it's -First 1 but I'm not sure. Feel free to edit.
  2. If the application is not installed by MSI it does not work. The reason it was written as below is because it modifies the MSI to uninstall without intervention, which is not always the default case when using the native uninstall string.

Using the WMI object takes forever. This is very fast if you just know the name of the program you want to uninstall.

$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString

if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /qb" -Wait}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /qb" -Wait}
Baskett answered 28/8, 2014 at 10:26 Comment(11)
Thanks for this! I'm trying to use this with -like "appNam*" since the version is in the name and it changes, but it doesn't seem to find the program. Any ideas?Intensity
Look up the -like function for powershell, find out which filter to use an how to make it match your string correctly. Just use the shell to test, and once you get it right replace the -match :)Baskett
This is gold. Personally, I remove the 'b' from the '/qb' so you don't have to see any dialogs.Christianna
Much much quicker :-)Nymph
Works great. A further simplification would be to use the PSChildName which is the MSI guid instead of the string manipulation of UninstallString.Performative
ugh... so many aliases... What is foreach { gp $_.PSPath } for?Ytterbia
@Ytterbia It's Get-ItemProperty and then the PSPath for each looped ($_) variable under the \Uninstall registration folder key.Baskett
@Intensity Use Get-WmiObject -Class Win32_Product -Filter "Name LIKE '%python%'"Graft
I turned this into a .ps1 script with prompt and a "what I'm about to uninstall" info. gist.github.com/chrisfcarroll/e38b9ffcc52fa9d4eb9ab73b13915f5aBodine
Thanks for this, is much quicker! I replaced the /qb with /qn as I didn't want any UI. Just adding now in-case anyone else comes across it.Eng
I wish this had more up votes. Thank you!!Shirting
T
34

To fix up the second method in Jeff Hillman's post, you could either do a:

$app = Get-WmiObject 
            -Query "SELECT * FROM Win32_Product WHERE Name = 'Software Name'"

Or

$app = Get-WmiObject -Class Win32_Product `
                     -Filter "Name = 'Software Name'"
Telegenic answered 29/1, 2009 at 7:2 Comment(2)
Just a heads-up... I found that using the "-Query" instead of the "-Filter" option did not return a WmiObject, so it didn't have a "uninstall" method.Quorum
This solution can't get the installed programs via exe, but msi. Because of that it works just programs which have installed via microsoft installer(msi)Gluten
C
14

One line of code:

get-package *notepad* |% { & $_.Meta.Attributes["UninstallString"]}
Cookout answered 23/2, 2019 at 8:54 Comment(2)
Returns CategoryInfo: InvalidOperation: (:) [], RuntimeException FullyQualifiedErrorId: BadExpressionPolak
Strage, this was working beforeCookout
E
9
function Uninstall-App {
    Write-Output "Uninstalling $($args[0])"
    foreach($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
        $dname = $obj.GetValue("DisplayName")
        if ($dname -contains $args[0]) {
            $uninstString = $obj.GetValue("UninstallString")
            foreach ($line in $uninstString) {
                $found = $line -match '(\{.+\}).*'
                If ($found) {
                    $appid = $matches[1]
                    Write-Output $appid
                    start-process "msiexec.exe" -arg "/X $appid /qb" -Wait
                }
            }
        }
    }
}

Call it this way:

Uninstall-App "Autodesk Revit DB Link 2019"
Ejection answered 10/10, 2018 at 23:13 Comment(0)
R
7

To add a little to this post, I needed to be able to remove software from multiple Servers. I used Jeff's answer to lead me to this:

First I got a list of servers, I used an AD query, but you can provide the array of computer names however you want:

$computers = @("computer1", "computer2", "computer3")

Then I looped through them, adding the -computer parameter to the gwmi query:

foreach($server in $computers){
    $app = Get-WmiObject -Class Win32_Product -computer $server | Where-Object {
        $_.IdentifyingNumber -match "5A5F312145AE-0252130-432C34-9D89-1"
    }
    $app.Uninstall()
}

I used the IdentifyingNumber property to match against instead of name, just to be sure I was uninstalling the correct application.

Rachellrachelle answered 21/5, 2013 at 20:37 Comment(1)
Simply lovely this solutionNosy
T
7

I found out that Win32_Product class is not recommended because it triggers repairs and is not query optimized. Source

I found this post from Sitaram Pamarthi with a script to uninstall if you know the app guid. He also supplies another script to search for apps really fast here.

Use like this: .\uninstall.ps1 -GUID {C9E7751E-88ED-36CF-B610-71A1D262E906}

[cmdletbinding()]            

param (            

 [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
 [string]$ComputerName = $env:computername,
 [parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
 [string]$AppGUID
)            

 try {
  $returnval = ([WMICLASS]"\\$computerName\ROOT\CIMV2:win32_process").Create("msiexec `/x$AppGUID `/norestart `/qn")
 } catch {
  write-error "Failed to trigger the uninstallation. Review the error message"
  $_
  exit
 }
 switch ($($returnval.returnvalue)){
  0 { "Uninstallation command triggered successfully" }
  2 { "You don't have sufficient permissions to trigger the command on $Computer" }
  3 { "You don't have sufficient permissions to trigger the command on $Computer" }
  8 { "An unknown error has occurred" }
  9 { "Path Not Found" }
  9 { "Invalid Parameter"}
 }
Tman answered 22/8, 2014 at 14:22 Comment(0)
G
4

Here is the PowerShell script using msiexec:

echo "Getting product code"
$ProductCode = Get-WmiObject win32_product -Filter "Name='Name of my Software in Add Remove Program Window'" | Select-Object -Expand IdentifyingNumber
echo "removing Product"
# Out-Null argument is just for keeping the power shell command window waiting for msiexec command to finish else it moves to execute the next echo command
& msiexec /x $ProductCode | Out-Null
echo "uninstallation finished"
Ghibelline answered 13/7, 2017 at 12:32 Comment(1)
I combined this approach with the following flags, for some reason this works better than the other approaches for me.Exemption
H
3

I will make my own little contribution. I needed to remove a list of packages from the same computer. This is the script I came up with.

$packages = @("package1", "package2", "package3")
foreach($package in $packages){
  $app = Get-WmiObject -Class Win32_Product | Where-Object {
    $_.Name -match "$package"
  }
  $app.Uninstall()
}

I hope this proves to be useful.

Note that I owe David Stetler the credit for this script since it is based on his.

Horseflesh answered 3/12, 2013 at 4:9 Comment(0)
S
2

Based on Jeff Hillman's answer:

Here's a function you can just add to your profile.ps1 or define in current PowerShell session:

# Uninstall a Windows program
function uninstall($programName)
{
    $app = Get-WmiObject -Class Win32_Product -Filter ("Name = '" + $programName + "'")
    if($app -ne $null)
    {
        $app.Uninstall()
    }
    else {
        echo ("Could not find program '" + $programName + "'")
    }
}

Let's say you wanted to uninstall Notepad++. Just type this into PowerShell:

> uninstall("notepad++")

Just be aware that Get-WmiObject can take some time, so be patient!

Scientism answered 2/11, 2016 at 23:8 Comment(0)
M
1

On more recent windows systems, you can use the following to uninstall msi installed software. You can also check $pkg.ProviderName -EQ "msi" if you like.

$pkg = get-package *name*
$prodCode = "{" + $pkg.TagId + "}"
msiexec.exe /X $prodCode /passive
Malign answered 28/1, 2023 at 14:57 Comment(0)
D
1

With Windows PowerShell 5.1 you can call: Uninstall-Package -Name <appname>. See https://learn.microsoft.com/en-us/powershell/module/packagemanagement/uninstall-package?view=powershellget-2.x

For Windows 11 and Windows 10 you can use winget tool in powershell to mange applications, so to uninstall an application call: winget uninstall <appname>. For more see https://learn.microsoft.com/en-us/windows/package-manager/winget/.

Dit answered 6/7, 2023 at 14:23 Comment(0)
M
0

Use:

function remove-HSsoftware{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,
ValuefromPipeline = $true,
HelpMessage="IdentifyingNumber can be retrieved with `"get-wmiobject -class win32_product`"")]
[ValidatePattern('{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}}')]
[string[]]$ids,
[parameter(Mandatory=$false,
            ValuefromPipeline=$true,
            ValueFromPipelineByPropertyName=$true,
            HelpMessage="Computer name or IP adress to query via WMI")]
[Alias('hostname,CN,computername')]
[string[]]$computers
)
begin {}
process{
    if($computers -eq $null){
    $computers = Get-ADComputer -Filter * | Select dnshostname |%{$_.dnshostname}
    }
    foreach($computer in $computers){
        foreach($id in $ids){
            write-host "Trying to uninstall sofware with ID ", "$id", "from computer ", "$computer"
            $app = Get-WmiObject -class Win32_Product -Computername "$computer" -Filter "IdentifyingNumber = '$id'"
            $app | Remove-WmiObject

        }
    }
}
end{}}
 remove-hssoftware -ids "{8C299CF3-E529-414E-AKD8-68C23BA4CBE8}","{5A9C53A5-FF48-497D-AB86-1F6418B569B9}","{62092246-CFA2-4452-BEDB-62AC4BCE6C26}"

It's not fully tested, but it ran under PowerShell 4.

I've run the PS1 file as it is seen here. Letting it retrieve all the Systems from the AD and trying to uninstall multiple applications on all systems.

I've used the IdentifyingNumber to search for the Software cause of David Stetlers input.

Not tested:

  1. Not adding ids to the call of the function in the script, instead starting the script with parameter IDs
  2. Calling the script with more then 1 computer name not automatically retrieved from the function
  3. Retrieving data from the pipe
  4. Using IP addresses to connect to the system

What it does not:

  1. It doesn't give any information if the software actually was found on any given system.
  2. It does not give any information about failure or success of the deinstallation.

I wasn't able to use uninstall(). Trying that I got an error telling me that calling a method for an expression that has a value of NULL is not possible. Instead I used Remove-WmiObject, which seems to accomplish the same.

CAUTION: Without a computer name given it removes the software from ALL systems in the Active Directory.

Macintosh answered 12/3, 2014 at 13:45 Comment(0)
G
0

For Most of my programs the scripts in this Post did the job. But I had to face a legacy program that I couldn't remove using msiexec.exe or Win32_Product class. (from some reason I got exit 0 but the program was still there)

My solution was to use Win32_Process class:

with the help from nickdnk this command is to get the uninstall exe file path:

64bit:

[array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString

32bit:

 [array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString

you will have to clean the the result string:

$uninstallPath = $unInstallPathReg[0].UninstallString
$uninstallPath = $uninstallPath -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstallPath = $uninstallPath .Trim()

now when you have the relevant program uninstall exe file path you can use this command:

$uninstallResult = (Get-WMIObject -List -Verbose | Where-Object {$_.Name -eq "Win32_Process"}).InvokeMethod("Create","$unInstallPath")

$uninstallResult - will have the exit code. 0 is success

the above commands can also run remotely - I did it using invoke command but I believe that adding the argument -computername can work

Gemmiparous answered 26/6, 2017 at 7:48 Comment(0)
M
0

For msi installs, "uninstall-package whatever" works fine. For non-msi installs (Programs provider), it takes more string parsing. This should also take into account if the uninstall exe is in a path with spaces and is double quoted. Install-package works with msi's as well.

$uninstall = get-package whatever | % { $_.metadata['uninstallstring'] }
# split quoted and unquoted things on whitespace
$prog, $myargs = $uninstall | select-string '("[^"]*"|\S)+' -AllMatches | 
  % matches | % value
$prog = $prog -replace '"',$null  # call & operator doesn't like quotes
$silentoption = '/S'
$myargs += $silentoption  # add whatever silent uninstall option
& $prog $myargs  # run uninstaller silently

Start-process doesn't mind the double quotes, if you need to wait anyway:

# "C:\Program Files (x86)\myapp\unins000.exe"
get-package myapp | foreach { start -wait $_.metadata['uninstallstring'] /SILENT }
Manolete answered 29/3, 2022 at 16:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.