Skip to content

Request: Where-Object used without -Property works on "the object" #8357

Closed

Description

@HumanEquivalentUnit

These don't work, but it would be nice if they did:

PS C:\> 'test.txt', '', 'example.xml' | where -match 'txt'
where-object : The specified operator requires both the -Property and -Value parameters.

PS C:\> 1..10 | ? -gt $x
?: The specified operator requires both the -Property and -Value parameters.

but integers and strings have no -Property way to access their values. Select-String helps for text, but outputs MatchInfo not String. And doesn't help for any of the operators except -match.

Used without -Property it could implicitly use $_ with whatever casting that implies, the same as if you'd written { $_ -operator 'value' } as the filterscript.

And then you could do:

PS C:\> 'test.txt', '', 'example.xml' | ? | foreach { .. }

and that would implicitly act like ? { $_ } which does a boolean cast on the object and passes true ones.

Activity

  1. jcotton42 commented on Nov 29, 2018

    @jcotton42
    Contributor

    I like this proposal, but I'm very not sold on just where-object testing for truthiness, feels too cryptic

  2. HumanEquivalentUnit commented on Nov 29, 2018

    @HumanEquivalentUnit
    ContributorAuthor

    I'm not sold on that part either, but where { $_ } is pretty cryptic, and where -property x -eq $true casts the wrong way, $true gets cast to string, instead of the property being cast to bool, so that looks like it would work but it "surprisingly" doesn't.

    The concept "filter things which are truthy" is useful, and it sort of falls out of this request - with a property it maps to {$_.Prop -op $value}, then with this request if you don't specify -property it works on "the object in the pipeline" and maps to {$_ -op $value}, and then if you don't specify the operation either it would map to just {$_}. This part could be separated out into a different request for a way to say something like where-object -Truthy.

  3. nohwnd commented on Nov 29, 2018

    @nohwnd
    Collaborator

    I would like to say that even though the operators already filter collections without using where:

    PS C:\> 'test.txt', '', 'example.xml' -match 'txt'
    PS C:\> 1..10 -gt $x 

    I am still missing the proposed syntax, because it would allow for using less parentheses. A command followed by a pipeline is not "overloaded", unlike a command followed by an operator. In that case the operator is considered a parameter of the command, and parentheses must be used:

    function Get-Range { 1..10 }
    $x = 5
    
    
    ( Get-Range ) -gt $x
    
    # vs.
    
    Get-Range | where -gt $x

    The truthiness I am also not sold at.

  4. BrucePay commented on Nov 29, 2018

    @BrucePay
    Collaborator

    HumanEquivalentUnit The inability to specify the whole object has annoyed me for years. I'd always expected that we would use -Property _ to mean the whole object but that never got implemented. Of course there's no reason not to do it now. So

    1..10 | where _ -gt 5

    would be equivalent to

    1..10 | where {$_ -gt 5}
  5. vexx32 commented on Nov 29, 2018

    @vexx32
    Collaborator

    That's not a bad idea Bruce Payette (@BrucePay) but it does prevent anyone from making use of a property actually named _ (i.e., | where {$_._ -gt 5} would be rendered impossible to work with using the parameter syntax).

  6. BrucePay commented on Nov 29, 2018

    @BrucePay
    Collaborator

    Rain Sallow (/u/ta11ow) (@vexx32) Practically speaking, the likelihood of a property named '_' is miniscule. And should it occur, you can always use the scriptblock form of the command. After all, the property syntax is a very small subset of what you can do in a scriptblock. Sso I don't see this as a significant concern.

  7. vexx32 commented on Nov 29, 2018

    @vexx32
    Collaborator

    Certainly, but I don't see why Where _ -gt 5 is better than Where-Object -gt 5; arguably the latter is more clear in intent, and doesn't risk being confused for an (admittedly unwise) property name. 😄

  8. HumanEquivalentUnit commented on Nov 29, 2018

    @HumanEquivalentUnit
    ContributorAuthor

    Bruce Payette (@BrucePay) I like the use of _ well enough; right now, gci | where length filters on the property Length being truthy, so |where _ is a valid instruction. Clashing with properties called _ doesn't bother me, but special casing -Property _ to mean "the object" feels weird, is there anywhere else that meaning exists? Would people then expect it to work with other cmdlets that have a -Property?

    What do you think of the idea of it being valid to use where-object with no parameters at all?

    It's growing on me to treat the word "object" in "where-object" as implying $_; if we agree that the parameter form -Property 'Foo' -oper -Value $bar equals a scriptblock form { $_.Foo -oper $bar }, then it makes sense to allow leaving any of those parts out in the parameter form and have the behaviour the same as if you left them out in the scriptblock form. The following patterns fall out of it, the first two are valid now, the last two are not, but seem fitting:

    # param + operator
    | where-object { $_.Foo -gt $bar }                  # scriptblock pattern becomes:
    | where-object -Property Foo -gt -Value $bar        # parameter form.
    | where-object Foo -gt $bar                         # binding without parameter names.
    Import-Csv test.csv | where-object Quantity -gt 5   # example use.
    
    
    # param only
    | where-object { $_.Foo }                     # casting the property value to bool
    | where-object -Property Foo                  # doing that by leving out -Value param
    | where-object  Foo                           # and by positional binding
    Import-Csv test.csv | where-object InStock
    
    
    # Operator only
    | where-object { $_ -gt $bar }     # no property specified, ->
    | where-object -gt $bar            # $_ is implied by the "object" part of "Where-Object"
    Get-Content test.txt | where-object -match 'baz'
    
    # Neither
    | where-object { $_ }        # implicitly casting the object to bool
    | where-object               #  $_ is implied by the "object" part of "where-object"
    Get-Content test.txt| Where-Object
    

    If we did use _ that also works, but it is a bit of a weird special case, and things seem to work neatly without that.

  9. mklement0 commented on Nov 29, 2018

    @mklement0
    Contributor

    Good point re name collisions, Rain Sallow (/u/ta11ow) (@vexx32) and HumanEquivalentUnit; omitting -Property altogether (... | where -gt 5), as HumanEquivalentUnit suggests, seems like a good solution.

    While omission may be slightly more obscure at first, proper documentation should address that.

    Similarly, I feel that it should be sufficient to properly document the implicit Boolean logic that Where-Object already applies, obviating the need for something like -Truthy.

    After all, something like 'hi', '', 'there' | Where-Object Length in lieu of 'hi', '', 'there' | Where-Object Length -gt 0 works fine.

    I've created two doc issues:

  10. vexx32 commented on Dec 1, 2018

    @vexx32
    Collaborator

    Michael Klement (@mklement0) HumanEquivalentUnit So I rolled some code to try this out, and it sort of works. Well, truthiness does, at least:

    PS> 0, 1, 0, 2, 0, 3 | Where-Object
    1
    2
    3
    

    The problem arises with how parameters are defined. If you attempt to do the above with Where-Object -gt 1 you hit an issue — the value 1 is assigned by position, and ends up being assigned to the -Property parameter, not -Value.

    I'm considering whether simply dropping -Value as a parameter might be a better option, and having the "value" be taken by the selected operator parameter, rather than have them be switch parameters. This would be a breaking change as it would technically remove an operator, but I think is preferable to having a confusing and potentially unpredictable doubling up on the parameters. (And, at least in the scripts I see, people actually specifying -Value by name are extremely rare.)

    The alternative would be doing the above without dropping -Value as a parameter, which means we have to do a runtime check to ensure users don't accidentally provide a value to both parameters.

    Either method is fine, but I'd be interested to hear what y'all think of that. 😄

  11. vexx32 commented on Dec 2, 2018

    @vexx32
    Collaborator

    HumanEquivalentUnit
    Update: Got a properly working implementation over here: https://github.com/vexx32/PowerShell/blob/093ef91fac588bb730a81ad2926166f23e4d08b5/src/System.Management.Automation/engine/InternalCommands.cs#L853

    Basically unless we want to do a whole lot of really kludgy implementation special-casing here, the most elegant and effective solution is just to completely drop the -Value parameter and have the -eq and other parameters (which are currently switches) take the value themselves (causing them to be typed object) and snip out the frankly just-getting-in-the-way-at-this-point -Value parameter completely.

    This allows the following new syntaxes:

    $Array | Where-Object # outputs only truthy inputobjects
    $Array | Where-Object -gt 3 # outputs only inputobjects that are numerically greater than 3

    As well, of course, as all current syntaxes, except one:

    $Array | Where-Object -Property $Name -eq -Value 2

    Any existing code instances where -Value is explicitly specified will unfortunately fail; and I don't really see an easy way around that issue. Without doing it like this, the $Array | Where-Object -gt 3 assigns 3 to the -Property parameter by position instead.

    Bruce Payette (@BrucePay) I'd appreciate your thoughts on this approach and/or any better alternatives if you are thinking of something I've missed. 😄

  12. mklement0 commented on Dec 2, 2018

    @mklement0
    Contributor

    Well done, Rain Sallow (/u/ta11ow) (@vexx32).

    I too was hoping that eliminating -Value can be considered a Bucket 3: Unlikely Grey Area change, but a quick search tells me that it is being used (I've gone through abut 20 pages' worth, which had many false positives, but I've encountered about a dozen distinct repos that use it).

    We can work around that with a bit of a kludge that simulates the presence of a -Value parameter, as in the following PowerShell PoC:

    [CmdletBinding()]
    param(
      # Operator with operand
      [object] $GT
      ,
      # Ideally, we'd hide this parameter from the syntax diagrams
      # See https://github.com/PowerShell/PowerShell/issues/7868
      [Parameter(ValueFromRemainingArguments)]
      [object[]] $LegacyRest
    )
    
    if ($LegacyRest.Count -gt 0) {
      if ($LegacyRest.Count -eq 1 -and $GT -is [string] -and 0 -eq '-Value'.IndexOf($GT, [StringComparison]::InvariantCultureIgnoreCase)) {
        # Direct operand was '-Value' (or an abbreviation), so we expect the true
        # operand as the one and only element of $LegacyRest.
        $GT = $LegacyRest[0]
      } else { # Unexpected extra arguments.
        # Should be a *statement*-terminating error.
        Throw "A parameter cannot be found that accepts '$LegacyRest'."
      }
    }
    
    # Echo the effective operand.
    "[$GT]"

    The above would support foo -GT 3 as well as foo -GT -Value 3.

    Caveats:

    • foo 3 -gt -Value would also work, but I don't think we need to worry about that.

    • Single-character abbreviation -v for -Value wouldn't work anymore, because in the absence of an actual -Value parameter -v unambiguously binds to -Verbose - again, not something I think we need to worry about.

    • Tab completion for -Value will go away, but I think that's fine, given that we want to steer users toward the operand syntax.

    • As suggested in Can we hide obsolete or internal-use-only helper parameters from syntax diagrams? #7868, it would be nice to be able to hide obsolete parameter or, as in this case, helper parameters that solely exist to preserve backward compatibility.

  13. vexx32 commented on Dec 2, 2018

    @vexx32
    Collaborator

    Hmm, with a ValueFromRemainingArguments...

    You're right, it is kind of a kludge. Considering the fact that -Value already exists I'd prefer to just keep it on as a parameter and have it determine which to use based on which is supplied.

    Not sure quite how to handle that in the actual code at the moment, though, due to how the parameters interact with the getters and setters.

  14. 5 remaining items

  15. rkeithhill commented on Nov 19, 2019

    @rkeithhill
    Collaborator

    I'd love to see this functionality!!

  16. vexx32 commented on Nov 19, 2019

    @vexx32
    Collaborator

    Running into parameter set issues again, I suppose that's predictable. I'd prefer to break it cleanly with my previous idea, but backwards compat would be a bit annoying to break I suppose. 🤔

    Will think on this some more this week/end and see if I can wrangle the parameter sets sensibly. So far that's a bit dicey to do. 🙂

  17. jszabo98 commented on Feb 4, 2020

    @jszabo98

    I'm not surprised there's not a simple way to search object property names and values without resorting to findstr. Select-string doesn't have the right effect.

    get-process cmd | findstr /i cmd

    Sure this works but it still returns all the properties:

    get-process cmd | where { $_ -match 'cmd' } 

    Here's my first attempt at a "search-object":

    # search-object.ps1
    
    param ($pattern)
    
    begin {
      $hash = @{}
    }
    
    process {
      $obj = $_
    
      $obj | Get-Member | foreach name | 
      foreach {
        $name = $_
        $value = $obj.$name
        if ($name -match $pattern -or $value -match $pattern) {
          $hash += @{$name = $value}
        }
      }
      [pscustomobject]$hash
    }
    get-process cmd | search-object cmd
    Modules         : {System.Diagnostics.ProcessModule (cmd.exe), System.Diagnostics.ProcessModule (ntdll.dll),
                      System.Diagnostics.ProcessModule (KERNEL32.DLL), System.Diagnostics.ProcessModule
                      (KERNELBASE.dll)...}
    Path            : C:\Windows\System32\cmd.exe
    MainModule      : System.Diagnostics.ProcessModule (cmd.exe)
    ProcessName     : cmd
    Name            : cmd
    MainWindowTitle : cmd - powershell
  18. mklement0 commented on Feb 4, 2020

    @mklement0
    Contributor

    jszabo98, this issue is about making object-based filtering via Where-Object easier, not about string searches; changing the currently unintuitive behavior of Select-String is the subject of the enhancement request in #10726.

  19. UberKluger commented on Jul 5, 2020

    @UberKluger

    jszabo98: the description of your intended results does not match with what the example commands are actually doing (suggesting that you perhaps don't "get" the way powershell works). You state that you are trying to search object property names and values but

    get-process cmd | findstr /i cmd
    

    simply finds all the lines produced by Get-Process AFTER its output objects are converted to text by Format-Table (or maybe Format-List for other cmdlets with more default displayed properties). In this case that's every line except the column headers, since you restricted Get-Process to only produce output objects for processes named "cmd". So no "selection" of property names or values.

    In

    get-process cmd | where { $_ -match 'cmd' }
    

    you are selecting Get-Process output objects which -match 'cmd' but -match only operates on [String] or [String[]] (i.e. single strings or arrays of string). Thus, every Get-Process output object is first converted to a [String] before the match is attempted. Since the ToString() method of [System.Diagnostics.Process] simply appends the process name to the type name (supplied by [Object].ToString()), $_ gets converted to "System.Diagnostics.Process (cmd)" which then matches so the entire object is passed through Where-Object.

    If you had not restricted the output of Get-Process, the result would have been similar except that the output would have included any process that had a name which matched 'cmd' (i.e. if the name had 'cmd' anywhere in it).

    In 'search-object.ps1', (ignoring several possible optimizations) you set $hash to an empty hash table in the -Begin script block (executed only once before processing any input objects) and then add each found item in the -Process script block. This means that the hash table will try to accumulate the matched properties of the current object and every preceding object. The most likely result is that you will have two objects match on the values of properties with same name or just have the names match $pattern. Attempting to add an item with the same name as an existing item will throw an exception. You could change $hash += @{$name = $value} to $hash[$name] = $value thus overwriting the value for each new object processed (automatically creating an item if it doesn't exist), however you will still accumulate items from previously processed objects. Unless this is what you intended, you need to place the $hash = @{} at the beginning of the -Process script block to have an empty hash table for each object to be matched (this would also solve the duplicate name problem).

  20. UberKluger commented on Jul 5, 2020

    @UberKluger

    Regarding the problem of accessing the object as a whole instead of a particular property, how about changing the allowed arguments to -Property to include methods? This would allow access to strings and value type objects as follows:

    Where-Object 'tostring()' -eq 'cmd' # trying to match a string object
    
    Where-Object 'tostring("x")' -match '00$' # trying to match a numeric value which is a multiple of 256
    # not the most efficient way, just a demonstration of the potential versatility available
    
    Where-Object 'toint32($null)' -gt 1024 # checking the value of a numeric or parsed string
    # had to use $null since no overload of ToInt32 with 0 arguments
    
    # "properties" are verbatim quoted to prevent parser errors
    

    The main argument in favour of this is that it provides the desired functionality for strings (vast majority of cases) but does not introduce any special cases or break existing syntax. While not as compact as perhaps desired, there might be efficiency benefits to directly calling a method and processing the result within the cmdlet instead of calling a script block to do the work.

  21. vexx32 commented on Jul 5, 2020

    @vexx32
    Collaborator

    That would probably be best served as a completely separate feature request UberKluger 🙂

    It would need investigation before making that assumption, though. The ways cmdlets can bind dynamic properties/methods is complicated, and although ForEach-Object does have a parameter that does that kind of thing (-MemberName) if my memory is decently reliable, it often ends up being slower than putting the method call in a scriptblock instead.

  22. mklement0 commented on Jul 5, 2020

    @mklement0
    Contributor

    Rain Sallow (/u/ta11ow) (@vexx32), you're probably thinking of #7700, though I suspect that there's ultimately no good reason for, say, Get-Item $PROFILE | ForEach-Object ToString to be slower than Get-Item $PROFILE / ForEach-Object { $_.ToString() }.

    UberKluger, I see searching string representations of objects as a separate use case and suggest sticking with the object/property scenario for Where-Object.

    If there really is a need to call .ToString() with arguments and test against the result, the script-block syntax has that covered (0x100 | Where-Object { $_.ToString("x") -match '00$' })

    A related scenario is to have a quick-and-dirty way to search the string representations as they would print to the console (the rich, user-friendly representations provided by the formatting system), line by line.
    This should be covered by Select-String, but currently isn't: e.g., you should be able to run Get-Date | Select-String July and get a result (in July, with an English-based culture), but you currently must use Get-Date | Out-String -Stream | Select-String July, i.e. an explicit call to the formatting system, because Select-String currently operates on the typically-useless .ToString() representations - see #10726 (comment)

  23. microsoft-github-policy-service commented on Nov 23, 2023

    @microsoft-github-policy-service
    Contributor

    This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

  24. dkaszews commented on Apr 23, 2025

    @dkaszews
    Contributor

    Bump from https://stackoverflow.com/questions/79588233/apply-where-object-shorthand-operators-to-entire-object/79588561#79588561

    Stack Overflow
    Where-Object's params providing shorthands for builtin operators, one can skip the scriptblock and write: Get-ChildItem | Where-Object { $_.Name -match '\.(txt|md)$' }

    as:
    Get-ChildItem | Where-Ob...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Issue-Discussionthe issue may not have a clear classification yet. The issue may generate an RFC or may be reclassifResolution-No ActivityIssue has had no activity for 6 months or moreWG-Cmdletsgeneral cmdlet issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions