Uninstalling Problem Applications using Powershell

Published: 2015-12-10
Last Updated: 2015-12-10 12:54:27 UTC
by Rob VandenBrink (Version: 1)
6 comment(s)

In my last story, we went over winnowing through a Nessus scan to determine which apps you might want to patch.  But what if the final finding isn’t “Lets update Java” but rather, “Why do we need JRE installed at all?  Let’s delete it across the domain”

How can you do this for free, without buying a full software inventory and management system.  Say, with nothing but Powershell?  In years past, I would have used WMIC and cmd files, but Powershell is much faster and much more flexible.  Let’s start by listing the apps on a workstation.  If you don’t have a software inventory solution, Powershell can get the job done with just a bit of scripting (note that this is does not show the entire list of members in $apps - it's a pretty long list)

Next, let’s get the info we need to delete an app – the App name, version and “IdentifyingNumber” (the application’s GUID):

OK – now let’s just pick the apps we want to remove:

That’s all the basic information we need to delete an app, let’s put it together.  The $classkey variable shown is the string needed to “fully qualify” the application to delete – let’s just look at the first app in the list:

(note the escaping to make the quote characters around the values)

Then, to uninstall our target application, for each instance, we want to execute:

If $killit.ReturnValue is non-zero, the uninstall was not successful – a value of 1603 for instance means you don’t have rights to uninstall the apps, retry as local admin (or domain admin)

So, putting it all together, let’s delete a target app remotely in a typical company. We start by getting domain admin credentials (remember that you need admin rights to delete an app)

Next, get your list of servers into an object.  Maybe you got that computer list from working through your Nessus report, or maybe output from your software inventory system:

Or maybe we’ll pull the target station list right out of AD.  If you take this approach, maybe you just want the non-servers:

Whatever method you used to get your list, MANUALLY CHECK IT AND EDIT IT FIRST, before you proceed:

Not the most elegant piece of code, and certainly not the fastest, but it’ll get the job done.  That "write-host" line especially would benefit from me making that an array of results, which would allow you at the end you to list out the full results, but then also just the failed results for instance.  For me, using write-host is almost always a last resort - there's always a better option.  The point was to keep things simple here though, not get to perfection  - come to think of it, that's the point of most scripts we write.

Note again that this script as written can be very dangerous!  We're running through a list of hosts, saying "if an application name has this string in it, delete it immediately".  If you pick the wrong string (say "advanced" or "microsoft"), you could match a lot more than you intend to!  Given that this script is going to UNINSTALL apps across your domain, and maybe reboot machines along the way, you likely will not want this in one mondo – automagic script anyway.  Dumping things out, checking output along the way and testing step by step is definitely the way to go.  Running your version of this against a few test stations first is also a really good practice.

This might sound like a lot of manual Posershell steps, when there's a script right there - but it’s still way faster than  having to recover an app after you’ve had a script like this run through your domain.  And needless to say, it's also way faster than uninstalling apps from stations one-by-one using just your keyboard and RDP!

If you’ve got a neat trick for uninstalling apps, or better yet, if you’ve got a more elegant Powershell approach for this, by all means, please share in our comment section!

Rob VandenBrink






6 comment(s)


I don't have time to try this at the moment, but does querying win32_product in this manner trigger unneeded MSI repairs? We used to do something similar to this but with .cmd scripts that leveraged wmic to get the list. When the initial query ran, it would often/always cause a number of apps to go through a repair process.

I eventually rewrote the script to use reg.exe instead and look for the identifiers. Never did get around to powershell-ize-ing it...
A simpler way to do this is to query each computer for the classkey and use that for the uninistall() commnad. e.g.

$clsid = gwmi win32_product -computername "computername" | where {$_ -match "Java"}
foreach($cls in $clsid) {$cls.uninstall()}

The computername is contained in the returned $clsid
You can also share the C:\ drive within your domain and call upon the C$ during scripts.
To do this first set the registry keys:
//Generate registry key to create admin shares
regset "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\LanmanServer\Parameters]" "AutoShareWks"=dword:00000001

//Set Remote Registry service to start automatically
regset "[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\RemoteRegistry]" "Start"=dword:00000002

Then ensure the Server ("Lanmanserver") service is restarted (no reboot required which means you can enable this using remote registry or script)

then you can call on hosts using \\$_\c$\program files\etc..
I usually set a location for a list of computers as you mentioned and then when calling on the list, \\$_ inserts each line of computer here. This may require the ForeachObject in this case? I havn't tested your uninstall script with my listed syntax yet
Writeup on why using win32_product is not the best idea in the world:

I should have read the Hey Scripting Guy article, it has a link to a different HSG article that references the issues with win32-product. Ho hum.

Anyway, formatting that long string:
One way to simplify the formatting is to use $(sub.expressions).
$classkey = "IdentifyingNumber=`"$($apps2go[0].IdentifyingNumber)`",Name=`"$($apps2go[0].Name)`",Version=`"$($apps2go[0].Version)`""

makes it easier to read as there are less double quotes and concatenation.

I prefer this way in complex strings. It is using the format operator (with works just fin with single quoted strings) to split the variables from the string. Makes the stricture of the string very easy to read.

$classkey = 'IdentifyingNumber="{0}",Name="{1}",Version="{2}"' -f $apps2go[0].IdentifyingNumber, $apps2go[0].Name, $apps2go[0].Version
Some time ago I wrote something similar in one of our SOE Scripts.
Works great along with replacing Java with Flash/Reader/etc as needed.


write-host "Uninstalling all/any versions of Java"

$product = Get-WmiObject -Class Win32_Product | Where-Object {$_.Name -like "Java*"}

if ($product -ne $null ) {
$ProductDetails = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "Java*" } | Select Name, Version
"Found $ProductDetails "

"Attempting to Uninstall"

Else {"No Java Versions Found "}

Diary Archives