The GUID is what uniquely identifies a piece of installed software among all of the other pieces of software on a computer. By creating a simple PowerShell script, and using a little registry-fu, we can create a function that easily extracts this information.

Software management in Windows has always been a painful ordeal. It seems like every piece of software installs differently! But, usually, they have one thing in common – the product GUID.

First, you'll need to know where a product's GUID is located. Spoiler alert: it's in the registry.

The registry is a big place though. We're looking for two (or more) keys in the following paths:

HKEY_LOCAL_MACHINE:\Software\Microsoft\Windows\CurrentVersion\Uninstall

HKEY_LOCAL_MACHINE:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall

These are where each piece of software places its keys when installed on the system. If the user decided to install the software under a user context, you'd find the registry keys here:

HKEY_USERS:\<User GUID>\Software\Microsoft\Windows\CurrentVersion\Uninstall

In these registry paths, you'll find the keys that represent each piece of software installed on a Windows PC.

'Uninstall' registry keys

'Uninstall' registry keys

You'll notice that some key names start with a GUID while others don't. This is what I mean about no standards! The type of installer is what dictates the changes to the PC that the installer makes.

We can use the Get-ChildItem cmdlet to query the registry keys and extract the GUID from each of the paths mentioned above. Notice in the screenshot below that I can narrow down the output to only those keys with a GUID, using the PSChildName property.

Displaying product GUIDs

Displaying product GUIDs

This is great, but not much use because I can't see which software title relates to each GUID. It'd also be nice if we could enter a title and then be returned a single GUID.

So let's create a script for this.

First, we need to pull together the results from each of the registry paths. You can see below that I'm creating an array from each registry path. Since there could be multiple users on a PC, I'm forced to manually mount the HKEY_USERS PS drive and check each of those paths as well. This gives me a full picture of every piece of software installed on the system.

$UninstallKeys = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
$null = New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS
$UninstallKeys += Get-ChildItem HKU: -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | ForEach-Object { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }

Once I’ve collected the keys from all of the paths, I need to loop through them and extract the registry value that represents the title from each of the keys. I do this by running Get-ChildItem on each of the keys, limiting it to only the keys named 'GUID'. I then use calculated properties to return both the GUID property and the name property, by using the GetValue() method on each key to extract the name of the software.

foreach ($UninstallKey in $UninstallKeys) {
    Get-ChildItem -Path $UninstallKey -ErrorAction SilentlyContinue | Where {$_.PSChildName -match '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$'} | Select-Object @{n='GUID';e={$_.PSChildName}}, @{n='Name'; e={$_.GetValue('DisplayName')}}
}

Once I have this code in place, I get output that looks like this:

Clean output of GUID and software name

Clean output of GUID and software name

At this point, I could be done if I wanted, but I'm usually not looking for all of the software titles' GUIDs. I only want one.

This is a great place to create a function, so I've done exactly that. The function is called Get-InstalledSoftware and pulls all of this logic together to allow us to pass a software title to a function and return the software’s GUID:

function Get-InstalledSoftware {
    <#
    .SYNOPSIS
        Retrieves a list of all software installed
    .EXAMPLE
        Get-InstalledSoftware
        
        This example retrieves all software installed on the local computer
    .PARAMETER Name
        The software title you'd like to limit the query to.
    #>
    [OutputType([System.Management.Automation.PSObject])]
    [CmdletBinding()]
    param (
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )

    $UninstallKeys = "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
    $null = New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS
    $UninstallKeys += Get-ChildItem HKU: -ErrorAction SilentlyContinue | Where-Object { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | ForEach-Object { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }
    if (-not $UninstallKeys) {
        Write-Verbose -Message 'No software registry keys found'
    } else {
        foreach ($UninstallKey in $UninstallKeys) {
            if ($PSBoundParameters.ContainsKey('Name')) {
                $WhereBlock = { ($_.PSChildName -match '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$') -and ($_.GetValue('DisplayName') -like "$Name*") }
            } else {
                $WhereBlock = { ($_.PSChildName -match '^{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}$') -and ($_.GetValue('DisplayName')) }
            }
            $gciParams = @{
                Path        = $UninstallKey
                ErrorAction = 'SilentlyContinue'
            }
            $selectProperties = @(
                @{n='GUID'; e={$_.PSChildName}}, 
                @{n='Name'; e={$_.GetValue('DisplayName')}}
            )
            Get-ChildItem @gciParams | Where $WhereBlock | Select-Object -Property $selectProperties
        }
    }
}

Using this Get-InstalledSoftware function, I can now use a single command to query all registry paths for the GUID for a single software title.

Subscribe to 4sysops newsletter!

Querying for a single software title

Querying for a single software title

avataravataravatar
10 Comments
  1. Alex Paper (Rank 2) 5 years ago

    Great work, very usefull!

  2. Melvin Backus 5 years ago

    OK, I’m assuming it’s something stupid I’m doing, or not doing, but I’m obviously missing something here. Saved the script, called with the name parameter and without. No output in either case. No errors, nothing. What am I missing?

  3. Scott L. (Rank 2) 5 years ago

    Awesome, works great, saved me the time to build my own function, thanks again. 🙂

  4. EMIL INDYGO (Rank 1) 5 years ago

    Good staff thanks!

  5. Preguntón Cojonero (@preguntoncabron) 5 years ago

    PC inventory ? using GPO and PS script ? asynchronous for not delay logon ?

    alternatives ?

  6. Steve Watson 4 years ago

    As an alternative, I find the following PS one liner easier to use rather than parse the registry.

    Get-WMIObject Win32_Product | Where {$_.Name -like "*Office*"} | FT Name,IdentifyingNumber

  7. Ken Skinner 4 years ago

    That works great, thank you, Adam!

    I would add that once you set up the function by copying the script into PowerShell and hit enter, you won't see anything happen yet. Then by calling the function and putting a space and ? in after the -name part and hit enter you get all of the software installed by name. Then you can copy the full name and version, hit the up key at the prompt and paste the name in and you get the single GUID you want. 

    The next thing I would like to know is how to put all of this into a .bat file that will open PowerShell, create the GetSoftwareInstalled function, pull up the list of installed software and prompt me for a name. Does anybody know how to do that? 

  8. Using Get-WMIObject -class Win32_Product is a bad idea.  If available, it will trigger the repair function of the installation, possibly resetting things.  It's also incredibly slow compared to parsing the registry. 

    David F. 

     

  9. Richard Neff 3 years ago

    Nice work Adam. I was able to use the * wildcard and get a list of all product modules from the same vendor.

  10. Richard Neff 3 years ago

    Adam,

      Can this be revised to enter a vendor name instead to ensure I get all software from the same vendor and can it be run on a remote computer?  I just realized not all software display names have the vendor name.within them.

Leave a reply

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2023

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account