Skip to content

Correct handling of explicit -[Operator]:$false parameter values in Where-Object#26485

Merged
iSazonov merged 5 commits intoPowerShell:masterfrom
yotsuda:fix/where-object-switches
Nov 21, 2025
Merged

Correct handling of explicit -[Operator]:$false parameter values in Where-Object#26485
iSazonov merged 5 commits intoPowerShell:masterfrom
yotsuda:fix/where-object-switches

Conversation

@yotsuda
Copy link
Contributor

@yotsuda yotsuda commented Nov 19, 2025

PR Summary

Fix #26480
Fix incorrect behavior when switch parameters (comparison operators) are explicitly set to $false in Where-Object.

Breaking Change

This PR contains a breaking change categorized as Bucket 3: Unlikely Grey Area (ref: breaking-change-contract.md#bucket-3).

Previous behavior: When operator parameters (e.g., -Not, -EQ) were explicitly set to :$false, the cmdlet would still attempt to use that operator, leading to incorrect results.

New behavior: When operator parameters are explicitly set to :$false, the cmdlet correctly falls back to default boolean property evaluation (as if no operator was specified).

Rationale: This corrects behavior in a subtle corner case that has no obvious utility. The previous behavior was unintuitive and likely not depended upon by existing scripts.

PR Context

When using Where-Object with explicit :$false on operator parameters (e.g., -Not:$false, -EQ:$false), the cmdlet should fall back to default boolean property evaluation instead of using the specified operator.

Before this fix:

@([pscustomobject]@{Prop=$true}, [pscustomobject]@{Prop=$false}) | Where-Object -Not:$false Prop
# Returns: Prop=$false (incorrectly applies -Not operator despite :$false)

After this fix:

@([pscustomobject]@{Prop=$true}, [pscustomobject]@{Prop=$false}) | Where-Object -Not:$false Prop
# Returns: Prop=$true (correctly falls back to boolean evaluation)

# Using operator with :$false and -Value should throw error (no operator to use)
1..5 | Where-Object -GT:$false -Property { $_ } -Value 3
# Error: An operator is required...

PR Checklist

  • PR has a meaningful title
  • Summarized changes
  • Change is not breaking
  • Make sure you've added a new test if existing tests do not effectively test the code changed
  • New and old tests pass locally

@yotsuda yotsuda marked this pull request as ready for review November 19, 2025 10:24
@iSazonov iSazonov added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Nov 19, 2025
@iSazonov iSazonov requested a review from Copilot November 19, 2025 11:27
@iSazonov
Copy link
Collaborator

Such using (-GT:$false now throw) looks weird :-)

@mklement0 What do you think?

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes the handling of explicit :$false parameter values for comparison operators in Where-Object. When a switch parameter operator is explicitly set to $false (e.g., -GT:$false), the cmdlet should fall back to default boolean property evaluation instead of using that operator.

Key changes:

  • Added if (value) guards to all 33 operator parameter setters in WhereObjectCommand to prevent operator assignment when explicitly set to $false
  • Added comprehensive test coverage for the :$false behavior across multiple operators
  • Tests verify both the boolean evaluation fallback and the error handling when :$false is used with -Value

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/System.Management.Automation/engine/InternalCommands.cs Added conditional guards to all operator property setters (EQ, NE, GT, LT, Like, Match, Contains, In, Is, Not, and their case-sensitive variants) to only apply operators when explicitly set to $true
test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 Added new test context with 11 tests covering :$false behavior for representative operators and error cases when combined with -Value parameter

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@mklement0
Copy link
Contributor

Such using (-GT:$false now throw) looks weird :-)

It does. The awkwardness comes from the fact that what are semantically operators are - of technical necessity - implemented as [switch] parameters.

However, it is consistent with the other fixes of the negated-switch problem to treat -GT:$false as if -GT hadn't been specified at all.

In the case of these operator switches, this has the following implications for those representing binary operators, using the example of GT:

  • Where-Object -Property Foo -GT:$false, which is currently a syntax error (due to the missing second operand), will work after the fix, resulting in implicit Boolean evaluation of the property.

  • Where-Object -Property Foo -GT:$false -Value 3, which is currently the same as Where-Object -Property Foo -GT -Value 3, will stop working after the fix, resulting in a syntax error (due to missing a binary operator switch).

I'd say that, unlike with more typical [switch] parameters, it is unlikely that people will want to use negated operator switches in practice, at least for binary operators: negating such a switch then requires conditional construction of the remaining parameter values, in which case not including the switch in the programmatically constructed arguments to begin with is simpler.

