Win32_Product WMI Queries are Bad!
Just a note for SysAdmins out there writing scripts or automating all the things: never query the Win32_Product class to find software installed on devices. There's better ways, and querying this can/will cause problems!
Why?
Because querying this class in WMI will cause trigger repair installs on every application Windows Installer ever installed. That means every MSI ever installed on the system will perform an automatic/silent repair install - which is bad if things were customized in an install script after the MSI ran, as it will put it back to default state. This can also cause problems if the msi was installed on a network share that is now unreachable (you should never install msi's from network shares anyway). Overall - avoid this.
A Better Way
If you have ConfigMgr in your environment, you can query the Win32Reg_AddRemovePrograms namespace instead! This will provide the info you are looking for.
Another Better Way
You can also write your own loop to iterate through uninstall keys in the registry. You will need to include both 32-bit and 64-bit uninstall locations in order to have a complete inventory here. I am including an example of this below:
$TotalSoftware = new-object System.Collections.ArrayList $TotalSoftwareList = new-object System.Collections.ArrayList $RegPathList = New-Object System.Collections.ArrayList $RegPathList.Add("SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall") | out-null $RegPathList.Add("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall") | out-null # Evaluate Registry for installed software foreach ($RegPath in $RegPathList) { try { $RegObject = [microsoft.win32.registrykey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Default) $RegKey = $RegObject.OpenSubKey($RegPath) foreach ($Software in $RegKey.GetSubKeyNames()) { $SoftwarePath = "$RegPath\\$Software" $SoftwareObject = [microsoft.win32.registrykey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Default) $SoftwareKey = $RegObject.OpenSubKey($SoftwarePath) if ($SoftwareKey.GetValue("DisplayName")) { $DisplayName = $SoftwareKey.GetValue("DisplayName") } else { $DisplayName = "Data Not Available" } if ($SoftwareKey.GetValue("DisplayVersion")) { $Version = $SoftwareKey.GetValue("DisplayVersion") } else { $Version = "0.0.0.0" } if ($SoftwareKey.GetValue("Publisher")) { $Publisher = $SoftwareKey.GetValue("Publisher") } else { $Publisher = "Data Not Available" } $SoftwareObject = New-Object -TypeName PSObject $SoftwareObject | Add-Member -MemberType NoteProperty -Name DisplayName -Value $DisplayName $SoftwareObject | Add-Member -MemberType NoteProperty -Name Version -Value $Version $SoftwareObject | Add-Member -MemberType NoteProperty -Name Publisher -Value $Publisher if ($TotalSoftwareList -notcontains $DisplayName) { $TotalSoftware.Add($SoftwareObject) | Out-Null } $TotalSoftwareList.Add($DisplayName) | Out-Null } } catch { # Nothing } }