2

CCleaner contains a tool to list and then uninstall programs on your PC. This list seems to include applications in a more comprehensive way than a walk through the uninstall registry keys. One example of this, is Atom (the Open Source text editor). This program does not appear in the uninstall registry, and is installed in the AppData folder of the user (I'm not aware of a way to install this for all users without building a custom package).

I wrote a script that installs and updates certain software packages on a regular basis. This makes it easy for me to keep them up to date without visiting a dozen or so websites every week or building a custom installer every time I want to update them (they don't auto-update like Chrome or Firefox). Therefore, I need a list that I can create dynamically and use to check for updates and if I need to execute the installer.

So my question is: How do I emulate what CCleaner does when it creates its list of programs for uninstalling -- programmatically? I can execute the GUI and navigate to the uninstall tool and click "save to text file" but that isn't dynamic. Any answer that allows me to capture (in a Powershell script) the same list of applications that CCleaner generates in the uninstall tool will be acceptable.

McKenning
  • 631
  • 4
  • 20
  • 32

2 Answers2

1

You can use Get-Package to list installed programs as well. It will list Atom. You may need to combine the registry approach with Get-Package in the case it doesn't show all.

Get-Package | Where-Object name -like *atom*

Name       Version     Source      ProviderName                                                                                                 
----       -------      ------     ------------                                                                                                 
Atom       1.53.0                  Programs  
Doug Maurer
  • 8,090
  • 3
  • 12
  • 13
  • 1
    This worked well. I had to reconcile the different headers for each array (the registry array as well as the Get-Package one), but I just created a new PSObject and was able to combine them into one, unique list. Thanks! – McKenning Nov 20 '20 at 17:25
1

What you are asking for used to be done with Get-CimInstance, but that comes at a cost and as you pointed out is no longer accurate. It used to be a WMI command. Now CIM. It is not a fast command, more on that later.

Get-CimInstance win32_product

The cost is Get-CimInstance can return incomplete data. It also runs a consistency check on all applications and performs automatic and silent repairs. Yes, when you run this command, it automatically runs a consistency check on all applications and performs automatic and silent repairs. That is why this simple command is so slow to report back. Microsoft's documentation on this: Link Here

So we do not use that anymore, now what? What you are looking for is gathering the information from both 32 and 64 bit installers that looks like this and must be done, I do it first, always.

$installedApplications = @()
$installedApplications+= Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" # 32 Bit
$installedApplications+= Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"             # 64 Bit

One caveat to the above is that the above method will return a ton more elements than the Win32_Product command does. It will have things such as Service Packs, Office Updates, Language Packs, etc. You will most likely need to filter out things you aren’t interested in, though you shouldn't have an issue using PowerShell to filter results.

To complete the answer to your question, how do you quantify data from installers regardless of their install location? Specifically finding the userprofile\AppData install information. The good news is these applications have their installation information documented in the registry as well, under HKEY_CURRENT_USER instead of HKEY_LOCAL_MACHINE. What this means is every user's install location information is sitting in the registry hive under their profile, for instance c:\users\inet\NTUSER.DAT.

What else needs to be said about this;

  • If a user is logged in this can be accessed by any other admin user on the system by using the HKEY_USERS\$ACCOUNT_SID key.
  • If a user is not logged in, the hive can be manually mounted using REG LOAD
  • If a user's registry hive is already loaded it cannot be loaded a second time and will give you the obligatory "this file is being used by another process."

So how do we get what you are asking for, which is to replicate the "full install data" for the device, and not just what's in the 32 and 64bit directories?

This is what I use, if one so chooses you can pipe the info to CSV/HTML/etc...

    $installedApplications = @()
    $installedApplications+= Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" 
    $installedApplications+= Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"  

    $32BitPath = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
    $64BitPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"

    $tigerStripes= Get-CimInstance Win32_UserProfile | Select LocalPath, SID, Loaded, Special | Where {$_.SID -like "S-1-5-21-*"}
    $unavailableProfiles = $tigerStripes| Where {$_.Loaded -eq $true}
    $availableProfiles = $tigerStripes| Where {$_.Loaded -eq $false}

    #Mounted
    $unavailableProfiles | % {
        $installedApplications += Get-ItemProperty -Path "Registry::\HKEY_USERS\$($_.SID)\$32BitPath"
        $installedApplications += Get-ItemProperty -Path "Registry::\HKEY_USERS\$($_.SID)\$64BitPath"
    }
    #Unmounted
    $availableProfiles | % {
        #Mount Hive
        $Hive = "$($_.LocalPath)\NTUSER.DAT"

        if (Test-Path $Hive) {
            REG LOAD HKU\temp $Hive
            $installedApplications += Get-ItemProperty -Path "Registry::\HKEY_USERS\temp\$32BitPath"
            $installedApplications += Get-ItemProperty -Path "Registry::\HKEY_USERS\temp\$64BitPath"

            #This lets the hive be unmounted, using a manual Get-Content
            [GC]::Collect()
            [GC]::WaitForPendingFinalizers()
        
            REG UNLOAD HKU\temp

        } else {
            Write-Warning "Unable to access registry at $Hive"
        }
    }

$installedApplications 
iNet
  • 126
  • 5
  • Thanks for your answer. While I didn't select it as the "the" answer, because of the slowness issues you mentioned, it did show additional software packages. – McKenning Nov 20 '20 at 17:27
  • `Get-CimInstance win32_product` is really slow, especially if you have a lot of applications similar in size to PhotoShop or MatLab. – iNet Nov 20 '20 at 19:17