- Create a certificate-signed RDP shortcut via Group Policy - Fri, Aug 9 2019
- Monitor web server uptime with a PowerShell script - Tue, Aug 6 2019
- How to build a PowerShell inventory script for Windows Servers - Fri, Aug 2 2019
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" }
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 })
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.
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.
Lines 3 and 4 should be swapped in your last code box.
There was a wrong line break in the code box. It should be okay now.
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
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
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
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
Just one little thing. The code provided does not work against multiple computers. In the code you have defined:
which only limits the function to a single PC.
I added
and it all works great against multiple PCs.
Cheers!