3

I have a function that checks the registry for an uninstall key called Get-InstalledApps

Function Get-InstalledApps {
    param (
        [Parameter(ValueFromPipeline=$true)]
        [string[]]$ComputerName = $env:COMPUTERNAME,
        [string]$NameRegex = ''
    )

    foreach ($comp in $ComputerName) {
        $keys = '','\Wow6432Node'
        foreach ($key in $keys) {
            try {
                $reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
                $apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
            } catch {
                continue
            }

            foreach ($app in $apps) {
                $program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
                $name = $program.GetValue('DisplayName')
                if ($name -and $name -match $NameRegex) {
                    [pscustomobject]@{
                        ComputerName = $comp
                        DisplayName = $name
                        DisplayVersion = $program.GetValue('DisplayVersion')
                        Publisher = $program.GetValue('Publisher')
                        InstallDate = $program.GetValue('InstallDate')
                        UninstallString = $program.GetValue('UninstallString')
                        Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
                        Path = $program.name
                    }
                }
            }
        }
    }
}

and then I grab the DisplayName/Version for what I need. My current problem is that it only seems to work on certain machines. Example:

Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"}

Name                           Value
----                           -----
InstallDate
ComputerName                   Computer
DisplayName                    Citrix Receiver 4.7
Bits                           64
UninstallString                C:\ProgramData\Citrix\Citrix Receiver 4.7\TrolleyExpress.exe /uninstall /cleanup
Path                           HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\CitrixOnlinePluginPackWeb
Publisher                      Citrix Systems, Inc.                                                              
DisplayVersion                 14.7.0.13011

So this is great, I get what I want. Now I normally just pipe in | Select-Object Displayname -ExpandProperty Displayname and it would return "Citrix Receiver 4.7" just like I want. My problem is that on certain machines I'm getting this:

Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"} | Select-Object DisplayName

DisplayName
-----------

And that's it. Why is there no value listed? If I try to expandproperty I get an error because it says nothing is there, but clearly there is something there or the Where-Object would not have found it in my search. Again, in a lot cases this code works just fine and I get the value I want but on a lot of machines I'm getting what you see above.

Edited in from comments:

I run this on a user's machine and I get the results I posted. If I run it on my machine I'll get the value "Citrix Receiver 4.7" every time. Also, on my machine I don't get the Name and Value columns. Only about 1/4 of the machines I ran this code on actually gave me the value I expected. Windows 7 vs Windows 10 thing?

briantist
  • 45,546
  • 6
  • 82
  • 127
noz3r0
  • 315
  • 3
  • 4
  • 16
  • Well, the obvious question would be to ask if there is an uninstall key for citrix on that machine or not? – EBGreen Jan 26 '18 at 20:05
  • Well yeah, it returns the Property and Value but I cannot select the Value for some reason. – noz3r0 Jan 26 '18 at 20:13
  • According to the example that you have shown it probably is not returning the property value. Select will add properties to an object. For instance if you run this code: **'' | Select Foo** you will see a Foo property with no value – EBGreen Jan 26 '18 at 20:31
  • 1
    You are showing some effects of your function but not its code. – Bill_Stewart Jan 26 '18 at 20:32
  • Yes, seeing what the function is doing (and hence what it is really returning) would certainly help. – EBGreen Jan 26 '18 at 20:33
  • Added the function. By the way, you were correct: | Select Foo returned a property with no value. Hopefully the function code will give some more insight, apologies for being a little ignorant here. Thanks for all the help. – noz3r0 Jan 26 '18 at 20:35

1 Answers1

1

It looks to me like your function returns a [hashtable], but you're using it like it's an object with properties.

That happens to work fine with Where-Object because the .Member syntax works for accessing [hashtable] values, but it's not going to work with Select-Object because it's operating on actual properties.

So what can you do?

If you want to keep it as a [hashtable], and insist on doing it in a pipeline, you can use ForEach-Object:

Get-InstalledApps | 
    Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
    ForEach-Object -Process { $_.DisplayName }

or

Get-InstalledApps | 
    Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
    ForEach-Object -MemberName Item -ArgumentList DisplayName

Another thing you can do is change your function to return an object.

This is really easy to do with a [hashtable]; so say your function is about to return $hash, instead return:

New-Object -TypeName PSObject -Property $hash

Now you can use the normal suite of cmdlets and have them work as expected.


Edit: after seeing your code, it looks like you are converting your hashtable to an object already, but your output says otherwise. It wouldn't display as Name and Value columns if that were the case, so I still think something is wrong and the output is a [hashtable].

Edit 2: with info from comments about the platform differences, this seems to be happening because the object conversion is being done with the [pscustomobject] type accelerator which was added in PowerShell v3. Since the problematic machine is running Windows 7, it may be running v2 (which is what Win 7 shipped with).

Recommendations:

  1. Get rid of Windows 7.
  2. If you can't do that, upgrade PowerShell (Windows Management Framework) on that machine.
  3. Either way, use New-Object as posted above.
briantist
  • 45,546
  • 6
  • 82
  • 127
  • The function definitely returns customobjects – EBGreen Jan 26 '18 at 20:43
  • Yeah and this is where I'm confused. I run this on a user's machine and I get the results I posted. If I run it on my machine I'll get the value "Citrix Receiver 4.7" every time. Also, on my machine I don't get the Name and Value columns. Only about 1/4 of the machines I ran this code on actually gave me the value I expected. Windows 7 vs Windows 10 thing? – noz3r0 Jan 26 '18 at 20:45
  • My first suspicion would be a 64bit/32bit thing – EBGreen Jan 26 '18 at 20:46
  • 1
    @noz3r0 yes, most likely due to the platform difference. `[pscustomobject]` was not available in PowerShell 2.0, which is the version that shipped with Windows 7. My method using `New-Object` would work on 2.0+ though. I'm guessing somewhere the error is being swallowed and the hashtable is getting returned instead. – briantist Jan 26 '18 at 20:46
  • @EBGreen `The function definitely returns customobjects` yes the fucntion as posted, when run on a version that supports the `[pscustomobject]` accelerator does return them, and it works as expected, but the output he shows in the question does not match object output, it matches `[hashtable]` output. – briantist Jan 26 '18 at 20:48
  • Yep, that would certainly explain it. I always use New-Object anyway out of habit so I've never seen this issue. – EBGreen Jan 26 '18 at 20:48
  • Alright so that's definitely the issue, a lot of machines have just PS2.0 Do you mind explaining how I would modify the function correctly to make the table using New-Object? I'm a bit novice still with Powershell. Thanks again for all the help so far! – noz3r0 Jan 26 '18 at 20:53
  • @noz3r0 already in my answer. Just pass the `[hashtable]` (literally or as a variable) to the `-Property` parameter. – briantist Jan 26 '18 at 20:54