Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 27 additions & 8 deletions src/System.Management.Automation/engine/InternalCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,7 @@ public ScriptBlock FilterScript
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "CaseSensitiveNotInSet")]
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsSet")]
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "IsNotSet")]
[Parameter(Mandatory = true, Position = 0, ParameterSetName = "Not")]
[ValidateNotNullOrEmpty]
public string Property
{
Expand Down Expand Up @@ -1199,6 +1200,16 @@ public SwitchParameter IsNot
get { return _binaryOperator == TokenKind.IsNot; }
}

/// <summary>
/// Binary operator -Not.
/// </summary>
[Parameter(Mandatory = true, ParameterSetName = "Not")]
public SwitchParameter Not
{
set { _binaryOperator = TokenKind.Not; }
get { return _binaryOperator == TokenKind.Not; }
}

#endregion binary operator parameters

private readonly CallSite<Func<CallSite, object, bool>> _toBoolSite =
Expand All @@ -1211,6 +1222,16 @@ private static Func<object, object, object> GetCallSiteDelegate(ExpressionType e
return (x, y) => site.Target.Invoke(site, x, y);
}

private static Func<object, object, object> GetCallSiteDelegateBoolean(ExpressionType expressionType, bool ignoreCase)
{
// flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it
// equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to
// be compared under the bool context. So that '"string" | Where-Object Length' would behave
// just like '"string" | Where-Object {$_.Length}'.
var site = CallSite<Func<CallSite, object, object, object>>.Create(binder: PSBinaryOperationBinder.Get(expressionType, ignoreCase));
return (x, y) => site.Target.Invoke(site, y, x);
}

