Description

Allows you get some information about installed software on a Local or Remote Computer or a List of computers.

This is best viewed using Export-CSV or Out-GridView.

Source Code

#\/******/\********\/********/\********\/********/\********\/********/\******\/******\/******\/
#/\()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()/\()()()/\()()()/\
#\/()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()\/()()()\/()()()\/
#/\******\/********/\********\/********/\********\/********/\********\/******/\******/\******/\
#\-\
#-\-Author:  Chris Rakowitz
#\-\Purpose:  Returns a list of programs installed on a Target Machine.
#/-/Date:  August 28, 2015
#-/-Updated:  September 14, 2016 (2.00)
#/-/			[+] Re-wrote the script to use the Registry instead of WMI Queries.
#\-\			[+] Script is substantially quicker.
#-\-			[+] Sorts output by Name making it easier to locate specific software.
#\-\			[+] Now checks if PC is online before attempting to retrieve data.
#/-/			[+] Updated Help information.
#-/-		  August 4, 2017 (2.01)
#/-/			[+] Script now detects if a machine is x86 or x64.
#\-\		  September 6, 2017 (2.02)
#-\-			[+] Corrected issue with getting installed software from 32-bit machines.
#\-\		  October 11, 2017 (2.03)
#/-/			[+] Added Progress Bars.
#-/-Version:  2.03
#/-/
#\/******/\********\/********/\********\/********/\********\/********/\******\/******\/******\/
#/\()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()/\()()()/\()()()/\
#\/()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()()\/()()()()/\()()()\/()()()\/()()()\/
#/\******\/********/\********\/********/\********\/********/\********\/******/\******/\******/\

<#
	.SYNOPSIS
	Extract information for all software installed in a computers registry.
	
	.DESCRIPTION
	This script gets software information by querying the registry Keys:

	64-Bit
	HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
	32-Bit
	HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
	
	The output is stored as a sorted array of objects using the Sort-object cmdlet
	so they can be exported easily to .csv files, the Format-List, Format-Table, Out-GridView cmdlets, etc.
	
	Running the script without any input will cause it to be run on the local machine.  Text files,
	comma-separated lists and single computers or IP Addresses can be provided to target other machines.
	
	The Remote Registry service is required to be enabled in order to extract information from a remote computer.
	This script will enable this service, extract the information, and then stop and disable the service.
	
	Provides the following information:
	Computer Name, Registry Key for the Software, Software Name, Software Publisher, Install Date,
	Software Version, Architecture(x86/x64), Install Source and the Uninstall String to remove the software.
	
	.PARAMETER ComputerName
	Targets a single computer, a comma-separated list of computers or a text file with
	a list of computer.  Each computer must be on a separate line in the text file.
	Also accepts IP Addresses.
	
	.EXAMPLE
	Get-InstalledSoftware
	
	Get information about installed software on the local machine.
	
	.EXAMPLE
	Get-InstalledSoftware Test-1234
	
	Get information about installed software for a remote machine.
	
	.EXAMPLE
	Get-InstalledSoftware Test-1234, Test-2345, Test-3456, Test-4567
	
	Get software information for each of the computers supplied in the
	comma-separated list.
	
	.EXAMPLE
	Get-InstalledSoftware "C:\My Directory\mylistoffiles.txt"
	
	Get information about installed software from a list of machines.
	
	.EXAMPLE
	Get-InstalledSoftware "C:\Another Directory\list3.txt" | Export-CSV "C:\SoftwareList.csv"
	
	Get software information from a list of computers and export it
	to a csv file for later viewing.
	
	.EXAMPLE
	Get-InstalledSoftware | Sort-Object -Property ProductName | Out-GridView
	
	Get a full list of software and sort it by Product Name before displaying it with
	Out-GridView.
	
	.NOTES
	This is required to be run as a local administrator or as a Domain account 
	that has Administrator rights on the target computer or computers.
#>


