How to create a Software Inventory Report using powershell (step by step)

There are different ways to obtain the list of the installed software on a computer, usually are wmi query using  the Win32_Product class, but therea ar an alternative: read directly the registry

Get the software installed on the local computer

Open a powershell as administrator

Paste the following code: Get-CimInstance -Class Win32_Product

The first thing you realize, the command give you a lot of information and is very slow

These are the fields returned br the command for an installed software
Name                  : PowerShell 7-x64
Version               : 7.1.3.0
InstallState          : 5
Caption               : PowerShell 7-x64
Description           : PowerShell 7-x64
IdentifyingNumber     : {A6307460-5CB8-47E2-91FE-A35552EA2C39}
SKUNumber             :
Vendor                : Microsoft Corporation
AssignmentType        : 1
HelpLink              : https://github.com/PowerShell/PowerShell
HelpTelephone         :
InstallDate           : 20210319
InstallDate2          :
InstallLocation       :
InstallSource         : C:\Users\gas\Downloads\
Language              : 1033
LocalPackage          : C:\WINDOWS\Installer\11b2728f.msi
PackageCache          : C:\WINDOWS\Installer\11b2728f.msi
PackageCode           : {6488FC90-3CAB-4246-A212-1AD7BDE685F7}
PackageName           : PowerShell-7.1.3-win-x64.msi
ProductID             :
RegCompany            :
RegOwner              :
Transforms            :
URLInfoAbout          :
URLUpdateInfo         :
WordCount             : 0
PSComputerName        :
CimClass              : root/cimv2:Win32_Product
CimInstanceProperties : {Caption, Description, IdentifyingNumber, Name…}
CimSystemProperties   : Microsoft.Management.Infrastructure.CimSystemProperties

We don’t want all information, but only some fields…

Now we select only the info wanted

Name,Version,Caption,IdentifyingNumber,Vendor,description

Paste the following code: Get-CimInstance -Class Win32_Product|select-object Name,Version,Caption,IdentifyingNumber,Vendor,description,installState

And this is the output

Some note about the field InstallState, should be useful

-6

Bad Configuration

-2

Invalid Argument

-1

Unknown Package

1

Advertised

2

Absent

5

Installed

Now we try to find the software installed on a remote computer, returning only the fields wanted

Get-CimInstance -Class Win32_Product -ComputerName RemotePcName|select-object Name,Version,Caption,IdentifyingNumber,Vendor,description,installState,PScomputerName

Note: if you get some error, probably you don’t have the permission on the remote computer or the firewall block the request, you can do some troubleshooting using WBEMTest.exe or read this link https://docs.microsoft.com/en-us/windows/win32/wmisdk/connecting-to-wmi-remotely-starting-with-vista

Now we try to query more computers
‘server1’,’server2’,’pc01’| % {Get-CimInstance -Class Win32_Product -ComputerName $_}|select-object Name,Version,Caption,IdentifyingNumber,Vendor,description,installState,PScomputerName

If you are on a server and have Active directory cmdlets, this command will return all computer that have MyApp installed on a specific OU

$OUpath = 'OU=test,DC=contoso,DC=com'
Get-ADComputer -Filter * -SearchBase $OUpath | 
 ForEach-Object {
      Get-CimInstance -Class Win32_Product -computername $_.Name |
          Where-Object {$_.Name -like "*MyApp*"}    }


CSV report creation

# Make Report folder
Mkdir c:\report

# Local PC Software Inventory Report creation
Get-CimInstance -Class Win32_Product | 
    select-object Name,Version,Caption,IdentifyingNumber,Vendor,description,installState,PScomputerName
       Export-Csv -UseCulture -NoTypeInformation -LiteralPath c:\Report\LocalInventorySwReport.csv



$OUpath
= 'OU=test,DC=contoso,DC=com'
Get-ADComputer -Filter * -SearchBase $OUpath |
ForEach-Object {
   Get-CimInstance -Class Win32_Product -computername $_.Name |
      Where-Object {$_.Name -like "*MyApp*"}|
            select-object  
Name,Version,Caption,IdentifyingNumber,Vendor,description,installState,PScomputerName |
                 Export-Csv -UseCulture -NoTypeInformation -LiteralPath c:\Report\OUInventorySwReport.csv

}

Measures the time it takes to run our script

In this example measures the time it takes to run the   Get-CimInstance -Class Win32_Product command that gets the software installed on the local computer


Paste the following code in the powershell windows :  
  Measure-Command {Get-CimInstance -Class Win32_Product} 

The output:

Days              : 0
Hours             : 0
Minutes           : 0
Seconds           : 27
Milliseconds      : 959
Ticks             : 279596631
TotalDays         : 0,000323607211805556
TotalHours        : 0,00776657308333333
TotalMinutes      : 0,465994385
TotalSeconds      : 27,9596631
TotalMilliseconds : 27959,6631

 

We want to be faster

The Win32_product class is not query optimized. Queries such as “select * from Win32_Product where (name like ‘Sniffer%’)” require WMI to use the MSI provider to enumerate all of the installed products and then parse the full list sequentially to handle the “where” clause…

https://devblogs.microsoft.com/scripting/use-powershell-to-quickly-find-installed-software/

The solution is read the registry

function Get-installedApps {
param( $computers=$env:computername  )
        $array = @()
        foreach($pc in $computers){
           $computername=$pc
           #Define the variable to hold the location of Currently Installed Programs
           $unistalKeys=@("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall","SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")
      
          $unistalKeys| ForEach-Object{
              $UninstallKey=$_
              #Create an instance of the Registry Object and open the HKLM base key
              $reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey(‘LocalMachine’,$computername) 
              #Drill down into the Uninstall key using the OpenSubKey Method
              $regkey=$reg.OpenSubKey($UninstallKey) 
              #Retrieve an array of string that contain all the subkey names
              $subkeys=$regkey.GetSubKeyNames() 
              #Open each Subkey and use GetValue Method to return the required values for each
              foreach($key in $subkeys){
                  $thisKey=$UninstallKey+”\\���+$key 
                  $thisSubKey=$reg.OpenSubKey($thisKey) 
                  $obj = New-Object PSObject
                  $obj | Add-Member -MemberType NoteProperty -Name “ComputerName” -Value ($computername).ToUpper()
                  $obj | Add-Member -MemberType NoteProperty -Name “DisplayName” -Value $($thisSubKey.GetValue(“DisplayName”))
                  $obj | Add-Member -MemberType NoteProperty -Name “DisplayVersion” -Value $($thisSubKey.GetValue(“DisplayVersion”))
                  $obj | Add-Member -MemberType NoteProperty -Name “InstallLocation” -Value $($thisSubKey.GetValue(“InstallLocation”))
                  $obj | Add-Member -MemberType NoteProperty -Name “Publisher” -Value $($thisSubKey.GetValue(“Publisher”))
                  $array += $obj
                  } 
             }
       }
  $array |sort-object  -Property displayname| Select-Object * -unique
}

Get-installedApps

# GET DURATION
measure-comman {Get-installedApps}

Ticks             : 4154470
Days              : 0
Hours             : 0
Milliseconds      : 415
Minutes           : 0
Seconds           : 0
TotalDays         : 4,80841435185185E-06
TotalHours        : 0,000115401944444444
TotalMilliseconds : 415,447
TotalMinutes      : 0,00692411666666667
TotalSeconds      : 0,415447

Fast! From 27 seconds to 0.4,

Fast Software Inventory Report creation

Using the procedure above  Get-installedApps  report generation is  easy

New-Item -Path "c:\" -Name "report" -ItemType "directory" 
$report='c:\Report\OUInventorySwReport.csv'
Get-installedApps| Export-Csv -LiteralPath $report -UseCulture -NoTypeInformation
# Search computer with the software *eye* installed
Get-installedApps -computers 'server1',’server2’|Where-Object {$_.DisplayName -like "*eye*"}

 

References

https://devblogs.microsoft.com/scripting/use-powershell-to-quickly-find-installed-software/