4

The code below produce 2 "identical" Hashtables, however on the one that was grouped using a code block I can't get items from the key.

$HashTableWithoutBlock = 
    Get-WmiObject Win32_Service | Group-Object State -AsHashTable
$HashTableWithBlock = 
    Get-WmiObject Win32_Service | Group-Object {$_.State} -AsHashTable

Write-Host "Search result for HashTable without using code block : " -NoNewline
if($HashTableWithoutBlock["Stopped"] -eq $null)
{
    Write-Host "Failed"
}
else
{
    Write-Host "Success"
}

Write-Host "Search result for HashTable with code block : " -NoNewline
if($HashTableWithBlock["Stopped"] -eq $null)
{
    Write-Host "Failed"
}
else
{
    Write-Host "Success"
} 

Output :

Search result for HashTable without using code block : Success
Search result for HashTable with code block : Failed

What is the difference between the two Hashtables ?

How to get Items on second one that was grouped by code block ?

EDIT : More than a workaround, I'd like to know if it is possible to retrieve the Item I want with a table lookup, and if yes, how ?

Luke Marlin
  • 1,398
  • 9
  • 26
  • I noticed that using a block, you can obviously Group by multiple properties, which then turns the HashTable key into an object. However with only one property, I'd assume that it would be a plain string and that my lookup should work.. – Luke Marlin Jan 28 '15 at 11:29
  • 1
    In $HashTableWithBlock keys are wrapped in `PSObject`, so `Hashtable` fail to compare them properly. – user4003407 Jan 28 '15 at 11:58
  • @PetSerAl I see, but then how can you make a correct lookup ? Even if I create a PSObject with a property, I suspect that the comparison will compare the object references.. – Luke Marlin Jan 28 '15 at 13:01
  • Can you not group by multiple properties without using a block? What are you actually _trying_ to do? Why must you work with a hash table? – arco444 Jan 28 '15 at 13:03
  • I can only propose to recreate `Hashtable`: `$HashTableWithBlock.GetEnumerator()|%{$HashTableWithBlock=@{}}{$HashTableWithBlock.Add($_.Key,$_.Value)}` – user4003407 Jan 28 '15 at 13:15
  • @PetSerAl Thanks, I already found that recreating it would ĥelp with the issue, I was interested if there was a "normal" way of retrieving items – Luke Marlin Jan 28 '15 at 13:22
  • @arco444 I don't know if it is possible to group by multiples properties without a block. And what I actually do is trying to understand this specific mechanism of powershell hashtables. – Luke Marlin Jan 28 '15 at 13:25
  • It is certainly possible. `Get-Help Group-Object`. Just add more properties to group by... FWIW in my experience there's not usually much of a case for using hashtables, much easier to work with objects – arco444 Jan 28 '15 at 13:27
  • @arco444 Although it's interesting, It actually doesn't answer my question (which has been updated to be clearer). If you look at the sample code, I'm not trying to use multiple properties anyway. – Luke Marlin Jan 28 '15 at 13:29

3 Answers3

4

The difference between two Hashtables is that $HashTableWithBlock have its key wrapped in PSObject. Problem is that PowerShell normally unwrap PSObject before pass it to the method call, so even if you have right key, you still can not just pass it to indexer. To workaround this you can create helper C# method what would call indexer with right object. Another way is to use reflection:

Add-Type -TypeDefinition @'
    public static class Helper {
        public static object IndexHashtableByPSObject(System.Collections.IDictionary table,object[] key) {
            return table[key[0]];
        }
    }
'@
$HashTableWithBlock = Get-WmiObject Win32_Service | Group-Object {$_.State} -AsHashTable
$Key=$HashTableWithBlock.Keys-eq'Stopped'
#Helper method
[Helper]::IndexHashtableByPSObject($HashTableWithBlock,$Key)
#Reflection
[Collections.IDictionary].InvokeMember('','GetProperty',$null,$HashTableWithBlock,$Key)
user4003407
  • 21,204
  • 4
  • 50
  • 60
  • @PetSerAI thanks, seems interesting. I however fail to see why the call table[key[0]] is different. Is it because of .Net's implicit casts to IDictionary and Object ? Because else, this would work : `$HashTableWithBlock[($HashTableWithBlock.Keys -eq 'Stopped')[0]]` – Luke Marlin Jan 28 '15 at 14:12
  • @LukeMarlin PowerShell will unwrap result `PSObject` of this expression `($HashTableWithBlock.Keys -eq 'Stopped')[0]` and pass just string in indexer. – user4003407 Jan 28 '15 at 14:16
  • @PetSerAI Thank you for the insight. I hope there's not many other weird things in PS compared to C# .Net though ! – Luke Marlin Jan 28 '15 at 14:18
3

The other posters are correct that the problem is with the key being stored as a PSObject but there is a built-in solution for this: use the -AsString switch along with -AsHashTable. This will force the key to be stored as a string. You can take a look at the code here

I've opened an issue on GitHub for this bug.

mklement0
  • 382,024
  • 64
  • 607
  • 775
Bruce Payette
  • 2,511
  • 10
  • 8
0

Here's a workaround I found, which is really not great:

$HashTableWithBlock = 
    Get-WmiObject Win32_Service | ForEach-Object -Process {
        $_ | Add-Member -NotePropertyName _StateProp -NotePropertyValue $_.State -Force -Passthru
    } |
    Group-Object -Proerty _StateProp -AsHashTable

I mean I guess once you're doing ForEach-Object you almost might as well just build a hashtable yourself?

Do note that, interestingly, this will not work if you group on a ScriptProperty instead. I haven't figured out why.

briantist
  • 45,546
  • 6
  • 82
  • 127