Powershell to Remove Software

Today we are beginning to talk about Powershell and its many uses. I decided to start here because this is where I started. This was one of the first task that I had to automate. We were deploying hundreds of machines a month and all of them came with software that was pre-installed by the manufacturer and in some cases we had as much as 30 different things we needed to uninstall. Thank you IBM!

So some people may ask why we did not just uninstall everything and capture an image and then use the image. We started in the world of Windows XP and back then using images on different types of hardware was not easy to do. We were beginning to use a Remote Management Tool called Kaseya which allowed us to upload scripts to a central repository that would then be used to download and execute on the computers wherever in the world they were. We wrote all of our scripts for this purpose. Today Kaseya is a very common RMM along with many others such as Labtech and SCCM.

Here is the function. You would utilize this by pasting the function into your script and calling it while supplying the parameters.

For Example...
Remove-Software -application "Bing Bar" -command "(Start-Process -FilePath `"msiexec.exe`" -ArgumentList `"/X{} /qn`" -Wait -Passthru).ExitCode"

This will find the GUID used for the application Bing Bar and place it in the uninstall string. The uninstall string usually does not change but sometimes the GUID does which is why it is not just running an uninstall command by itself.

function Remove-Software { 
        [CmdletBinding()]

    <# This function allows you to uninstall software that is listed in WMI. To find if it is listed run this command... get-wmiobject win32_product | where {$_.name -match "Reader"} There are 2 parameters that are all mandatory --application -- This is the name of the application as it exist in WMI --command -- this is the uninstall command to uninstall the software Example: foreach($item in $apps){ Remove-Software -application $item[0] -command $item[1] } #>
    param   (
            [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True)]
            [string[]]$application,
                    
            [Parameter(Mandatory=$True,
            ValueFromPipeline=$True,
            ValueFromPipelineByPropertyName=$True)]
            $command
            )
    BEGIN {}
    PROCESS {
            $app = get-wmiobject win32_product | Where-Object {$_.name -match "$application"}
            $appid = $app.IdentifyingNumber
            
            if (test-path 'c:\program files (x86)')  {
                $appreg = Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" |
                        ForEach-Object {Get-ItemProperty $_.PSPath} | Where-Object{$_.DisplayName -match "$application"}
            } else{
                $appreg = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" |
                        ForEach-Object {Get-ItemProperty $_.PSPath} | Where-Object{$_.DisplayName -match "$application"}
            }
                                
            if($app) {
                $uninstallcommand = $command.Replace("{}","$appid")
                Write-Verbose "$application is installed"
                Invoke-Expression $uninstallcommand     
                if(get-wmiobject win32_product | Where-Object {$_.name -match "$application"}) {
                    Write-Verbose "$application did not uninstall and is still installed"
                } else {
                    Write-Verbose "$application has been uninstalled properly"
                }
            } elseif($appreg) {
                Write-Verbose "$application is installed"
                Invoke-Expression $command
                if (test-path 'c:\program files (x86)')  {
                    $appreg2 = Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" |
                            ForEach-Object{Get-ItemProperty $_.PSPath} | Where-Object{$_.DisplayName -match "$application"}
                } else {
                    $appreg2 = Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall" |
                            ForEach-Object{Get-ItemProperty $_.PSPath} | Where-Object{$_.DisplayName -match "$application"}
                }
                if($appreg2){
                    Write-Verbose "$application did not uninstall properly"
                } else {
                    Write-Verbose "$application has been uninstalled"
                }
            }
        }             
    END {}
}

FUNCTIONS
Let's start with Functions. Functions are just a way to group a bunch of code together and giving it a name so that when you want to run the code you just have to use the name. The function above has 72 lines of code inside of it. If you wanted to uninstall 20 applications then you need it to run 20 times. You could type out each one and have 1440 lines of code or you can wrap your code in a function so that you only need one line to call it each time.

Remove-Software -application App1 -command "*uninstall command*"
Remove-Software -application App2 -command "*uninstall command*"
Remove-Software -application App3 -command "*uninstall command*"

PARAMETERS
Parameters allow you to accept input to your function. When writing functions think of writing them so that they receive a reliable input and return a reliable output. What do I mean by reliable input? The way that the application parameter is written is that it is immediately casted to a string. If you try to input something that is not capable of being a string then the function will fail. You can do this with other types such as integers as well.

I will go in more detail here later as to all of the options available for parameters in advanced functions like making them mandatory or allowing piping into them.

VARIABLES
Variables are virtually the same in all programming languages with a few small syntax changes or rules on some languages. When you need to store information in memory using Powershell you need to start by using the $ and then provide a name for the section of memory.

$app

Here we are asking the computer to create a section of memory to store information and we are calling it app. In some programming languages you have to supply the type of information that will be stored there such as string, int, bool or other but in Powershell you do not need to do this as it is what is called a weak typed language. You can force it to store it as a specific type if you wish and you may want to at times to force it to be of a certain type or to force it to a specific size of memory as each type has different size requirements.

CMDLETS
In Powershell you will use preset commands called CMDLETS pronounced "Command-Lets". They always come in a VERB-NOUN pattern such as Get-Process, Set-Process, Start-Process, Stop-Process. You can get a list of all commands by typing "Get-Command -List" in the Powershell command window. If you would like to add more commands to Powershell you can do so by creating your own like we are with the function or by creating or importing a module.

Get-WMIObject is one of the most powerful cmdlets when automating builds as it allows you to get information about the underlying computer and operating system. The Get-WMIObject cmdlet then takes in the name of a specific class that you want to find information about. I used the Win32_Product class in the example above because it is the class that was built for manipulating the installed software on the computer. There are literally hundreds of classes and you can see them all by typing Get-WMIObject -list in the Powershell command window. Other useful ones are Win32_BIOS, Win32_ComputerSystem, Win32_NetworkAdapter. I advise that you take the time to go through some of these and play with them to see what the results look like.

CLASSES
One thing about classes. When you type Get-WMIObject Win32_Product and hit Enter, your computer is not going to a place in storage and retrieving some pre-made objects and returning their value. Instead classes are blueprints and Powershell creates an object from the blueprint at runtime and fills the properties of the object with information about the computer.

An analogy I use is that if you have a coffee cup on your desk then a class to represent that cup would say that all cups have properties such as height, volume, shape, and color. They also all have methods (actions) of fill, empty, and clean. No matter what cup you make from this blueprint it will always have these features. At runtime, Powershell will create the cup and fill the property values with things like height = 8 inches, volume = 8oz, shape = square, color = blue. It may then create another cup with entirely different values but in the end both objects are both cups and both have the same properties available to them.

Leave a Reply

Your email address will not be published.