private static Tuple<CallSite<Func<CallSite, object, IEnumerator>>, CallSite<Func<CallSite, object, object, object>>> GetContainsCallSites(bool ignoreCase)
{
var enumerableSite = CallSite<Func<CallSite, object, IEnumerator>>.Create(PSEnumerableBinder.Get());
Expand Down Expand Up @@ -1261,12 +1282,7 @@ protected override void BeginProcessing()
}
else
{
// flip 'lval' and 'rval' in the scenario '... | Where-Object property' so as to make it
// equivalent to '... | Where-Object {$true -eq property}'. Because we want the property to
// be compared under the bool context. So that '"string" | Where-Object Length' would behave
// just like '"string" | Where-Object {$_.Length}'.
var site = CallSite<Func<CallSite, object, object, object>>.Create(PSBinaryOperationBinder.Get(ExpressionType.Equal, true));
_operationDelegate = (x, y) => site.Target.Invoke(site, y, x);
_operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.Equal, ignoreCase: true);
}
break;
case TokenKind.Ceq:
Expand Down Expand Up @@ -1338,6 +1354,9 @@ protected override void BeginProcessing()
_operationDelegate =
(lval, rval) => ParserOps.MatchOperator(Context, PositionUtilities.EmptyExtent, lval, rval, notMatch: true, ignoreCase: false);
break;
case TokenKind.Not:
_operationDelegate = GetCallSiteDelegateBoolean(ExpressionType.NotEqual, ignoreCase: true);
break;
// the second to last parameter in ContainsOperator has flipped semantics compared to others.
// "true" means "contains" while "false" means "notcontains"
case TokenKind.Icontains:
Expand Down Expand Up @@ -1463,7 +1482,7 @@ protected override void ProcessRecord()
else
{
// Both -Property and -Value need to be specified if the user specifies the binary operation
if (_valueNotSpecified && (_binaryOperator != TokenKind.Ieq || !_forceBooleanEvaluation))
if (_valueNotSpecified && ((_binaryOperator != TokenKind.Ieq && _binaryOperator != TokenKind.Not) || !_forceBooleanEvaluation))
{
// The binary operation is specified explicitly by the user and the -Value parameter is
// not specified
Expand Down Expand Up @@ -1829,4 +1848,4 @@ protected override void EndProcessing()
#endregion Set-StrictMode

#endregion Built-in cmdlets that are used by or require direct access to the engine.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Describe "Where-Object" -Tags "CI" {
BeforeAll {
$Computers = @(
[PSCustomObject]@{
ComputerName = "SPC-1234"
IPAddress = "192.168.0.1"
NumberOfCores = 1
Drives = 'C','D'
},
[PSCustomObject]@{
ComputerName = "BGP-5678"
IPAddress = ""
NumberOfCores = 2
Drives = 'C','D','E'
},
[PSCustomObject]@{
ComputerName = "MGC-9101"
NumberOfCores = 3
Drives = 'C'
}
)
}

It "Where-Object -Not Prop" {
$Result = $Computers | Where-Object -Not 'IPAddress'
$Result.Count | Should -Be 2
}

It 'Where-Object -FilterScript {$true -ne $_.Prop}' {
$Result = $Computers | Where-Object -FilterScript {$true -ne $_.IPAddress}
$Result.Count | Should -Be 2
}

It "Where-Object Prop" {
$Result = $Computers | Where-Object 'IPAddress'
$Result.Count | Should -Be 1
}

It 'Where-Object -FilterScript {$true -eq $_.Prop}' {
$Result = $Computers | Where-Object -FilterScript {$true -eq $_.IPAddress}
$Result.Count | Should -Be 1
}

It 'Where-Object -FilterScript {$_.Prop -contains Value}' {
$Result = $Computers | Where-Object -FilterScript {$_.Drives -contains 'D'}
$Result.Count | Should -Be 2
}

It 'Where-Object Prop -contains Value' {
$Result = $Computers | Where-Object Drives -contains 'D'
$Result.Count | Should -Be 2
}

It 'Where-Object -FilterScript {$_.Prop -in $Array}' {
$Array = 'SPC-1234','BGP-5678'
$Result = $Computers | Where-Object -FilterScript {$_.ComputerName -in $Array}
$Result.Count | Should -Be 2
}

It 'Where-Object $Array -in Prop' {
$Array = 'SPC-1234','BGP-5678'
$Result = $Computers | Where-Object ComputerName -in $Array
$Result.Count | Should -Be 2
}

It 'Where-Object -FilterScript {$_.Prop -ge 2}' {
$Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -ge 2}
$Result.Count | Should -Be 2
}

It 'Where-Object Prop -ge 2' {
$Result = $Computers | Where-Object NumberOfCores -ge 2
$Result.Count | Should -Be 2
}

It 'Where-Object -FilterScript {$_.Prop -gt 2}' {
$Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -gt 2}
$Result.Count | Should -Be 1
}

It 'Where-Object Prop -gt 2' {
$Result = $Computers | Where-Object NumberOfCores -gt 2
$Result.Count | Should -Be 1
}

It 'Where-Object -FilterScript {$_.Prop -le 2}' {
$Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -le 2}
$Result.Count | Should -Be 2
}

It 'Where-Object Prop -le 2' {
$Result = $Computers | Where-Object NumberOfCores -le 2
$Result.Count | Should -Be 2
}

It 'Where-Object -FilterScript {$_.Prop -lt 2}' {
$Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -lt 2}
$Result.Count | Should -Be 1
}

It 'Where-Object Prop -lt 2' {
$Result = $Computers | Where-Object NumberOfCores -lt 2
$Result.Count | Should -Be 1
}

It 'Where-Object -FilterScript {$_.Prop -Like Value}' {
$Result = $Computers | Where-Object -FilterScript {$_.ComputerName -like 'MGC-9101'}
$Result.Count | Should -Be 1
}

It 'Where-Object Prop -like Value' {
$Result = $Computers | Where-Object ComputerName -like 'MGC-9101'
$Result.Count | Should -Be 1
}

It 'Where-Object -FilterScript {$_.Prop -Match Pattern}' {
$Result = $Computers | Where-Object -FilterScript {$_.ComputerName -match '^MGC.+'}
$Result.Count | Should -Be 1
}

It 'Where-Object Prop -like Value' {
$Result = $Computers | Where-Object ComputerName -match '^MGC.+'
$Result.Count | Should -Be 1
}
}