1

I have an application that seems to have left a mess behind. I've uninstalled the app and it seems to have progressed normally. No longer shows in Programs & Features. Nothing in the registry locations: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer
Nothing even when searching.

When I try to install a newer version I get an error that the previous version needs to be removed first.

I finally found something when I looked in Win32_Product using PS:

Get-WmiObject Win32_Product | Sort-Object Name | Format-Table IdentifyingNumber, Name, LocalPackage

Question is, how can I get it out of here with a script? I can't run an uninstall, get errors. I have been able to use MSI Cleanup Utility to remove but I'd like to be able to do something more automated. Estimating that there's about 200 machines in this state.

Steven
  • 6,817
  • 1
  • 14
  • 14
Derryn
  • 31
  • 4
  • Which app is it? – js2010 May 18 '21 at 02:40
  • Symantec WSS Proxy – Derryn May 18 '21 at 11:48
  • Never use `Win32_Product`. [See my answer here](https://stackoverflow.com/questions/71575378/powershell-for-software-inventory/71576041#71576041) for how to query the registry for software inventory. `Win32_Product` is ultimately sourced from here, anyways. – codewario Mar 24 '22 at 14:22

2 Answers2

1

There are some very good reasons to never use Win32_Product. If you Google, there's lots of explanations, but here's one of the first hits, Please Stop Using Win32_Product To Find Installed Software. Alternatives Inside!

Of course, that's not really your question, Win32_Product was just how you located it. It's possible the program installation data is in a different location.

Try looking through:

  1. 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
  2. 'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'
  3. 'Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\'

There's an awesome script on the Gallery called Get-RemoteProgram.ps1 it packages a function by the same name so dot source it into your session like:

. <Path>\Get-RemoteProgram.ps1

Once you are in you can search for the program and include the registry path in the output. There're plenty of examples in the help file but something like:

Get-RemoteProgram -ComputerName $env:computername -IncludeProgram ^Office -ProgramRegExMatch -DisplayRegPath

Once you know the location I would look for an UninstallString value. If yes, I'd then think about how to get it to run silently, which if it's an MSI package should be pretty straight forward. Once you've got it worked out simply wrap some PowerShell code around it to invoke and monitor it through to completion.

Update from Comments:

Obviously I'd have trouble figuring this out from a afar. I posted above because it would find something in the registry. Partly because you hadn't listed the Wow6432... location.

Given my earlier statements I'm not going to try testing Win32_Product on my own. However, my next step would be to figure out what Win32_Product is finding. In that case I would start with Process Monitor. It will take some work, but may illuminate what Win32_Process is finding.

The other thing I can suggest is to observe a fresh installation of the software on another system. By snapshotting the registry, and perhaps a directory listing before and after you may find additional bread crumbs.

You can also use a secondary instance of the program to harvest the uninstall string, then try running it on the concerned system to see what happens.

Steven
  • 6,817
  • 1
  • 14
  • 14
  • Sounds like a fancy module! Will take a look at it:) – Abraham Zinala May 18 '21 at 02:50
  • Thanks for your reply. The problem is, its not in the registry anywhere. I've never seen anything like this. Uninstalling with the MSI gives me an error, trying to upgrade gives me an error. The only way I can can tell that this machine has the program on is by querying Win32_Product. – Derryn May 18 '21 at 11:39
0

Piggy backing off Steven's answer, you can use something along the lines of this:

Function Uninstall-Software {
[CmdletBinding()]
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$false,
                   ValueFromPipeline=$true,
                   ValueFromPipelineByPropertyName=$true)]
        [Alias('cn','name')]
        [string[]]$ComputerName,

        [Parameter(Mandatory=$false)]
        [switch]$Quiet
    )
