The PowerShell script introduced in this post allows you to easily list all installed programs on remote computers.

To quickly check what software is installed on a computer, you can remote into the console of a client or server and bring up the Programs and Features control panel applet. Of course, you can also use a software inventory tool. The advantage of using PowerShell for this task is that you can further process the output of your script to perform additional tasks.

By building a PowerShell function, you can reduce that process of accessing the console of a remote computer and pointing and clicking with the mouse to simply running a single line of code that will generate a list of every piece of software installed on a local or remote computer. But before you can do that, you need to write that function. Let's see how that's done.

First of all, it's important to know where exactly the software list is stored. Depending on the way in which the software installed, the software can be found in one of three different registry keys: HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall or
HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
for machine-based installs or
HKU:\<UserSID>\Software\Microsoft\Windows\CurrentVersion\Uninstall
for user-based installs. The HKU registry key will only be available if a user is logged in. Otherwise, you will only see one of the HKLM registry keys.

Let's first figure out a way to check for and enumerate each of the values inside these registry keys. To do that, I'll need to start creating a scriptblock containing all of the code that will be executed on the remote computer. I'll use this code to wrap up into a scriptblock when we're done to pass to Invoke-Command to run on the remote computer.

The first step is to create an array of each machine-based registry path. This will allow me to query each key easily later.

$keys = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'

Next, I need to figure out if there's any user (or multiple users) logged on and, if so, get the registry key path to the location where software might be installed. To do that, I'll need to enumerate all of the registry keys under the HKEY_USERS hive. But unfortunately, that registry hive is not loaded by default, so I'll need to first do that.

New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null

I can now look for keys that have user SIDs in them and add them to the array I created earlier.

$keys += Get-ChildItem HKU: | where { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | foreach { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }
Looking for keys that have a user SID in them

Looking for keys that have a user SID in them

I now need to search through each of those registry keys for keys that have the DisplayName value inside of them. Once I do that, I'll grab all of the registry values inside of each key. These are the attributes for each piece of software.

@($keys).foreach({
@(Get-ChildItem -Path $_ -ErrorAction SilentlyContinue).where({ $_.GetValue('DisplayName') }).foreach({ 
	$key = $_
$_.GetValueNames().foreach({
	$valName = $_
	if ($valName) {
			[string]$valData = $key.GetValue($valName)
		}
}) 
})
})

Finally, I now need to output an object for each software instance. I'll do this by using each registry value's name as a property and the actual data for the property value.

@($keys).foreach({
		$swInstance = @{ }
		@(Get-ChildItem -Path $_ -ErrorAction SilentlyContinue).where({ $_.GetValue('DisplayName') }).foreach({
				$key = $_
				$_.GetValueNames().foreach({
						$valName = $_
						if ($valName) {
							[string]$valData = $key.GetValue($valName)
							$swInstance[$valName] = $valData
						}
					})
			})
		[pscustomobject]$swInstance
	})
Using each registry values name as a property and the actual data for the property value

Using each registry values name as a property and the actual data for the property value

I now have all the code I need to execute on the remote computer. Next, I'll wrap up all of this code into a scriptblock and execute it on the remote computer.

$scriptBlock = {
	$keys = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall','HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall'
	$keys += Get-ChildItem HKU: | where { $_.Name -match 'S-\d-\d+-(\d+-){1,14}\d+$' } | foreach { "HKU:\$($_.PSChildName)\Software\Microsoft\Windows\CurrentVersion\Uninstall" }
	New-PSDrive -Name HKU -PSProvider Registry -Root Registry::HKEY_USERS | Out-Null
	@($keys).foreach({
		$swInstance = @{}
		@(Get-ChildItem -Path $_ -ErrorAction SilentlyContinue).where({ $_.GetValue('DisplayName') }).foreach({ 
			$key = $_
			$_.GetValueNames().foreach({
				$valName = $_
				if ($valName) {
				[string]$valData = $key.GetValue($valName)
				$swInstance[$valName] = $valData
				}
			})
		})
	[pscustomobject]$swInstance
 	})
}
Invoke-Command –ComputerName CLIENT1 –ScriptBlock $scriptBlock

You will now get a list of each piece of software installed on the remote computer along with a lot of useful attributes associated with each instance.

Subscribe to 4sysops newsletter!

If you'd rather not build your own code to do this, I've already built a function called Get-InstalledSoftware that uses this method. It is built as a function that allows you to query one or more computers and includes logging and error handling as well.

8 Comments
  1. Melvin Backus 5 years ago

    I gave this a quick try and while I do get results, the list seems to be horribly incomplete as compared to what shows up via other methods.  I ran it on a couple of my servers and in both cases I got 2 packages listed while the results of a WMI query on those same servers listed over 2 dozen packages.

  2. John 5 years ago

    Lines 3 and 4 should be swapped in your last code box.

  3. Frank@TI 5 years ago

    Hi,

    get this
    hello
    Method invocation failed because [System.String] doesn't contain a method named 'foreach'.
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodNotFound
    + PSComputerName : pc0013

    Windows 7 SP1

  4. Frank@TI 5 years ago

    hi,

    get this error:

    Connecting to remote server pc0013 failed with the following error message : Access is denied. For more information, see the
    about_Remote_Troubleshooting Help topic.
    + CategoryInfo : OpenError: (pc0013:String) [], PSRemotingTransportException
    + FullyQualifiedErrorId : AccessDenied,PSSessionStateBroken

  5. Peter Barnett 4 years ago

    Here are other ways how to get list of installed software on a remote computer:

    https://www.action1.com/kb/list_of_installed_software_on_remote_computer.html

     

  6. Hi Adam,

    I know this is an old post, but I recently hit it and aslo checked the code you provide on GitHub.

    Great job, thanks!

    L

  7. Just one little thing. The code provided does not work against multiple computers. In the code you have defined:

    	param (
    		
    		[Parameter()]
    		[ValidateNotNullOrEmpty()]
    		[string]$ComputerName,

    which only limits the function to a single PC.

    I added 

    	param (
    		
    		[Parameter()]
    		[ValidateNotNullOrEmpty()]
    		[string[]]$ComputerName,

    and it all works great against multiple PCs.

    Cheers!

Leave a reply

Please enclose code in pre tags

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

*

© 4sysops 2006 - 2021

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