Expanding Registry Hive not working as expected!

by MACE1 at 2012-08-21 09:43:50

Taking ideas from numerous sources I have assembled this script. I am finding however for reasons as yet unknown I am unable to enumerate the chosen hives in ‘ReadKeys’ as in every loop the Try fails!
When I had initially hardcoded ‘LocalMachine’ only, I had it working but the commented out $Hive option now fails to work as well. The parameters are getting passed correctly to the function.

This is being developed to assist in identifying VM VDI’s which have had Local instances of software loaded and need refreshing or updating accordingly.
I want to identify all software installed and I am open to any additional methods available. All remote domain machines have the remote registry service available to authorised users.
I try NOT to load powershell commandlets where possible, preferring WMI and .Net methods I can encode into the individual scripts. Keeps things portable :slight_smile:


Function ReadKeys([string]$computername, [string]$Hive, [string]$UninstallKey){
#Drill down into the Uninstall key using the OpenSubKey Method
# If ($Hive -eq “Users”){$Type=[Microsoft.Win32.RegistryHive]::Users}
# else {$Type=[Microsoft.Win32.RegistryHive]::LocalMachine}
$Type=$([Microsoft.Win32.RegistryHive]::$Hive)
Try {
$reg=[microsoft.win32.registrykey]::OpenRemoteBaseKey($Type,$computername)
#Retrieve an array of string that contain all the subkey names
$regkey=$reg.OpenSubKey($UninstallKey)
#Open each Subkey and use GetValue Method to return the required values for each
$subkeys=$regkey.GetSubKeyNames()
$collarray = @()
foreach($key in $subkeys){
$thisSubKey=$UninstallKey+"\"+$key
#if (-not $thisSubKey.getValue(“DisplayName”)) { continue }
If ($thisSubKey.getValue(“DisplayName”) -ne $Null){
write-host $computername" : “$Hive” : “$thisSubKey #For debugging
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name “ComputerName” -Value $computername
$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”))
$obj | Add-Member -MemberType NoteProperty -Name “UninstallString” -Value $($thisSubKey.GetValue(“UninstallString”))
$obj | Add-Member -MemberType NoteProperty -Name “DisplayIcon” -Value $($thisSubKey.GetValue(“DisplayIcon”))
$collarray += $Obj
}
Else{write-host ‘#’$thisSubKey.getValue(“Name”)}
}
If ($collarray.Count -gt $Null ){ Return $collarray}
}
Catch{Write-Host “Failed:$computername $Hive $UninstallKey”}
}

Function InstalledApps([string]$computername, [string[]]$Apps){
#Define the variable to hold the location of Currently Installed Programs
$array = @()
#i86 Create an instance of the Registry Object and open the HKLM base key
$UninstallKey=“SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall”
$array += (ReadKeys $computername “LocalMachine” $UninstallKey)
$array += (ReadKeys $computername “Users” $UninstallKey)
#x64 Create an instance of the Registry Object and open the HKLM base key
$UninstallKey=“SOFTWARE\Wow6432node\Microsoft\Windows\CurrentVersion\Uninstall”
$array += (ReadKeys $computername “LocalMachine” $UninstallKey)
$array += (ReadKeys $computername “Users” $UninstallKey)
#$array | Where-Object { $_.DisplayName } | select ComputerName, DisplayName, DisplayVersion, Publisher | ft -auto
$A = @()
Foreach ($i in $array){
Foreach ($Ap in $Apps){
If ($i.DisplayName -eq $Ap){
$A += “DN=”+$i.DisplayName
$A += “IL=”+$i.InstallLocation
$A += “DV=”+$i.DisplayVersion
$A += “DI=”+$i.DisplayIcon
$A += “US=”+$i.UninstallString
}
}
}
If ($A.Count -gt $Null ){ Return $A}
}

