Last Update: Sep 04, 2024 | Published: Dec 10, 2014
Over the last several articles I’ve been guiding you on how to discover what applications might be installed. In the previous article in this series I demonstrated how to use WMI to query the registry on remote computers, where I specifically showed you how to use StdRegProv. One of the drawbacks is that this uses legacy technology and requires DCOM, which means it is not very firewall friendly.
There is also no provision for alternate credentials like there is with the Get-WMIObject cmdlet. Although you can get around that using PowerShell remoting and Invoke-Command, then you might as well use the CIM cmdlets. With CIM, we get an easy way to provide alternate credentials and also eliminate DCOM and RPC from the picture.
The first step is to create a CIM session to the remote computer. If you need to use alternate credentials, this is where you would do so.
$cred = Get-Credential globomanticsadministrator
$cs = New-CimSession -ComputerName chi-win81 -Credential $cred
Even though we will be using CIM, we are still accessing the same StdRegProv provider, so we still need to provide the appropriate hive values and paths.
$HKLM=2147483650
$rpath = "SOFTWAREMicrosoftWindowsCurrentVersionUninstall"
For now I’m going to search the Uninstall key under HKLM.
Next we’ll create an object for StdRedProv using Get-CimClass. Remember, StdRegProv is not something with instances. We will be using the class’s methods directly.
$regcim = Get-CimClass -Namespace rootdefault -class StdRegProv -CimSession $cs
To query, we will use the EnumKey() method that requires a few parameters. If you have been following the article series, then these will look familiar. Let me quickly show you how to discover them. The $regcim object includes definitions of all the class methods.
$regcim.cimclassmethods
Let’s narrow the focus to EnumKey().
$regcim.CimClassMethods["EnumKey"].parameters
The “IN” parameters are what we need. We will be using Invoke-CimMethod, so all we need to do is define a hashtable of the necessary parameters and their values.
$enumArgs = @{hDefKey=$HKLM;sSubKeyName=$rpath}
To make the Invoke-CimMethod expression easier to read, I’m going to create a hashtable of parameters to splat to the cmdlet.
$paramHash = @{
cimclass = $regcim
CimSession = $cs
Name = "EnumKey"
Arguments = $enumArgs
}
Essentially I’m going to invoke EnumKey with my arguments on the StdRegProv object using the remote CIMSession. I know that the method is going to write an array of strings called sNames that I’ll expand, which should give me the names of each entry under Uninstall.
Invoke-CimMethod @paramHash | select -expand snames
Once I know the name of the subkey, then I can get its values. In turn, I can grab the string value of the properties I am interested in. Here’s a version of code that will also filter out the Microsoft Office updates.
$data = Invoke-CimMethod @paramHash | select -expand snames |
where {$_ -notmatch '(.)?KBd+'} -pv p | foreach {
$keyPath = "$rpath$_"
write-host $keyPath -ForegroundColor Cyan
#revise paramhash
$paramHash.Name = "EnumValues"
$paramHash.Arguments = @{hDefKey=$HKLM;sSubKeyName=$keyPath}
Invoke-CimMethod @paramHash | foreach {
#get value data
$hash = [ordered]@{Path = $KeyPath}
#add a list of known properties
"Displayname","DisplayVersion","Publisher",
"InstallDate","InstallLocation","Comments","UninstallString" | foreach {
$paramHash.Name = "GetStringValue"
$paramhash.Arguments = @{hDefKey = $HKLM ;sSubKeyName=$KeyPath;sValueName=$_}
$value = Invoke-CimMethod @paramhash
$hash.Add($_,$($value.sValue))
} #foreach property name
#write a custom object to the pipeline
[pscustomobject]$hash
} #foreach subkey name
} #foreach sname
$data | out-gridview
Essentially there is an iterative process here of enumerating the sNames, the values, and their actual data. This requires different methods and parameters, but by splatting my code, it stays relatively simple, as all I have to do is update the Invoke-Command parameters that have changed.
On x64 systems I might also want to check this path:
$rpath = "SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall"
Re-running my code provides this result:
Finally, let’s check for user specific applications using CIM. The concepts are the same as what I used with WMI in the previous article, except I’m going to use CIM methods to enumerate keys and values.
$HKEY_USERS=2147483651
#use an empty string not $Null
$enumArgs = @{hDefKey=$HKEY_USERS;sSubKeyName=""}
$paramHash = @{
cimclass = $regcim
CimSession = $cs
Name = "EnumKey"
Arguments = $enumArgs
}
#snames is the collection of user hives and filter out *Classes and .DEFAULT
$snames = Invoke-CimMethod @paramhash | Select -ExpandProperty sNames | Where {$_ -notmatch "_Classes$"}
$results = foreach ($item in $snames) {
$rpath = "$itemSoftwareMicrosoftWindowsCurrentVersionUninstall"
Write-Host "Checking $rpath" -ForegroundColor cyan
$paramHash.Arguments = @{hDefKey=$HKEY_USERS;sSubKeyName=$rpath}
#get subkeys with defined sname values
$data = Invoke-CimMethod @paramhash
if ($data.snames) {
#only process if sname data discovered
#$paramHash.Name = "EnumValues"
$paramHash.Arguments = @{hDefKey=$HKEY_USERS;sSubKeyName=$rpath}
$appnames = Invoke-CimMethod @paramhash
if ($appnames.snames) {
#resolve the SID using Get-WSManInstance
write-host "Resolving $item" -ForegroundColor Cyan
$Resolve = Get-WSManInstance -resourceURI "wmi/root/cimv2/Win32_SID?SID=$item" -computername $cs.ComputerName -Credential $cred
if ($resolve.accountname) {
$Username = "$($resolve.ReferencedDomainName)$($resolve.AccountName)"
}
else {
$Username = $item
}
#enumerate each sname which will be an application of some kind
foreach ($app in $appnames.sNames) {
$hash = [ordered]@{Username = $Username; Path = $rpath}
write-host "Querying $rpath$app" -ForegroundColor Cyan
#add a list of known properties
"Displayname","DisplayVersion","Publisher",
"InstallDate","InstallLocation","Comments","UninstallString" | foreach {
$paramHash.Name = "GetStringValue"
$paramHash.Arguments = @{hDefKey= $HKEY_USERS;sSubKeyName = "$rpath$app";sValueName = $_}
$value = Invoke-CimMethod @paramhash
$hash.Add($_,$value.svalue)
}
#write a custom object to the pipeline
[pscustomobject]$hash
} #foreach app
} #if appnames
#reset
Clear-Variable data
} #if snames found
} #foreach item in snames
$results | Out-GridView -title "HKEY_USERS"
One step I had to do differently was to resolve the SID name.
$Resolve = Get-WSManInstance -resourceURI "wmi/root/cimv2/Win32_SID?SID=$item" -computername $cs.ComputerName -Credential $cred
You can’t query the Win32_SID class with a filter, and I have yet to find any way to specify the WMI path to the specific SID instance. Instead I use Get-WSManInstance, which allows me to specify a WMI path. Here’s my final result:
Querying the registry with either WMI or CIM is certainly not a beginning PowerShell topic and in terms of identifying applications, it still may not be the most foolproof way to identify applications. But this solution may be enough. I will leave you with a script that uses CIM to pull everything together that I have shown you.
#requires -version 3.0 #Get-InstalledApplicationFromRegistry.ps1 <# Get-InstalledApplicationfromRegistry CHI-WIN81 -credential globomanticsadministrator -includeUsers #> [cmdletbinding()] Param( [Parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)] [Alias("cn")] [ValidateNotNullorEmpty()] [string]$Computername = $env:computername, [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, [switch]$IncludeUsers ) Begin { Write-Verbose -Message "Starting $($MyInvocation.Mycommand)" $HKEY_USERS=2147483651 $HKLM=2147483650 $rpaths = "SOFTWAREMicrosoftWindowsCurrentVersionUninstall", "SOFTWAREWow6432NodeMicrosoftWindowsCurrentVersionUninstall" $progParam = @{ Activity = $MyInvocation.MyCommand Status = "Starting" CurrentOperation = "" PercentComplete = 0 } } #begin Process { Write-Verbose "Creating CIMSession to $computername" $progParam.CurrentOperation = "Creating CIMSession to $computername" Write-Progress @progParam #initialize $data $data = @() Try { $ncsParams = @{ ComputerName = $computername ErrorAction = "Stop" } if ($Credential.UserName) { $ncsParams.Add("Credential",$Credential) } $progparam.currentOperation = "Creating CIM session and class objects" Write-Progress @progParam $cs = New-CimSession @ncsParams $regcim = Get-CimClass -Namespace rootdefault -class StdRegProv -CimSession $cs } Catch { Write-Warning "There was a problem creating a CIMSession to $computername" Throw } foreach ($rpath in $rpaths) { Write-Verbose "Querying $rpath" $progParam.Status = "Querying $rpath" $progParam.CurrentOperation = "EnumKey" Write-Progress @progParam $enumArgs = @{hDefKey=$HKLM;sSubKeyName=$rpath} $paramHash = @{ cimclass = $regcim CimSession = $cs Name = "EnumKey" Arguments = $enumArgs } $mySnames = Invoke-CimMethod @paramHash | select -expand snames | where {$_ -notmatch '(.)?KBd+'} $i=0 $data += foreach ($item in $MySnames) { $i++ $progParam.CurrentOperation = $_ $progParam.Status = "EnumValues $rpath" $pct = ($i/$mySNames.count)*100 $progParam.PercentComplete = $pct Write-Progress @progParam $keyPath = "$rpath$item" #revise paramhash $paramHash.Name = "EnumValues" $paramHash.Arguments = @{hDefKey=$HKLM;sSubKeyName=$keyPath} Invoke-CimMethod @paramHash | foreach { #get value data $hash = [ordered]@{Path = $KeyPath} #add a list of known properties "Displayname","DisplayVersion","Publisher", "InstallDate","InstallLocation","Comments","UninstallString" | foreach { $paramHash.Name = "GetStringValue" $paramhash.Arguments = @{hDefKey = $HKLM ;sSubKeyName=$KeyPath;sValueName=$_} $value = Invoke-CimMethod @paramhash $hash.Add($_,$($value.sValue)) } #foreach property name #write a custom object to the pipeline [pscustomobject]$hash } #foreach subkey name } #foreach sname } #foreach rpath #get information from HKEY_USERS if ($IncludeUsers) { Write-Verbose "Getting data from HKEY_USERS" $progParam.Status = "Getting data from HKEY_USERS" $progParam.CurrentOperation = "" $progParam.PercentComplete = 0 Write-Progress @progParam $enumArgs = @{hDefKey=$HKEY_USERS;sSubKeyName=""} $paramHash = @{ cimclass = $regcim CimSession = $cs Name = "EnumKey" Arguments = $enumArgs } #snames is the collection of user hives and filter out *Classes and .DEFAULT $snames = Invoke-CimMethod @paramhash | Select -ExpandProperty sNames | Where {$_ -notmatch "_Classes$"} $i = 0 $data+= foreach ($item in $snames) { $i++ $pct = ($i/$snames.count)*100 $progParam.CurrentOperation = $item $progParam.PercentComplete = $pct Write-Progress @progParam $rpath = "$itemSoftwareMicrosoftWindowsCurrentVersionUninstall" Write-Verbose "Checking $rpath" $paramHash.Arguments = @{hDefKey=$HKEY_USERS;sSubKeyName=$rpath} #get subkeys with defined sname values $mydata = Invoke-CimMethod @paramhash if ($mydata.snames) { #only process if sname data discovered $paramHash.Arguments = @{hDefKey=$HKEY_USERS;sSubKeyName=$rpath} $appnames = Invoke-CimMethod @paramhash if ($appnames.snames) { #resolve the SID using Get-WSManInstance write-verbose "Resolving $item" $WSManParamHash = @{ resourceURI = "wmi/root/cimv2/Win32_SID?SID=$item" computername = $cs.ComputerName Credential = $credential } $Resolve = Get-WSManInstance @WSManParamHash if ($resolve.accountname) { $Username = "$($resolve.ReferencedDomainName)$($resolve.AccountName)" } else { $Username = $item } #enumerate each sname which will be an application of some kind foreach ($app in $appnames.sNames) { $hash = [ordered]@{Username = $Username; Path = $rpath} Write-Verbose "Querying $rpath$app" #add a list of known properties "Displayname","DisplayVersion","Publisher", "InstallDate","InstallLocation","Comments","UninstallString" | foreach { $paramHash.Name = "GetStringValue" $paramHash.Arguments = @{hDefKey= $HKEY_USERS;sSubKeyName = "$rpath$app";sValueName = $_} $value = Invoke-CimMethod @paramhash $hash.Add($_,$value.svalue) } #foreach property name #write a custom object to the pipeline [pscustomobject]$hash } #foreach app } #if appnames #reset Clear-Variable mydata } #if snames found } #foreach item in snames } #if users #write results to pipeline $data #clean up Write-Verbose "Removing CIMSession" $progParam.CurrentOperation = "" $progParam.Status = "Removing CIMSession" Write-Progress @progParam Remove-CimSession $cs } #process End { $progParam.Status = "Finished" Write-Progress @Progparam -Completed Write-Verbose -Message "Ending $($MyInvocation.Mycommand)" } #end
You can run it like this:
PS C:> $d4 = .Get-InstalledApplicationFromRegistry.ps1 -IncludeUsers -Computername chi-win81 -Credential globomanticsjeff
PS C:> $d4 | out-gridview
Hopefully I’ve given you enough information to build the tools you need to discover what applications are installed using PowerShell. If you run into issues or have follow up questions, feel free to leave a comment or post in the PowerShell forum here on the site.