Find and uninstall apps

Andrews Dumith 41 Reputation points
2021-12-17T18:44:25.913+00:00

Hi all,

I have a script that does a search for the apps that I have in an array.

The idea is to find the app and silently uninstall it.

I have more than hours trying to understand why it does not work and I can not get the origin of the failure.

Can you give me a hand?

Thank you...

Script

########## Removing Apps ##########
$Appname =  @(
'Drafting Assistant'
)

foreach ($App in $AppName) 
{
$App = Get-Itemproperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName |
Where-Object { $_.DisplayName -match "^*$appname*"}
$UninstallString = if($App.Uninstallstring -match '^msiexec')
  {
  "$($App.UninstallString -replace '/I', '/X' ) /S /v /qn /norestart"
  }
  else
  {
  $App.UninstallString
  }
Write-Verbose $UninstallString
Start-Process -FilePath cmd -ArgumentList "/v /qn /norestart", $UninstallString -NoNewWindow -Wait  
}
Windows for business | Windows Server | User experience | PowerShell
0 comments No comments
{count} votes

Accepted answer
  1. MotoX80 36,291 Reputation points
    2021-12-17T20:56:56.167+00:00

    Well you do a "foreach ($App in $AppName)" and the first thing that you do in the script block is to overwrite the $app variable. You don't have any error handling to test to see that you actually found an app to uninstall. On the start-process you include the msiexec switches that you already added to the $uninstall variable.

    Try this and see if it works. Uncomment the start-process when you have tested it and actually want to run the uninstall.

    # Updated 20-December, test for admin level and multiple apps, add logging to msiexec. 
    
    $Appname =  @(
      'Agent Ransack'
      )
    
    $admin = (whoami.exe /all | select-string S-1-16-12288) -ne $null
    if ($admin -ne $true){
        'You are not running Powershell in administrator mode. (UAC is not elevated)'
        "This script probably won't work."
         return
    } else {
        'You are running Powershell in administrator mode.'
    }
    
    
      foreach ($App in $AppName)  {
         ""
         "Looking for $app"
         $RegApp = Get-Itemproperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
             Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName |
             Where-Object { $_.DisplayName -match "^*$app*"}
         if ($Regapp.Count -gt 1) {
             "Multiple applications were found."
             "Please make the search string unique."
             ($regapp).displayname 
             continue
         }  
    
         if($RegApp) {
             "We found $app"
             if($RegApp.Uninstallstring -match '^msiexec')   {
                  $UninstallString = "$($RegApp.UninstallString -replace '/I', '/X' )  /qn /norestart /log C:\Windows\temp\msi.log"
    
             } else {
                  "But it does not use MSIEXEC!" 
                  $UninstallString = $RegApp.UninstallString
             }
             #### $RegApp.UninstallString
             $pos = $UninstallString.ToLower().IndexOf('.exe')
             $exe =  $UninstallString.Substring(0,$pos + 4)
             $args = $UninstallString.Substring($pos + 4).trim()
             "The uninstall string is: $UninstallString"
             "The program is ........: $exe"
             "The arguments are......: $args"
             #####  Uncomment the next line to actually run the uninstall. 
             #####  Start-Process -FilePath $exe -ArgumentList $args -NoNewWindow -Wait  
             Start-Process -FilePath $exe -ArgumentList $Args -Wait 
             "It's finished. Here is the log."
             get-content C:\Windows\temp\msi.log
    
         } else {
             "We did not find $App"
         }
     }
    

5 additional answers