Begin{
$Date_Now = Get-Date
}
Process{
    if($Quiet){
        foreach($Computer in $ComputerName){
            try{

            $PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop

        [array]$Software_List = Invoke-Command -ScriptBlock { 
                                    Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
                                                     "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"} -Session $PSSession  
        [array]$Software_List = $Software_List | Sort-Object -Property DisplayName -Unique
            $(for($i=0; $i -lt $Software_List.Count; $i++){
            [PSCustomObject]@{
                'Display Name' ="${i}.) $($Software_List[$i].DisplayName)"
                '  Version  '  = $Software_List[$i].DisplayVersion
            }
        } ) | Out-Host

$QS = Read-Host -Prompt "Enter Number of software to uninstall"
$QS = $QS -split ','
    if([string]::IsNullOrEmpty("$QS") -eq $true){"Null string"; break}

   foreach ($Q in $QS) {
        if($Software_List[$Q].QuietUninstallString -eq $null){
            
            $Quiet_Switch = '/quiet'
            $Uninstall_String = $Software_List[$Q].UninstallString

            Invoke-Command -ScriptBlock { & cmd /c $Using:Uninstall_String $Using:Quiet_Switch /norestart } -Session $PSSession -EnableNetworkAccess
        }
        else{
            
            $Uninstall_String = $Software_List[$Q].QuietUninstallString 

            Invoke-Command -ScriptBlock { & cmd /c $Using:Uninstall_String /norestart } -Session $PSSession -EnableNetworkAccess
        }

        #& cmd /c $Uninstall_String $Quiet_Switch /norestart 
        
            }
        } Catch [System.Management.Automation.RuntimeException] {
                $Error[0].Message.Split('.')[1].Trim()
        }
    }
}
    Else{
        foreach($Computer in $ComputerName){
            try{

        $PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop

        [array]$Software_List = Invoke-Command -ScriptBlock { 
                                    Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
                                                     "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"} -Session $PSSession 
        [array]$Software_List = $Software_List | Sort-Object -Property DisplayName -Unique
            $(for($i=0; $i -lt $Software_List.Count; $i++){
            [PSCustomObject]@{
                'Display Name' ="${i}.) $($Software_List[$i].DisplayName)"
                '  Version  '  = $Software_List[$i].DisplayVersion
            }
        } ) | Out-Host

$QS = Read-Host -Prompt "Enter Number of software to uninstall"
$QS = $QS -split ','
    if([string]::IsNullOrEmpty("$QS") -eq $true){"Null string"; break}
    
        foreach($Q in $QS){
            $Uninstall_String = $Software_List[$Q].UninstallString
            & cmd /c $Uninstall_String /norestart 

                    }
                } Catch [System.Management.Automation.RuntimeException] {
                    $Error[0].Message.Split('.')[1].Trim()
                }
        
            }
        }
    }
}

Calling the function gives you 2 options.

  • Interactive: Uninstall-Software
  • Non-Interactive: Uninstall-Software -Quiet

As you can imagine, the interactive choice doesn't work for remote computers, but using -Quiet will.

  • Uninstall-Software -ComputerName RemoteComputer -Quiet

This is a script I made a while ago and never finished it so it's all over the place. Works for the most part but, can use some serious work. I just dont care for it anymore.

Running the function will make a number selection out of your list of installed software pulled from the registry. So, all you have to do is select the number of software to uninstall, or select multiple. Hopefully this gets you on the right track following Stevens comments in regards to using the uninstall string to uninstall software, instead of using CIM methods.

Please don't mark this as the answer, thought i'd share an unfinished script that works.

Abraham Zinala
  • 4,267
  • 3
  • 9
  • 24
  • Thanks for your reply. The problem is, its not in the registry anywhere. I've never seen anything like this. Uninstalling with the MSI gives me an error, trying to upgrade gives me an error. The only way I can can tell that this machine has the program on is by querying Win32_Product. – Derryn May 18 '21 at 11:41
  • Most likely it's corrupt. How did you uninstall it first? Using the product class? Can you just copy over an MSI file and run the uninstall for it again – Abraham Zinala May 18 '21 at 12:50
  • I've tried with the MSI. It gives an error about a process not being initialized. Can't upgrade, can't find it in the registry. In fact if I try to update the client, I end up with 2 entries in Win32_Product, neither of which work and neither of which will remove. – Derryn May 18 '21 at 12:54