PowerShell for Software Inventory

First, Win32_Product is cancer. Despite the name implying it’s a read-only operation it will silently execute repair installations for any software failing an integrity check. Avoid using it because:

  1. This can introduce unintended and unscheduled change in a controlled environment; and

  2. It is unacceptable for a read-only operation to result in a changed state; and

  3. Even if it doesn’t cause an outage due to re-installation, the integrity check can be CPU and disk intensive as can the re-installation of software, causing resource contention with other applications running on the system.

MS has previously stated this is a “won’t fix” issue. Since it’s how the class has behaved for so long, “fixing” it could break folks who do rely on the erroneous behavior today.


By repurposing some of the code in my linked answer above we can instead check the registry for the software inventory, get the current computer name, and return a [hashtable] with the computer name as the key and its value being an array of installed software on the system:

# We need to check for both 64-bit and 32-bit software
$regPaths = "HKLM:\SOFTWARE\Wow6432node\Microsoft\Windows\CurrentVersion\Uninstall",
  "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"

# Get the name of all installed software registered in the registry
$softwareInventory = @{
 $env:ComputerName = $regPaths | Foreach-Object {
    ( Get-ItemProperty "${_}\*" DisplayName -EA SilentlyContinue ).DisplayName
  }
}

Note: This will not capture software not installed using Microsoft Installer, but most software is installed via MSIs (often .exe installers simply wrap around .msi installers).

$softwareInventory is now a hashtable with the computer name as the key. So once this is collected from a server/computer, you can reference the software inventory of a computer named PlanetExpressServer01 like so:

$softwareInventory['PlanetExpressServer01']

# OR (must still wrap property name in quotes for specially parsed characters
# such as the hyphen (-)

$softwareInventory.PlanetExpressServer01
$softwareInventory.'PlanetExpressServer-02'

If you instead want the computer name to be its own property, not the key of a hashtable, we can make one more adjustment when creating $softwareInventory:

$softwareInventory = [PSCustomObject]@{
  ComputerName = $env:ComputerName
  Software = $regPaths | Foreach-Object {
    ( Get-ItemProperty "${_}\*" DisplayName -EA SilentlyContinue ).DisplayName
  }
}

Simply create a new hashtable key called ComputerName, assign the $env:ComputerName to it, put the software inventory under a new key called Software, and cast the hashtable to a PSCustomObject so it operates more like a conventional object than an array. Now each “row” will have both a ComputerName “column” and Software “column”.


Additional Information

For additional information on why *Win32_Product is bad, the following article is a good read, and offers additional techniques to avoid its use:

Please Stop Using Win32_Product To Find Installed Software

Thanks to @FoxDeploy for the link from a now-deleted question.

Leave a Comment