PowerShell: a peculiar case of being unable to filter a generic List

Viewed 96

I have two system langages: English and Russian. So, Get-WinUserLanguageList returns two items:

PS> Get-WinUserLanguageList

LanguageTag     : en-US
[…]

LanguageTag     : ru
[…]

Though, when I'm trying to filter the results of this cmdlet, I fail:

PS> Get-WinUserLanguageList | Where-Object { $_.LanguageTag -eq 'ru' }

LanguageTag     : en-US
[…]

LanguageTag     : ru
[…]

At the same time, if I add parens around the cmdlet invocation, it suddenly starts working:

PS> (Get-WinUserLanguageList) | Where-Object { $_.LanguageTag -eq 'ru' }

LanguageTag     : ru
[…]

Being curious, I've tried to analyze the return type of this cmdlet. Documentation says it should return a System.Collections.Generic.List<Microsoft.InternationalSettings.Commands.WinUserLanguage>.

I've tried to check it, and this seems to be true:

PS> (Get-WinUserLanguageList).GetType().FullName
System.Collections.Generic.List`1[[Microsoft.InternationalSettings.Commands.WinUserLanguage, Microsoft.InternationalSettings.Commands, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]

And this generic List gets passed as a single item to the Where-Object pipeline, which is why the filtering breaks:

PS> Get-WinUserLanguageList | Where-Object { Write-Host "Item: $($_.GetType())"; $_.LanguageTag -eq 'ru' }
Item: System.Collections.Generic.List[Microsoft.InternationalSettings.Commands.WinUserLanguage]

LanguageTag     : en-US
[…]

LanguageTag     : ru
[…]

So, my next hypothesis is that Where-Object is unable to filter a generic List. Okay, let's check it:

PS> $myList = [System.Collections.Generic.List[string]]::new()
PS> $myList.Add('a'); $myList.Add('b'); $myList.Add('c')
PS> $myList.GetType().FullName
System.Collections.Generic.List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
PS> $myList | Where-Object { $_ -gt 'a' }
b
c

Nope, this works. It doesn't break even if I write the function that returns the list:

PS> function foo() { return $myList }
PS> foo | Where-Object { $_ -gt 'a' }
b
c

So, my questions are:

  1. Why isn't Where-Object works properly with Get-WinUserLanguageList results?
  2. Why does it suddenly starts working after I add the parens?
  3. Why am I unable to reproduce the same effect with my custom instance of the same generic List type?
1 Answers

Why isn't Where-Object [working] properly with Get-WinUserLanguageList results?

Because Get-WinUserLanguageList outputs an instance of [List[string]] as a single object, by telling PowerShell to not unroll it.

Why does it suddenly starts working after I add the parens?

Because nesting the pipeline in a separate statement causes PowerShell to unroll the list - this is PowerShell's default behavior.

Why am I unable to reproduce the same effect with my custom instance of the same generic List type?

Because you haven't told PowerShell to not unroll it :)

To reproduce the same behavior that Get-WinUserLanguageList exhibits, try with Write-Output -NoEnumerate:

$myList = [System.Collections.Generic.List[string]]::new()
$myList.Add('a')
$myList.Add('b')
$myList.Add('c')

Write-Output $myList -NoEnumerate | Where-Object { $_ -gt 'a' }

-NoEnumerate tells PowerShell to not unroll the list

As to why this particular cmdlet behaves this way, the answer is probably just that "the developer who designed and implemented it made a bad choice".

Related