param
(
	[Parameter(ValueFromPipelineByPropertyName=$true,ValueFromPipeline=$true,`
	Mandatory=$False,Position=0)] [string[]]$ComputerName = $env:computername
)
# Tell Powershell to ignore any errors that may fill up the screen.
$ErrorActionPreference = 'silentlycontinue'

# Allows the Script to accept a text file list of computers or IP Addresses as input.
# Each item must be on its own line.
If($ComputerName -like "*.txt")
{
	$CompList = Get-Content ([regex]::matches($ComputerName,'[^\"]+') | %{$_.value})
}
Else
{
	$CompList = $ComputerName
}

[int]$CompCount = $CompList.Count
[int]$CCount = 0

Foreach($Computer in $CompList)
{
	$CCount++
	Write-Progress -Activity "Installed Software Scan" -Status "Checking Computer [$CCount / $CompCount]: $Computer" `
		-PercentComplete (($CCount / $CompCount) * 100) -Id 1
	
	# Check to make sure if computer is online.  Skip PC if not online.
	If(!(Test-Connection -ComputerName $Computer -TimeToLive 18 -Count 1))
	{
		Write-Host "$Computer - Offline" -Foreground red -Background black
		Write-Host ""
		Write-Progress -Activity "Installed Software Scan" -Status "Complete" -Completed -Id 1
		continue
	}
	Else
	{
		# Get the Computers Name.  This is helpful if a single IP address or list of
		# IP Addresses is provided.
		$CompName = (Get-WMIObject -Class Win32_ComputerSystem -ComputerName $Computer).Name
		
		# Get the Computers Archtecture (x86 or x64).
		$Architecture = (Get-WMIObject -Class Win32_ComputerSystem -ComputerName $Computer).SystemType
	
		# Enable the Remote Registry service on the target computer.  This allows keys to be read.
		# Checks if computer is a Local Computer.
		If($Computer -NotLike "*$env:computername*")
		{
			Set-Service -ComputerName $Computer -StartUpType Manual -Status Running `
				-Name RemoteRegistry -DisplayName "Remote Registry"
		}
		
		# Array to hold the software information objects.
		$ProgramsList = @()
		
		# This is the HKLM Registry Hive on a target computer.
		$HKLMBaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBasekey('LocalMachine', "$Computer")
		
		# Get the Uninstall keys for the 64-bit software installed on the target computer.
		# This key will get the x86 software from 32-bit machines.
		# $False specifies that the key cannot be modified and is Read-Only.
		$UninstallKeyx64 = $HKLMBaseKey.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", $False)
		$UninstallListx64 = $UninstallKeyx64.GetSubKeyNames()
		
		# Get the Uninstall keys for the 32-bit software installed on 64-bit computers.
		# $False specifies that the key cannot be modified and is Read-Only.
		$UninstallKeyx86 = $HKLMBaseKey.OpenSubKey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall", $False)
		$UninstallListx86 = $UninstallKeyx86.GetSubKeyNames()
		
		# For 64-bit machines get all the software from the 32-bit and 64-bit portions of the registry.
		If($Architecture -Like "*x64*")
		{
			[int]$x64Count = $UninstallListx64.Count
			[int]$x86Count = $UninstallListx86.Count
			[int]$Total = $UninstallListx64.Count + $UninstallListx86.Count
			[int]$PCount = 0
			# Extract desired information from the 64-bit Uninstall registry keys.
			Foreach($Key in $UninstallListx64)
			{
				$Value = $HKLMBaseKey.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$Key", $False)
				
				# Skip keys that have no values in them.
				If($Value.GetValue("DisplayName") -Like "")
				{
					$PCount++
					continue
				}
				# Skip keys that are just Windows Updates.
				ElseIf($Value.GetValue("DisplayName") -Like "*(KB*)*")
				{
					$PCount++
					continue
				}
				Else
				{
					$PCount++
					$Name = $Value.GetValue("DisplayName")
					
					Write-Progress -Activity "Software" -Status "Found [$PCount / $Total]: $Name" `
						-PercentComplete (($PCount / $Total) * 100) -Id 2
					
					$ObjProgram = New-Object PSObject
					$ObjProgram | Add-Member -MemberType NoteProperty -Name ComputerName -Value $CompName
					$ObjProgram | Add-Member -MemberType NoteProperty -Name RegistryKey -Value $Key
					$ObjProgram | Add-Member -MemberType NoteProperty -Name DisplayName -Value $Value.GetValue("DisplayName")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Publisher -Value $Value.GetValue("Publisher")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallDate -Value $Value.GetValue("InstallDate")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Version -Value $Value.GetValue("DisplayVersion")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Architecture -Value "x64"
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallSource -Value $Value.GetValue("InstallSource")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name UninstallString -Value $Value.GetValue("UninstallString")
					
					# Add the Program Info to the array.
					# This allows for easier use of Out-GridView.
					$ProgramsList += $ObjProgram 
				}
			}
			
			# Extract desired information from the 32-bit Uninstall registry keys.
			Foreach($Key in $UninstallListx86)
			{
				$Value = $HKLMBaseKey.OpenSubKey("SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$Key", $False)
				
				# Skip keys that have no values in them.
				If($Value.GetValue("DisplayName") -Like "")
				{
					$PCount++
					continue
				}
				# Skip keys that are just Windows Updates.
				ElseIf($Value.GetValue("DisplayName") -Like "*(KB*)*")
				{
					$PCount++
					continue
				}
				Else
				{
					$PCount++
					$Name = $Value.GetValue("DisplayName")
					
					Write-Progress -Activity "Software" -Status "Found [$PCount / $Total]: $Name" `
						-PercentComplete (($PCount / $Total) * 100) -Id 2
				
					$ObjProgram = New-Object PSObject
					$ObjProgram | Add-Member -MemberType NoteProperty -Name ComputerName -Value $CompName
					$ObjProgram | Add-Member -MemberType NoteProperty -Name RegistryKey -Value $Key
					$ObjProgram | Add-Member -MemberType NoteProperty -Name DisplayName -Value $Value.GetValue("DisplayName")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Publisher -Value $Value.GetValue("Publisher")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallDate -Value $Value.GetValue("InstallDate")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Version -Value $Value.GetValue("DisplayVersion")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Architecture -Value "x86"
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallSource -Value $Value.GetValue("InstallSource")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name UninstallString -Value $Value.GetValue("UninstallString")
					
					# Add the Program Info to the array.
					# This allows for easier use of Out-GridView.
					$ProgramsList += $ObjProgram
				}
			}
			Write-Progress -Activity "Software" -Status "Complete" -Completed -Id 2
		}
		# For 32-bit machines get the software that is stored in the registry.
		ElseIf($Architecture -Like "*x86*")
		{
			[int]$x86Count = $UninstallListx64.Count
			[int]$PCount = 0
			
			# Extract desired information from the 32-bit Uninstall registry keys.
			Foreach($Key in $UninstallListx64)
			{
				$Value = $HKLMBaseKey.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\$Key", $False)
				
				# Skip keys that have no values in them.
				If($Value.GetValue("DisplayName") -Like "")
				{
					$PCount++
					continue
				}
				# Skip keys that are just Windows Updates.
				ElseIf($Value.GetValue("DisplayName") -Like "*(KB*)*")
				{
					$PCount++
					continue
				}
				Else
				{
					$PCount++
					$Name = $Value.GetValue("DisplayName")
					
					Write-Progress -Activity "Software" -Status "Found [$PCount / $x86Count]: $Name" `
						-PercentComplete (($PCount / $Total) * 100) -Id 2
						
					$ObjProgram = New-Object PSObject
					$ObjProgram | Add-Member -MemberType NoteProperty -Name ComputerName -Value $CompName
					$ObjProgram | Add-Member -MemberType NoteProperty -Name RegistryKey -Value $Key
					$ObjProgram | Add-Member -MemberType NoteProperty -Name DisplayName -Value $Value.GetValue("DisplayName")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Publisher -Value $Value.GetValue("Publisher")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallDate -Value $Value.GetValue("InstallDate")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Version -Value $Value.GetValue("DisplayVersion")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name Architecture -Value "x86"
					$ObjProgram | Add-Member -MemberType NoteProperty -Name InstallSource -Value $Value.GetValue("InstallSource")
					$ObjProgram | Add-Member -MemberType NoteProperty -Name UninstallString -Value $Value.GetValue("UninstallString")
					
					# Add the Program Info to the array.
					# This allows for easier use of Out-GridView.
					$ProgramsList += $ObjProgram
				}
			}
			Write-Progress -Activity "Software" -Status "Complete" -Completed -Id 2
		}
		
		# Display the Information on screen sorted by "DisplayName".
		$ProgramsList | Sort-Object -Property "DisplayName"
		
		# Stop and Disable the Remote Registry service.
		# Checks if computer is a Local Computer.
		If($Computer -NotLike "*$env:computername*")
		{
			(Get-Service -ComputerName $Computer -Name RemoteRegistry).stop()
			Set-Service -ComputerName $Computer -StartUpType Disabled -Name RemoteRegistry
		}

		Write-Progress -Activity "Installed Software Scan" -Status "Complete" -Completed -Id 1
	}
}
1 Spice up

Just so you know, Win32_Product is evil: Win32_Product Is Evil. | Greg's Systems Management Blog (and so is this 10 word minimum)

Win32 has a other really bad thing… it only displays you the Software that is installed as an MSI. So you couldn´t find other Software. If you take a look in a few other Scripts für Softwareinventory (like https://community.spiceworks.com/scripts/show/2170-get-a-list-of-installed-software-from-a-remote-computer-fast-as-lightning?page=3 ) you can see that this is realized with a Registryscan. It is very fast.

You are right, I have done some research and re-vamped this script to get more accurate information. It produces some duplicates, but it works better at least.