
close
close
Chance to win $250 in Petri 2023 Audience Survey
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 globomantics\administrator
$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 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
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 root\default -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
Using the $regcim object to find definitions of all the class methods in Windows PowerShell. (Image Credit: Jeff Hicks)
$regcim.CimClassMethods["EnumKey"].parameters
Looking at parameters for EnumKey() in Windows PowerShell. (Image Credit: Jeff Hicks)
$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
Writing an array of strings called sNames in Windows PowerShell. (Image Credit: Jeff Hicks)
$data = Invoke-CimMethod @paramHash | select -expand snames |
where {$_ -notmatch '(\.)?KB\d+'} -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.
Updating Invoke-Command parameters that have changed. (Image Credit: Jeff Hicks)
$rpath = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
Re-running my code provides this result:
The Out-GridView dialog box. (Image Credit: Jeff Hicks)
$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 = "$item\Software\Microsoft\Windows\CurrentVersion\Uninstall"
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:
Specifying a WMI path. (Image Credit: Jeff Hicks)
#requires -version 3.0 #Get-InstalledApplicationFromRegistry.ps1 <# Get-InstalledApplicationfromRegistry CHI-WIN81 -credential globomantics\administrator -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 = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" $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 root\default -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 '(\.)?KB\d+'} $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 = "$item\Software\Microsoft\Windows\CurrentVersion\Uninstall" 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 globomantics\jeff
PS C:\> $d4 | out-gridview
The results of the CMI-based script. (Image Credit: Jeff Hicks)
More in PowerShell
Most popular on petri