2

Why this

Get-WinUserLanguageList | Where-Object LanguageTag -eq en-US

returns empty list (seems it does not filter values) but this

(Get-WinUserLanguageList) | Where-Object LanguageTag -eq en-US

does work? Usually I don't need () but in this case they are mandatory, but why?

mklement0
  • 382,024
  • 64
  • 607
  • 775
RandomB
  • 3,367
  • 19
  • 30
  • 1
    What exactly do you mean by _"does not work"_? – Clijsters May 10 '19 at 15:09
  • 1
    I think he means there is no output selected, and I have to admit it does strike me as odd. – codewario May 10 '19 at 15:13
  • It does not filter. The result is empty which looks that it does not filter values from the list. But with parenthesis I get a correct result – RandomB May 10 '19 at 15:13
  • Lol! Just to throw some fuel on the fire. If you run `Get-WinUserLanguageList | where { $_.LanguageTag -eq 'en-US' }` you get values as well. I've never actually seen `Where-object` called without a code block like you have it. – Adam May 10 '19 at 15:29
  • I don't know all of the nuances with that syntax either as normally I use a `ScriptBlock` when filtering with `Where-Object`, but it's valid. – codewario May 10 '19 at 15:31
  • @Adam your version returns all languages instead of filter out unequal. Also I tried `-ne` instead of `-eq` and I get the same result: all items in both cases which is wrong (nothing filtered) – RandomB May 10 '19 at 15:31
  • Another strange thing: if I write `dir|where N` and press TAB, I get auto-completed property name but in the case of this strange cmdlet - nothing is auto-completed. It returns list of items instead of array of items. So, may be @BendertheGreatest is right - and reason is List instead of Array? – RandomB May 10 '19 at 15:34
  • @Paul-AG: `Get-WinUserLanguageList | Where-Object { $_.LanguageTag -eq 'en-US' }` does not return all languages. I just tested this locally and if I use some other language tag, the `en-US` entry is not returned – codewario May 10 '19 at 15:38
  • super strange, I copy-pasted your snippet and it returns my 2 languages, not only "en-US". My PSVersion is: 5.1.17763.316 – RandomB May 10 '19 at 15:40
  • I've only got one language installed but if i change the tag to `fr-CA` I do not get my `en-US` pack returned. – codewario May 10 '19 at 15:41
  • @Paul-AG Lol! I have no problem filtering results using that snippet. If I change the value from 'us-EN' to 'us' for example, I get no results. Without seeing your work, its tough to say why I'm getting different results from you. – Adam May 10 '19 at 15:43
  • 1
    @Adam: The simplified, script-block-less syntax was introduced in PowerShell v3, and it is called a _comparison statement_. _Typically_, the two syntax forms are interchangeable, but in this particular case - surprisingly - they're not: the comparison statement outputs nothing, and the script-block form outputs the one and only collection-as-a-whole input object; see the footnote in my answer for an explanation. – mklement0 May 10 '19 at 16:16

4 Answers4

4

Usually I don't need () but in this case they are mandatory, but why?

  • (...) forces enumeration of the elements of a collection output by the enclosed command in a pipeline.

  • This shouldn't be necessary, but is in your case, because Get-WinUserLanguageList exhibits nonstandard behavior: instead of outputting multiple result objects one by one to the pipeline, it emits an entire collection[1] as a single output object.

    • Without the enclosing (...), the command in the next pipeline segment - Where-Object in your case - therefore receives just one input - the entire collection - and operates on it rather than on the elements one by one.
      Since the collection object itself has no LanguageType property, nothing matches, and you get no output.[2]

As mentioned in the comments, you can pipe a command's output to Get-Member to see the (distinct) types of its output objects; for standard cmdlets, you'd see the types of the individual objects output, not a collection type.


[1] Specifically, the collection is a generic list of type [System.Collections.Generic.List[Microsoft.InternationalSettings.Commands.WinUserLanguage]].

[2] You're using simplified syntax in your command - Where-Object LanguageType -eq en-US - instead of the more verbose, but more flexible script-block syntax - Where-Object { $_.LanguageType -eq 'en-US' }. Had you used the latter, your command would have accidentally returned the entire collection and thereby effectively all languages. The reason is that only the script-block syntax applies member-access enumeration to the input collection, which means that even though $_ itself doesn't have a .LanguageTag property, the elements do, and their values are returned as an array. With an array as the LHS, -eq acts as a filter, and as long as en-US is among the values returned, the -eq operation will still be considered $true, causing the input object - the entire collection - to be passed through.
This surprising discrepancy in behavior between the two seemingly equivalent syntax form is discussed in GitHub issue #9576.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 2
    sounds very believable! Probably, this cmdlet is not written correctly - in violation of the convention for returning collections – RandomB May 10 '19 at 15:48
  • 2
    Just to add to @mklement0's response. If you were to see the members of each... `(Get-WinUserLanguageList) | gm` vs `Get-WinUserLanguageList | gm` you'd notice the difference in return values. – Adam May 10 '19 at 15:53
  • 1
    Yes, `Get-Member` or its alias `gm` is a great tool to inspect objects when you're not quite sure what your returned object looks like. It shows all members on the object as well as the type. – codewario May 10 '19 at 15:56
2

Get-WinUserLanguageList returns an array of System.Generic.Collection.List objects. That underlying list is what you need to filter on.

Placing the cmdlet in parentheses unrolls the underlying collection without having to iterate over every index in the returned array. mklement0's answer explains more about this behavior and why Get-WinUserLanguageList works differently than most other cmdlets that return collections.

codewario
  • 19,553
  • 20
  • 90
  • 159
1

As mentioned above you're getting a list object and not the WinUserLanguage object you're expecting.

PS C:\Users\admin user> $test = Get-WinUserLanguageList

PS C:\Users\admin user> $test.GetType()

IsPublic IsSerial Name                                     BaseType                                                                                                                           
-------- -------- ----                                     --------                                                                                                                           
True     True     List`1                                   System.Object                                                                                                                      



PS C:\Users\admin user> $test[0].GetType()

IsPublic IsSerial Name                                     BaseType                                                                                                                           
-------- -------- ----                                     --------                                                                                                                           
True     False    WinUserLanguage                          System.Object                                                                                                                      

You could also use this.

(Get-WinUserLanguageList).where({$_.LanguageTag -eq 'en-US'})
mklement0
  • 382,024
  • 64
  • 607
  • 775
RoscoeT
  • 87
  • 8
  • This is a little more Posh-like I think: `Get-WinUserLanguageList | Where-Object { $_.LanguageTag -eq 'en-us' }` – codewario May 10 '19 at 15:33
  • this does not work for me: it returns ALL languages, not that where tag is "en-US". `-ne` condition - the same - returns all. – RandomB May 10 '19 at 15:38
  • Sure. One thing of note is that the Where-Object cmdlet is significantly slower than the .where() method. It might not make a diff here but good practice for larger lists. – RoscoeT May 10 '19 at 15:39
  • @RoscoeT true true, but we're not moving mountains here :) – codewario May 10 '19 at 15:40
  • @BendertheGreatest Maybe you're not ;) – RoscoeT May 10 '19 at 15:52
-2

FOund :) Check Get-member. It's return array.

Adam
  • 52
  • 3