Tuesday, March 3, 2015

Get-Uninstall Function

When using WMI (Win32_Product class) to collect and manipulate software on machines I found that I was not happy with the lack of information that the query would return.  This is because Win32_Product only seems to collect information for software that was installed using an MSI and is populated in the CLASSES part of the registry.  I wrote this function to collect information from the uninstall keys and build objects based on the information in those keys.  The function by default collects a small set of properties to make the collection quick and practical.  You can set the "full" switch on the function to get every more properties, which usually aren't necessarily useful.  In the future I would like to implement remote capability, add a script method to make uninstalling software easy, and detect registry/system comflicts (keys for software that were already uninstalled but not cleaned up properly; I'm looking at you Java and Adobe).

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
Function Get-Uninstall([switch]$Full){
    $GUID_Regex =  "\b[A-F0-9]{8}(?:-[A-F0-9]{4}){3}-[A-F0-9]{12}\b"

    $Table= @()
    $All_Properties = @()

    Switch(Get-WmiObject -Class Win32_OperatingSystem | select -ExpandProperty OSArchitecture){
        "32-Bit" {$Uninstall_Locations = @{32="HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"}}
        "64-Bit" {$Uninstall_Locations = @(@{32="HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"};@{64="HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"})}
    }

    $Full_Key_Paths = Foreach($Location in $Uninstall_Locations){
        $Uninstall_Root = Get-Item $Location.Values
        $Uninstall_Root.GetSubKeyNames() | %{$Uninstall_Root.OpenSubKey($_).Name -replace "HKEY_LOCAL_MACHINE", "HKLM:"}
    }

    if($Full){
        $Properties_Greedy += foreach($Key in $Full_Key_Paths){
            (Get-Item $Key).GetValueNames()
        }

        $All_Properties = $Properties_Greedy | select -Unique
        $All_Properties = $All_Properties | ?{$_ -ne ""}
    }
    else{$All_Properties = @("DisplayName","InstallDate","WindowsInstaller","DisplayVersion","UninstallString","Publisher","InstallSource","RegOwner","EstimatedSize")}
  


    $i = 0
    foreach($Key in $Full_Key_Paths){
    $i++
    Write-Progress -Activity "Processing Software" -Status "$i/$($Full_Key_Paths.Count) complete." -PercentComplete (($i/$Full_Key_Paths.Count)*100)
        $Hash = [ordered]@{}
        foreach($Prop in $All_Properties){
            $Hash.$Prop = (Get-Item $Key).GetValue($Prop)
        }
        if($Key -match "Wow6432Node"){$Hash.Architecture = "32-Bit"}
        elseif($Key -notmatch "Wow6432Node" -and $Uninstall_Locations.Count -eq 2){$Hash.Architecture = "64-Bit"}
        else{$Hash.Architecture = "32-Bit"}
        $Hash.GUID = [regex]::Match($Key,$GUID_Regex, [System.Text.RegularExpressions.RegexOptions]::IgnoreCase) | select -ExpandProperty Value
        $Table += (New-Object -TypeName psobject -Property $Hash)
    }
    $Table = $Table | ?{$_.DisplayName -ne "" -and $_.DisplayName -ne $null}
    $Table
}