CLS
$Log=[System.Environment]::ExpandEnvironmentVariables(”%userprofile%")+"\Desktop\FixedApps.log"

$objDomain=New-Object System.DirectoryServices.DirectoryEntry
$objSearcher=New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.PageSize=1000
$objSearcher.SearchScope=“Subtree”
$objSearcher.SearchRoot=$objDomain
# Identify Computer names example VDI
$colPropComp=“cn”
$objSearcher.Filter="(&(objectCategory=computer)(name=VDI))“
foreach ($i in $colPropComp){[void]$objSearcher.PropertiesToLoad.Add($i)}
$colComp=($objSearcher.FindAll()|Sort-Object CN)

# Put into array a list of software you want to search for. Most of the following load in Users Local.
$A=“Rapport”,“DropBox”,“Chrome”,“Skype”

$L=”~~~~~~~~~~“
foreach ($objItem in $colComp){
$ErrorActionPreference = “Continue”
$objComps = $objItem.Properties
$CN=$objComps.cn
Try {
$X=InstalledApps $CN $A
If ($X.count -gt $Null){
$D=Get-Date
Write-Output “$CN $D”| Out-File -filePath $LOG -append
Write-Host “$CN $D”
Foreach ($I in $X){
Write-Output $I| Out-File -filePath $LOG -append
}
Write-Output $L$L$L$L$L$L| Out-File -filePath $LOG -append
}
Else {Write-Host “$CN $D - None Found”}
}
Catch {Continue}
}
by DonJ at 2012-08-21 11:12:07
So, it’d be nice to see some error messages, if you got any. A good test is to open RegEdit.exe on your client, and try to connect to one of these servers using that tool. If that works, then we know the basic permissions are working and the Remote Registry Service is started.

I’m going to move this to another forum since you’re actually not using WMI.
by poshoholic at 2012-08-21 11:45:24
Seconding what Don suggested, the error messages help a lot when diagnosing issues. Right now your catch statement calls Write-Host to output some useful information, however it also throws away the actual exception and the error text it contains. Consider updating your catch statement instead so that it calls Write-Host followed by a single throw command to re-throw the exception.

Also, another useful tip would be to put a breakpoint in your catch statement and run it through a debugger (PowerShell ISE, PowerSE, PowerGUI, or any of the other products that have support for debugging PowerShell scripts). When you’re on that breakpoint take a look at $_ to see what it tells you. It can often identify the exact error and point to what is the real problem in the script.
by MACE1 at 2012-08-22 03:07:58
I appreciate the WMI forum comment, was not sure what forum to use initially as what I do with the output is heavily WMI. Where are you moving it to ?
As to Remote Registry, Regedit works fine for manually browsing domain machines.

Googling “single throw” powershell is proving less than fruitfull. Any pointers to appropriate reference material on this would be very helpfull.

Write-Host $error[0]|format-list –force lists the following errors:
i86;localmachine; Method invocation failed because [System.String] doesn’t contain a method named ‘getValue’.
i86;users; You cannot call a method on a null-valued expression.
x64;localmachine; Method invocation failed because [System.String] doesn’t contain a method named ‘getValue’.
x64;users; You cannot call a method on a null-valued expression.
This to ME implies the script has not resolved the initial root registry on the machine therefore not returning valid data!
Suggestions how I compartmentalise this; multiple nested try levels ?

As I am trying to hit the ground running with regards to powershell, I am still trying to absorbe methods to get error output and debug.
I REALLY want to find good debugging methods I can use within Powershell ISE as this is very different from anything I have used before.
Been Kixtarting for a decade and a little VB so powershell is a whole new ballgame.
by poshoholic at 2012-08-22 08:30:21
Hi,

We moved this to the Advanced PowerShell forum. It’s actually mirrored, not moved, so you can see it in both places and respond in both places. There’s really no harm that you posted this in WMI since it is about WMI for you…you just need to make a judgement call. The core question didn’t seem WMI related though which is why we moved it. If it’s a question involving interacting with .NET classes from PowerShell, the Advanced PowerShell forum is probably your best bet.

What I mean by a “single throw” statement is simply one (single) throw statement with no parameters, like this:

[script=powershell]try {
# Do my work here
}
catch {
Write-Error “This is some stuff that I want to see when an exception is raised.”
throw # This “single throw” throws the error higher up so that I see the exception details that PowerShell wants to share with me.
}[/script]

The error information you’re sharing from the $error variable is useful as well though. That gives the same information you get from an unhandled exception. The error states that you are calling getValue on an object of type System.String, and that fails because System.String does not have a getValue method. Based on that information, I think I see the broken line of script.

This line assigns a string value to the thisSubKey variable:

[script=powershell]$thisSubKey=$UninstallKey+”\"+$key[/script]

After that line, you call getValue on it as if it is a registry key object. You should change that line to this:

[script=powershell]$thisSubKey = $UninstallKey.OpenSubKey($key)[/script]

Note: I have not tested this, I’m just pointing out what seems to be the problem based on the error information (which tells me where to look in your script for problems). I’m pretty confident this is your issue though.

Does that make sense?
by MACE1 at 2012-08-22 14:02:38
Unfortunately as $UninstallKey is also a string it is throwing the same error with the suggested amendement.
I am unsure how to prefix the Object $Key with the String to get the desired result $UninstallKey+"\"+$key while keeping the output as an object.

:frowning:
by poshoholic at 2012-08-22 14:13:09
Sorry, I missed that (one of the hazards of working with script only as you can read it and not actually testing it out yourself).

I should have typed this instead:

[script=powershell]$thisSubKey = $regkey.OpenSubKey($key)[/script]

That should work because earlier in ReadKeys you have $regkey = $reg.OpenSubKey($UninstallKey) which opens that key path, so you just need to work with that key path and open the subkeys you already enumerated from there.
by MACE1 at 2012-08-22 14:52:37
Got it;
$key is a String name… need to re-open this level of keys again as objects so…


$thisSubKey=$reg.OpenSubKey($UninstallKey+"\"+$key)
or your code
$thisSubKey = $regkey.OpenSubKey($key)
each do same thing.

If necessary could recursively get this to work in any part of the registry by passing the $uninstallkey+key of interest back to the same function as the $uninstallkey parameter. Looks like not necessary in this instance but its all about forcing the OpenSubKey values to the desired level each loop which had tripped me up !

NB: Still none the wiser on using the Throw statement. Any working examples from some code ?

How do I mark this one as solved :slight_smile:

EDIT:
Would you credit it, it STILL does not pick up ClickOnce installations in the Users Hive. Ahhhhhhh
Anyone know additional User locations to search ?
by poshoholic at 2012-08-23 07:13:06
I have asked Don how to mark a question as solved. I don’t see that option anywhere.

For how throw works, compare this:

[script=powershell]try {
# Force throwing an error here
foreach ($path in @(‘C:\Windows’,‘C:\users’,‘C:\PathDoesNotExist’)) {
Get-Item $path -ErrorAction Stop | Out-Null
}
}
catch {
Write-Host ‘The path could not be found.’
}[/script]
to this:

[script=powershell]try {
# Force throwing an error here
foreach ($path in @(‘C:\Windows’,‘C:\users’,‘C:\PathDoesNotExist’)) {
Get-Item $path -ErrorAction Stop | Out-Null
}
}
catch {
Write-Host ‘The path could not be found.’
throw
}[/script]
In the former case, you get a custom message, which may contain some information useful to you. You do not however get to see the error that identifies the line that threw the error because it isn’t being sent back to the console for processing. The catch statement is preventing that.

In the latter case however, you get your custom message, plus you re-throw the exception you received so that you can see its details as an error in the console. You may not always want to do this, but it is useful when debugging to show comments that help plus to see error messages. Note that normally I would use in Write-Debug, Write-Verbose, Write-Warning or Write-Error instead of Write-Host for the debugging comments; I’m just trying to highlight how throw works to your advantage.

Does that explain throw a little better and how you can make it work for you?

Regarding ClickOnce installations, according to this post they are per-user and are written to the Registry here:

HKCU:\SOFTWARE\Classes\Software\Microsoft\Windows\CurrentVersion\Deployment\SideBySide\2.0\Components
by poshoholic at 2012-08-23 07:49:55
I heard back from Don. To mark a particular entry in a discussion as the solution, look for the small green checkmark button in the mini toolbar at the top of the entry (you should it next to the EDIT button). That will add the [SOLVED] tag to the post title.
by MACE1 at 2012-08-24 00:48:35
If ClickOnce has to be searched there then it looks like I am defeated because remote registry does not give HKCU and in this case I am very limited in what I can do. May have to resort to login script again which is NOT what we wanted for the VDI’s.
Ho umm the joys of security and Microsoft :frowning:
by MACE1 at 2012-08-24 01:12:37
I looked back into more WMI so maybee not…
http://msmvps.com/blogs/richardsiddaway/archive/2012/03/10/migrating-to-cim-part3.aspx
by poshoholic at 2012-08-24 08:37:49
Right, the current user hive and remote management do not work very well together. Is the user logged on when you run your script? If not, the current user hive won’t be loaded, which poses a problem. You can access HKCU on a remote system using PowerShell remoting, but again, you’re dependent on the user being logged on. What you probably need to do is to automate the loading of the registry hive for the user where you want to find the ClickOnce installs. That way you don’t have to worry about whether or not they are logged in. The RegLoadKey function supports this, and it even works on remote systems. Using that from PowerShell is a little tricky because it is a Win32 API, not a .NET class/method, so you need to first expose it to PowerShell so that you can then use it from script.