Skip to content

Instantly share code, notes, and snippets.

@mark05e
Forked from Bill-Stewart/Get-InstalledApp.ps1
Created July 7, 2021 02:14
Show Gist options
  • Select an option

  • Save mark05e/b2d56c224d18f198d1c0c421c5d35945 to your computer and use it in GitHub Desktop.

Select an option

Save mark05e/b2d56c224d18f198d1c0c421c5d35945 to your computer and use it in GitHub Desktop.

Revisions

  1. @Bill-Stewart Bill-Stewart created this gist Jan 13, 2020.
    276 changes: 276 additions & 0 deletions Get-InstalledApp.ps1
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,276 @@
    # Get-InstalledApp.ps1
    # Written by Bill Stewart ([email protected])
    #
    # Outputs installed applications on one or more computers that match one or
    # more criteria.
    #
    # Version history:
    #
    # Version 1
    # * Written for PowerShell 1.0. Lost in the mists of time.
    #
    # Version 2
    # * Requires PowerShell 2.0. (Uses new help and pipeline features.)
    # * Works properly on 64-bit Windows versions.
    #
    # Version 2.1
    # * Fixed problem if a registry path contains a ':' character.
    #
    # Version 2.2
    # * Add InstallDate property if available. Reported as a string.
    #
    # Version 2.3
    # * InstallDate property reported as a DateTime object.
    #
    # Version 2.4 (2020-01-13)
    # * Added UninstallString property
    # * Standarized param section; minor code tweaks

    #requires -version 2

    <#
    .SYNOPSIS
    Outputs installed applications for one or more computers.
    .DESCRIPTION
    Outputs installed applications for one or more computers.
    (64-bit Windows only) If you run Get-InstalledApp.ps1 in 32-bit PowerShell on a 64-bit version of Windows, Get-InstalledApp.ps1 can only detect 32-bit applications.
    .PARAMETER ComputerName
    Outputs applications for the named computer(s). If you omit this parameter, the local computer is assumed.
    .PARAMETER AppID
    Outputs applications with the specified application ID. An application's AppID is equivalent to its subkey name underneath the Uninstall registry key. For Windows Installer-based applications, this is the application's product code GUID (e.g. {3248F0A8-6813-11D6-A77B-00B0D0160060}). Wildcards are permitted.
    .PARAMETER AppName
    Outputs applications with the specified application name. The AppName is the application's name as it appears in the Add/Remove Programs list. Wildcards are permitted.
    .PARAMETER Publisher
    Outputs applications with the specified publisher name. Wildcards are permitted
    .PARAMETER Version
    Outputs applications with the specified version. Wildcards are permitted.
    .PARAMETER Architecture
    Outputs applications for the specified architecture. Valid arguments are: 64-bit and 32-bit. Omit this parameter to output both 32-bit and 64-bit applications. Note that 32-bit PowerShell on 64-bit Windows does not output 64-bit applications.
    .PARAMETER MatchAll
    Outputs all matching applications. Otherwise, output only the first match.
    .INPUTS
    System.String
    .OUTPUTS
    PSObjects containing the following properties:
    ComputerName - computer where the application is installed
    AppID - the application's AppID
    AppName - the application's name
    Publisher - the application's publisher
    Version - the application's version
    UninstallString - the application's uninstall string
    InstallDate - DateTime containing the date when application was installed*
    Architecture - the application's architecture (32-bit or 64-bit)
    *InstallDate property not available for all applications
    .EXAMPLE
    PS C:\> Get-InstalledApp
    This command outputs installed applications on the current computer.
    .EXAMPLE
    PS C:\> Get-InstalledApp | Select-Object AppName,Version | Sort-Object AppName
    This command outputs a sorted list of applications on the current computer.
    .EXAMPLE
    PS C:\> Get-InstalledApp wks1,wks2 -Publisher *microsoft* -MatchAll
    This command outputs all installed Microsoft applications on the named computers.
    .EXAMPLE
    PS C:\> Get-Content ComputerList.txt | Get-InstalledApp -AppID "{1A97CF67-FEBB-436E-BD64-431FFEF72EB8}" | Select-Object ComputerName
    This command outputs the computer names named in ComputerList.txt that have the specified application installed.
    .EXAMPLE
    PS C:\> Get-InstalledApp -Architecture "32-bit" -MatchAll
    This command outputs all 32-bit applications installed on the current computer.
    #>

    [CmdletBinding()]
    param(
    [Parameter(Position = 0,ValueFromPipeline = $true)]
    [String[]] $ComputerName = [Net.Dns]::GetHostName(),

    [String] $AppID,

    [String] $AppName,

    [String] $Publisher,

    [String] $Version,

    [ValidateSet("32-bit","64-bit")]
    [String] $Architecture,

    [Switch] $MatchAll
    )

    begin {
    $HKLM = [UInt32] "0x80000002"
    $UNINSTALL_KEY = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
    $UNINSTALL_KEY_WOW = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"

    # Create a hash table containing the requested application properties.
    $PropertyList = @{}
    if ( $AppID -ne "" ) { $PropertyList.AppID = $AppID }
    if ( $AppName -ne "" ) { $PropertyList.AppName = $AppName }
    if ( $Publisher -ne "" ) { $PropertyList.Publisher = $Publisher }
    if ( $Version -ne "" ) { $PropertyList.Version = $Version }
    if ( $Architecture -ne "" ) { $PropertyList.Architecture = $Architecture }

    # Replacement for 'Split-Path -Leaf'; 'Split-Path -Leaf'
    # chokes when the path contains ':'
    function Get-Leaf {
    param(
    $path
    )
    $path -split '\\' | Select-Object -Last 1
    }

    # Replacement for 'Split-Path -Parent'; 'Split-Path -Parent' chokes
    # when the path contains ':'
    function Get-Parent {
    param(
    $path
    )
    $elements = $path -split '\\'
    if ( $elements.Count -gt 1 ) {
    $elements[0..($elements.Count - 2)] -join '\'
    }
    else {
    $elements[0]
    }
    }

    # Returns $true only when the leaf items from both lists are equal.
    function Compare-LeafEquality {
    param(
    $list1,
    $list2
    )
    # Create ArrayLists to hold the leaf items and build both lists.
    $leafList1 = New-Object Collections.ArrayList
    $list1 | ForEach-Object { [Void] $leafList1.Add((Get-Leaf $_)) }
    $leafList2 = New-Object System.Collections.ArrayList
    $list2 | ForEach-Object { [Void] $leafList2.Add((Get-Leaf $_)) }
    # If Compare-Object has no output, then the lists matched.
    (Compare-Object $leafList1 $leafList2 | Measure-Object).Count -eq 0
    }

    function GetInstalledApp {
    param(
    $computerName
    )
    try {
    $regProv = [WMIClass] "\\$computerName\root\default:StdRegProv"

    # Enumerate HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
    # Note that this request will be redirected to Wow6432Node if running
    # from 32-bit PowerShell on 64-bit Windows.
    $keyList = New-Object System.Collections.ArrayList
    $keys = $regProv.EnumKey($HKLM, $UNINSTALL_KEY)
    foreach ( $key in $keys.sNames ) {
    [Void] $keyList.Add((Join-Path $UNINSTALL_KEY $key))
    }

    # Enumerate HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
    $keyListWOW64 = New-Object System.Collections.ArrayList
    $keys = $regProv.EnumKey($HKLM, $UNINSTALL_KEY_WOW)
    if ( $keys.ReturnValue -eq 0 ) {
    foreach ( $key in $keys.sNames ) {
    [Void] $keyListWOW64.Add((Join-Path $UNINSTALL_KEY_WOW $key))
    }
    }

    # Default to 32-bit. If there are any items in $keyListWOW64, then
    # compare the leaf items in both lists of subkeys. If the leaf items in
    # both lists match, we're seeing the Wow6432Node redirection in effect
    # and we can ignore $keyListWOW64. Otherwise, we're 64-bit and append
    # $keyListWOW64 to $keyList to enumerate both.
    $is64bit = $false
    if ( $keyListWOW64.Count -gt 0 ) {
    if ( -not (Compare-LeafEquality $keyList $keyListWOW64) ) {
    $is64bit = $true
    [Void] $keyList.AddRange($keyListWOW64)
    }
    }

    # Enumerate the subkeys.
    foreach ( $subkey in $keyList ) {
    $name = $regProv.GetStringValue($HKLM, $subkey, "DisplayName").sValue
    if ( $null -eq $name ) { continue } # skip entry if empty display name
    $output = New-Object PSObject
    $output | Add-Member "ComputerName" $computerName
    # $output | Add-Member "Subkey" (Get-Parent $subkey) # useful when debugging
    $output | Add-Member "AppID" (Get-Leaf $subkey)
    $output | Add-Member "AppName" $name
    $output | Add-Member "Publisher" $regProv.GetStringValue($HKLM, $subkey, "Publisher").sValue
    $output | Add-Member "Version" $regProv.GetStringValue($HKLM, $subkey, "DisplayVersion").sValue
    $output | Add-Member "UninstallString" $regProv.GetStringValue($HKLM, $subkey, "UninstallString").sValue
    $dateString = $regProv.GetStringValue($HKLM, $subkey, "InstallDate").sValue
    $installDate = $null
    if ( $dateString.Length -eq 8 ) {
    try {
    # Date format is assumed to by YYYYMMDD.
    $installDate = New-Object DateTime $dateString.Substring(0,4),$dateString.Substring(4,2),$dateString.Substring(6,2)
    }
    catch [Management.Automation.MethodInvocationException] {
    }
    }
    $output | Add-Member "InstallDate" $installDate
    # If subkey's name is in Wow6432Node, then the application is 32-bit.
    # Otherwise, $is64bit determines whether the application is 32-bit or
    # 64-bit.
    if ( $subkey -like "SOFTWARE\Wow6432Node\*" ) {
    $appArchitecture = "32-bit"
    }
    else {
    if ( $is64bit ) {
    $appArchitecture = "64-bit"
    }
    else {
    $appArchitecture = "32-bit"
    }
    }
    $output | Add-Member "Architecture" $appArchitecture

    # If no properties defined on command line, output the object.
    if ( $PropertyList.Keys.Count -eq 0 ) {
    $output
    }
    else {
    # Otherwise, iterate the requested properties and count the number of matches.
    $matches = 0
    foreach ( $key in $PropertyList.Keys ) {
    if ( $output.$key -like $PropertyList.$key ) {
    $matches += 1
    }
    }
    # If all properties matched, output the object.
    if ( $matches -eq $PropertyList.Keys.Count ) {
    $output
    # If -MatchAll is missing, don't enumerate further.
    if ( -not $MatchAll ) { break }
    }
    }
    }
    }
    catch [Management.Automation.RuntimeException] {
    Write-Error $_
    }
    }
    }

    process {
    foreach ( $computerNameItem in $ComputerName ) {
    GetInstalledApp $computerNameItem
    }
    }