It may make sense with -Not, however, which is the only unary operator supported, from what I can tell.

In short: While users are unlikely to run into this scenario, resolving it in a manner consistent with the other negated-switch fixes makes sense to me.


@yotsuda, as an aside:

1..5 | Where-Object -GT:$false -Property { $_ } -Value 3 even with -GT instead of GT:$false doesn't work as intended, because you cannot use a delay-bind script block with the -Property parameter; your command is equivalent to:
1..5 | Where-Object -GT:$false -Property { $_ }.ToString() -Value 3 and therefore to ``1..5 | Where-Object -GT:$false -Property ' $_ ' -Value 3`

The current, problematic inability to make the input object itself (rather than one of its properties) the target of the evaluation has been discussed before:

@yotsuda
Copy link
Contributor Author

yotsuda commented Nov 20, 2025

Thank you @mklement0 for the thorough analysis and for confirming the consistency with other negated-switch fixes! I appreciate the pointer to #8357 regarding the -Property limitation.

@iSazonov
Copy link
Collaborator

iSazonov commented Nov 20, 2025

@mklement0 Thanks! I think it is a breaking change - brucket 3 (unlikely grey area) @yotsuda Please add this in the PR description.
https://github.com/PowerShell/PowerShell/blob/master/docs/dev-process/breaking-change-contract.md#bucket-3-unlikely-grey-area

GitHub
PowerShell for every system! Contribute to PowerShell/PowerShell development by creating an account on GitHub.

@iSazonov iSazonov added the CL-BreakingChange Indicates that a PR should be marked as a breaking change in the Change Log label Nov 20, 2025
@iSazonov iSazonov self-assigned this Nov 20, 2025

It "-GT:$false should behave like default boolean evaluation" {
# With -GT:False, the parameter is not specified, so default boolean evaluation applies
$result = $testData | Where-Object Age -GT:$false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yotsuda It seems -LT is skipped. Please re-check that we have test for all operators.

Copy link
Contributor Author

@yotsuda yotsuda Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out. I've added the missing tests for all case-insensitive operators in 357e79c:

  • -LT:$false
  • -NE:$false
  • -GE:$false
  • -LE:$false
  • -NotLike:$false
  • -NotMatch:$false
  • -NotContains:$false
  • -NotIn:$false
  • -IsNot:$false

Also fixed the escaping of $false in test names and the Context title for consistent display.
All 17 case-insensitive operators are now covered. I didn't add tests for the case-sensitive variants (-CEQ, -CGT, -CLT, etc.) since they share the same setter logic in the property implementations. Should I add those as well for completeness?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to cover all code paths.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I've added tests for all 14 case-sensitive operators (CEQ, CNE, CGT, CLT, CGE, CLE, CLike, CNotLike, CMatch, CNotMatch, CContains, CNotContains, CIn, CNotIn) and additional error tests in 688b06c.

All 59 tests pass. This now covers all code paths for explicit :$false handling.

It "-GT:$false with -Value should throw an error requiring an operator" {
# When a switch parameter is set to $false with -Value specified,
# it should throw an error because no valid operator is active
{ $testData | Where-Object Age -GT:$false -Value 25 } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An operator is required to compare the two specified values. Include a valid operator in the command, and then try the command again. For example, Get-Process | Where-Object -Property Name -eq Idle

Hmm, formally it is not correct. @mklement0 Do you expect user's reports about this? It seems we need to update the error message.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the operator is technically there, but conceptually it isn't, due to its negation.
Given that I expect very few users to even attempt this, I don't think it's worth addressing in the error message.

yotsuda and others added 3 commits November 20, 2025 15:43
….Tests.ps1

Co-authored-by: Ilya <darpa@yandex.ru>
- Add tests for -LT, -NE, -GE, -LE, -NotLike, -NotMatch, -NotContains, -NotIn, -IsNot
- Fix escaping of $false in test names for consistent display
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Collaborator

@iSazonov iSazonov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although not all tests have been formally added (to cover everything), I believe they will not add value.

@iSazonov iSazonov merged commit 108eb85 into PowerShell:master Nov 21, 2025
37 checks passed
@iSazonov
Copy link
Collaborator

@mklement0 Thanks for help!

@yotsuda yotsuda deleted the fix/where-object-switches branch November 21, 2025 12:07
SIRMARGIN pushed a commit to SIRMARGIN/PowerShell that referenced this pull request Dec 12, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-BreakingChange Indicates that a PR should be marked as a breaking change in the Change Log CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

None yet

Development

Successfully merging this pull request may close these issues.

-[Operator]:$false not respected in Where-Object

4 participants