One of the first items I wanted to accomplish when I started using PowerShell was to automatically gather information from Windows systems throughout the environment. This information included hardware, bios, operating system and installed applications. I found that PowerShell was quite capable, especially when you could piggy back off of other existing components such as WMI (Windows Management Instrumentation) that allowed access to all kinds of obscure details about a system.
A SharePoint view of installed software applications, gathered using PowerShell.
I went and put together my scripting tool, and before you know it I had tons of information pouring in from many systems to a central repository. It was great. Until I read a post by someone suggesting that the use of the Win32_Product class in a live environment could be a bad thing. I thought it was one of the best things since sliced bread until I read that. But, to be safe I quickly turned off that portion of my scripting tool and carried on while I planned the replacement for it.
Luckily, there was a replacement, and this was even better than my original scripting setup. The Win32_Product query was nice, it was just one line and it gave a lot of information. But it was inefficient and it also had some scary side effects due to the non-optimized structure of the class. By running this, I was exposing systems to the chance that installed applications identified by the query could theoretically be repaired and set back to defaults on systems being queried. Wow that’s scary. Anyway, the solution was totally not scary though. Instead of using this one-liner, I had to put together a function that queried the registry on the systems. The result was a safer (and faster) scripting tool, with pretty much the same information from the previous setup.
The first one was so nice and simple, but it was flawed so it had to be replaced. The new one is basically a function that queries two registry keys…
HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall
HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
For each key, a new object is created and added to an array. This is done for both registry keys to capture 64-bit and 32-bit items.
Take a look at the OLD way and then the NEW way…
OLD (using Get-WmiObject Win32_Product)
$getProducts = Get-WmiObject Win32_Product -ComputerName $computerName | Select-Object -Property @{Name=”ProductName”;Expression={$_.”Name”}},@{Name=”ProductVersion”;Expression={$_.”Version”}},Vendor,InstallDate,InstallSource,LocalPackage,PackageName,IdentifyingNumber
NEW (using Windows Registry and native PowerShell)
# gets installed software from a chosen computer…
function Get-ComputerInstalledSoftware() {
Param (
[Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[string]$computerName
)
$array = @()
$getAll = Get-ChildItem -Path HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall -Recurse
$getAll | ForEach {
Try {
$getProperties = $_ | Get-ItemProperty
$getDate = $getProperties.InstallDate
$getVersion = $getProperties.DisplayVersion
If (($getDate.Length -gt 0) -And ($getVersion.Length -gt 0)) {
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name “PSComputerName” -Value $computerName
$obj | Add-Member -MemberType NoteProperty -Name “DisplayName” -Value $getProperties.DisplayName
$obj | Add-Member -MemberType NoteProperty -Name “DisplayVersion” -Value $getProperties.DisplayVersion
$obj | Add-Member -MemberType NoteProperty -Name “EstimatedSize” -Value $getProperties.EstimatedSize
$obj | Add-Member -MemberType NoteProperty -Name “HelpLink” -Value $getProperties.HelpLink
$obj | Add-Member -MemberType NoteProperty -Name “HelpTelephone” -Value $getProperties.HelpTelephone
$obj | Add-Member -MemberType NoteProperty -Name “InstallDate” -Value $getProperties.InstallDate
$obj | Add-Member -MemberType NoteProperty -Name “InstallLocation” -Value $getProperties.InstallLocation
$obj | Add-Member -MemberType NoteProperty -Name “InstallSource” -Value $getProperties.InstallSource
$obj | Add-Member -MemberType NoteProperty -Name “Language” -Value $getProperties.Language
$obj | Add-Member -MemberType NoteProperty -Name “ModifyPath” -Value $getProperties.ModifyPath
$obj | Add-Member -MemberType NoteProperty -Name “NoModify” -Value $getProperties.NoModify
$obj | Add-Member -MemberType NoteProperty -Name “NoRemove” -Value $getProperties.NoRemove
$obj | Add-Member -MemberType NoteProperty -Name “NoRepair” -Value $getProperties.NoRepair
$obj | Add-Member -MemberType NoteProperty -Name “PSChildName” -Value $getProperties.PSChildName
$obj | Add-Member -MemberType NoteProperty -Name “Publisher” -Value $getProperties.Publisher
$obj | Add-Member -MemberType NoteProperty -Name “SystemComponent” -Value $getProperties.SystemComponent
$obj | Add-Member -MemberType NoteProperty -Name “UninstallString” -Value $getProperties.UninstallString
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersion” -Value $getProperties.Version
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersionMajor” -Value $getProperties.VersionMajor
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersionMinor” -Value $getProperties.VersionMinor
$obj | Add-Member -MemberType NoteProperty -Name “WindowsInstaller” -Value $getProperties.WindowsInstaller
$array += $obj
}
} Catch {}
}
$getAll = Get-ChildItem -Path HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall -Recurse
$getAll | ForEach {
Try {
$getProperties = $_ | Get-ItemProperty
$getDate = $getProperties.InstallDate
$getVersion = $getProperties.DisplayVersion
If (($getDate.Length -gt 0) -And ($getVersion.Length -gt 0)) {
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name “PSComputerName” -Value $computerName
$obj | Add-Member -MemberType NoteProperty -Name “DisplayName” -Value $getProperties.DisplayName
$obj | Add-Member -MemberType NoteProperty -Name “DisplayVersion” -Value $getProperties.DisplayVersion
$obj | Add-Member -MemberType NoteProperty -Name “EstimatedSize” -Value $getProperties.EstimatedSize
$obj | Add-Member -MemberType NoteProperty -Name “HelpLink” -Value $getProperties.HelpLink
$obj | Add-Member -MemberType NoteProperty -Name “HelpTelephone” -Value $getProperties.HelpTelephone
$obj | Add-Member -MemberType NoteProperty -Name “InstallDate” -Value $getProperties.InstallDate
$obj | Add-Member -MemberType NoteProperty -Name “InstallLocation” -Value $getProperties.InstallLocation
$obj | Add-Member -MemberType NoteProperty -Name “InstallSource” -Value $getProperties.InstallSource
$obj | Add-Member -MemberType NoteProperty -Name “Language” -Value $getProperties.Language
$obj | Add-Member -MemberType NoteProperty -Name “ModifyPath” -Value $getProperties.ModifyPath
$obj | Add-Member -MemberType NoteProperty -Name “NoModify” -Value $getProperties.NoModify
$obj | Add-Member -MemberType NoteProperty -Name “NoRemove” -Value $getProperties.NoRemove
$obj | Add-Member -MemberType NoteProperty -Name “NoRepair” -Value $getProperties.NoRepair
$obj | Add-Member -MemberType NoteProperty -Name “PSChildName” -Value $getProperties.PSChildName
$obj | Add-Member -MemberType NoteProperty -Name “Publisher” -Value $getProperties.Publisher
$obj | Add-Member -MemberType NoteProperty -Name “SystemComponent” -Value $getProperties.SystemComponent
$obj | Add-Member -MemberType NoteProperty -Name “UninstallString” -Value $getProperties.UninstallString
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersion” -Value $getProperties.Version
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersionMajor” -Value $getProperties.VersionMajor
$obj | Add-Member -MemberType NoteProperty -Name “ProductVersionMinor” -Value $getProperties.VersionMinor
$obj | Add-Member -MemberType NoteProperty -Name “WindowsInstaller” -Value $getProperties.WindowsInstaller
$array += $obj
}
} Catch {}
}
Return $array
}
$getProducts = Get-ComputerInstalledSoftware -ComputerName $computerName
The Microsoft KB article that describes why Win32_Product may not be suitable in a live environment: https://support.microsoft.com/en-us/kb/974524