Sort by: Most helpful
  1. Andrews Dumith 41 Reputation points
    2021-12-17T21:48:28.037+00:00

    Thanks for replying,

    It can be said that it is my first script in PowerShell, I do apologies for the basic omissions, such as error handling.

    I have put together a script looking for parts in various places and at the same time learning the language.

    I just tested the script and for all apps the result is that it wasn't found.

    Could it be that the error is in the name of the software?

    I search for the name with the command

    Get-WmiObject -Class Win32_Product | Select-Object Vendor, Name

    Then I took the name and create an array with it.

    Again, I really appreciate your guidance


  2. Andrews Dumith 41 Reputation points
    2021-12-17T21:58:04.443+00:00

    Never mind,

    I just got it.

    Your machine its 64bits and mine is 32, so I had to change de RegKey Path for this

    \Software**WOW6432Node**\Microsoft\Windows\CurrentVersion\Uninstall\

    The script is working perfect.

    Thank you so much.


  3. Rich Matheisen 47,901 Reputation points
    2021-12-18T03:54:27.807+00:00

    Not to be picky, but I'd change the ' Where-Object { $_.DisplayName -match "^$app"} ' to use the "-Like" operator and lose the "^" at the beginning. That "^" really means match zero or more occurrences of the beginning of the string. If you want to keep it, I'd change it to "^." (match zero or more occurrences of anything at the beginning of the string), or just lose the "" altogether (which makes more sense).

    Because you're using wildcard matches, there's also the possibility of finding multiple instances where $RegApp becomes an array instead of a simple string.

    The use of the variable "$args" is probably a mistake. It's actually an array of the arguments that are passed into the script at the time that it is run.

    Here's another version:

    ########## Removing Apps ##########
    $Appname = @(
        'Drafting Assistant'
    )
    
    $OtherArgs = "/S /v /qn /norestart"
    
    foreach ($App in $AppName) {
        # there may be multiple applications that meet the partial/wild-card match
        $Apps = Get-ItemProperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
            Where-Object { $_.DisplayName -like "*$_*" } |
                Select-Object DisplayName, DisplayVersion, UninstallString, PSChildName |
    
        if (-not $Apps){
            "We did not find $App"
        }
        else{
            # You might want to verify that only one application matched before uninstaling!
            $Apps |
                ForEach-Object{
                    if ($_.Uninstallstring -match '^msiexec\.exe ') {
                        $RegApp = $_.UninstallString -replace "/I", "/X"
                        $exe = $RegApp -replace '^([^ ]+) .*$', '$1'        # get the executable name
                        $arguments = $RegApp -replace '^([^ ]+)', ''        # get the argument(s) -- if there are any
                        $arguments += $OtherArgs                            # add additional arguments
                        Write-Verbose "Starting '$exe' with arguments '$arguments'"
                        # uncomment next line when you're ready
                        #Start-Process -FilePath $exe -ArgumentList $arguments -NoNewWindow -Wait
                    }
                    else {
                        $exe = $_.Uninstallstring -replace '^([^ ]+) .*$', '$1'        # get the executable name
                        $arguments = $_.Uninstallstring -replace '^([^ ]+)', ''        # get the argument(s) -- if there are any
                        Write-Verbose "Starting '$exe' with arguments '$arguments'"
                        # uncomment next line when you're ready
                        #Start-Process -FilePath $exe -ArgumentList $arguments -NoNewWindow -Wait
                    }
                }
            }
        }
    

  4. Rich Matheisen 47,901 Reputation points
    2021-12-21T03:16:48.74+00:00

    Have a look at this version. The previous code was modified from you and @MotoX80 . This one will work on 32- and 64-bit software installed on 32- and 64-bit machines. All the code posted earlier looks like (I didn't test it!) it might have a problem with some software installed by non-Microsoft installer apps.

    $AppNames =  @(  
     'App display name goes here'  
    )  
      
    $MsiExecOtherArgs = "/S /v /qn /norestart"  
      
    Function GetStartProcessArgs{  
        param (  
            [Parameter(mandatory=$true)]  
            [string]  
            $RemovalString,  
      
            [Parameter(mandatory=$false)]  
            [string]  
            $MoreArgs  
        )  
        $props = [ordered]@{}  
        if ($removalString -like "msiexec.exe *"){  
            $UninstallApp            = $RemovalString -replace "/I", "/X"  
            $props['FilePath']       = $UninstallApp -replace '^([^ ]+) .*$', '$1'          # get the executable name -- expects " /X{<guid>}" to always be there!  
            $props['ArgumentList']   = $UninstallApp -replace '^[^ ]+ ', ''                 # get the argument(s) -- expects " /X{<guid>}" to always be there!  
            if ($MoreArgs -and $MoreArgs.Length -gt 0){                                     # $MoreArgs can be an empty string too  
                $props.ArgumentList += (" " + $MoreArgs)                                    # add additional arguments (with leading space to separate from previous args)  
            }  
        }  
        else{  
            # NOTE: ignores $MoreArgs entirely if msiexec.exe isn't the uninstaller to run  
            if ($RemovalString -match '^"'){                                                # a quoted string -- path contains a space  
                $props['FilePath']   = $RemovalString -replace '^"([^"]+?)" ?.*$', '$1'     # keep spaces in path, drop quotes  
                if ($RemovalString -match '^".+?" ?'){  
                    $potentialargs       = $RemovalString -replace '^".+?" ?', ''           # remove path, keep arguments  
                }  
                else{  
                    $potentialargs = ""                                                     # return an empty string to avoid exceptions in code  
                }  
            }  
            else{  
                $props['FilePath']   = $RemovalString -replace '^([^ ]+?) .*$', '$1'        # get the executable name (unquoted path)  
                $potentialargs       = $RemovalString -replace '^[^ ]+ ', ''                # get the argument(s), if there are any  
            }  
            if ($potentialargs.trim().length -gt 0){                                        # if no arguments, ignore  
                $props['ArgumentList'] = $potentialargs  
            }  
        }  
      
        # return exe and arguments  
        $props  
    }  
      
    # begin main code  
    # get 64-bit software on 64-bit systems OR 32-bit software on 32-bit systems  
    [array]$32_or_64bitsoftware = get-itemproperty 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'  
    # get 32-bit software ON 64-bit systems  
    [array]$32_on_64bitsoftware = get-itemproperty 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'  
      
    $AppNames|  
        ForEach-Object{  
            $appname = $_  
            [array]$32_AND_64bit = @()  
            # check for software in both locations  
            $32_AND_64bit  = $32_or_64bitsoftware |  
                                        Where-Object { $_.DisplayName -like "*$appname*" }  
            $32_AND_64bit += $32_on_64bitsoftware |  
                                        Where-Object { $_.DisplayName -like "*$appname*" }                              
            # was the app's display name found?          
            if ($32_AND_64bit.count -eq 0){  
                "'$appname' was not found on this system"  
                Write-Verbose "'$appname' was not found on this system"  
            }  
            else{  
                # uninstall all versions of this software  
                $32_AND_64bit |  
                    ForEach-Object{  
                        if ($_.UninstallString.length -gt 0){  
                            $props = GetStartProcessArgs $_.UninstallString $MsiExecOtherArgs  
                            Write-Verbose "Starting '$($props.FilePath)' with arguments '$($props.ArgumentList)'"  
                            "Starting '$($props.FilePath)' with arguments '$($props.ArgumentList)'"  
                            # uncomment next line when you're ready  
                            #Start-Process @props -NoNewWindow -Wait  
                        }  
                    }  
            }  
        }  
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.