# encoding: ascii
# api: powershell
# title: Get-Software Function
# description: I was having a great deal of difficulty in properly enumerating software applications on remote machines, so I wrote this function to help ease the problem somewhat. Among the many methods of gathering this information that are available, I had the most success using the “SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall” registry key as an information source. In order to use this function, the Powershell instance must support .Net 4.0 or greater, which is fairly straightforward if you follow these instructions.
# version: 0.1
# type: function
# author: Geoffrey Guynn
# license: CC0
# function: Get-Software
# x-poshcode-id: 3186
# x-archived: 2014-03-10T19:53:51
# x-published: 2014-01-25T08:40:00
#
# 1. Open notepad and copy the below text exactly as shown into the document.
# <?xml version=“1.0”?>
# <configuration>
# <startup useLegacyV2RuntimeActivationPolicy=“true”>
# <supportedRuntime version=“v4.0.30319”/>
# <supportedRuntime version=“v2.0.50727”/>
# </startup>
# </configuration>
# 2. Save this document as c:\windows\System32\WindowsPowerhsell\v1.0\Powershell.exe.config (or c:\windows\System32\WindowsPowerhsell\v1.0\Powershell_ise.exe.config)
# 3. Reload powershell and type the following command: $PsVersionTable.clrVersion (It should show Major version 4 if .Net 4 is supported.)
# Take note that this release does not contain error checking functions that my production version uses, I removed references to these functions in order to provide a good starting function for others to use. If you have any errors with this code, please contact me and I will resolve them as soon as possible.
#
Function Get-Software{
    <#
    .SYNOPSIS
        Gets the software applications on a remote computer.
    .DESCRIPTION
        This function interrogates the remote registry for installed software products.
        It then returns an array of powershell objects that can be sorted and parsed.
        
        Optionally, you can provide a product name that will filter applications based on
        displayname before they are returned, thus reducing the typing needed to get specific results.
    .PARAMETER computer
        The name of the computer to retrieve a software list from.
    .PARAMETER product
        The partial name of a software application to search for.
    .INPUTS
    .OUTPUTS
    .NOTES
        Name: Get-Software
        Author: Geoffrey Guynn
        DateCreated 9 Aug 2011
    .EXAMPLE
        Get-Software -computer "computer" -name "Adobe"
        Returns all instances of Adobe software on the computer.
    #>

    [cmdletbinding(
        SupportsShouldProcess = $True,
        DefaultParameterSetName = "process",
        ConfirmImpact = "low"
    )]
    param(
        [ValidateNotNullOrEmpty()]
        [string]$Computer
        ,
        [string]$Product
    )
    #Get Architechture Type of the system
    $OSArch = (Gwmi -computer $Computer win32_operatingSystem).OSArchitecture
    if ($OSArch -like "*64*") {$Architectures = @("32bit","64bit")}
    else {$Architectures = @("32bit")}
    #Create an array to capture program objects.
    $arApplications = @()
    foreach ($Architecture in $Architectures){
        #We have a 64bit machine, get the 32 bit software.
        if ($Architecture -like "*64*"){
            #Define the entry point to the registry.
            $strSubKey = "SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            $SoftArchitecture = "32bit"
            $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry64
        }
        #We have a 32bit machine, use the 32bit registry provider.
        elseif ($Architectures -notcontains "64bit"){
            #Define the entry point to the registry.
            $strSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            $SoftArchitecture = "32bit"
            $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry32
        }
        #We have "64bit" in our array, capture the 64bit software.
        else{
            #Define the entry point to the registry.
            $strSubKey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall"
            $SoftArchitecture = "64bit"
            $RegViewEnum = [Microsoft.Win32.RegistryView]::Registry64
        }
        #The standard routine to get software.
        #************************************************************************
        #Create a remote registry connection to the server.
        $remRegistry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $Computer, $RegViewEnum)
        #Open the entry point.
        $remKey = $remRegistry.OpenSubKey($strSubKey)
        #Get all subkeys that exist in the entry point.
        $remSoftware = $remKey.GetSubKeyNames()
        #Loop through the applications and capture data.
        foreach ($remApplication in $RemSoftware){
            $remProgram = $remRegistry.OpenSubKey("$strSubKey\\$remApplication")
            $strDisplayName = $remProgram.GetValue("DisplayName")
            if ($strDisplayName){
                #Get a list of all available properties for the program.
                $arProperties = $remProgram.GetValueNames()
                #Create a custom object for this program.
                $objApplication = New-Object System.Object
                #Write-Host "`n"
                foreach ($strProperty in $arProperties){
                    #Get the value associated with the current property.
                    $strValue = [string]($remProgram.GetValue($strProperty))
                    if ($strValue){
                        #If the property has a value but no name, then it is the default property.
                        if (!$strProperty){
                            $objApplication | Add-Member -type NoteProperty -Name "(Default)" -Value $strValue
                        }
                        #The property has a value and a name, assign them both as a new property on the object.
                        else{
                            $objApplication | Add-Member -type NoteProperty -Name ([string]$strProperty) -Value $strValue
                        }
                    }
                    #Write-Host $strValue ": " $remProgram.GetValue($strValue)
                }
                #Add a final property to denote the software architecture type.
                $objApplication | Add-Member -type NoteProperty -Name "Architecture" -Value $SoftArchitecture
                #Add the last application to the array of programs.
                $arApplications += $objApplication
            }
        }
    }
    if ($Product){
        $objApplication = $arApplications | ? {$_.DisplayName -Like "*$Product*"} | Sort-Object -property Architecture,DisplayName
        return $objApplication
    }
    #Sort the array by each object's Architecture and DisplayName.
    $arApplications = $arApplications | Sort-Object -property Architecture,DisplayName
    return $arApplications
}
