From b3be70d554e753c6fd31afbd0c4daa8b0d344bee Mon Sep 17 00:00:00 2001 From: Simon Wahlin Date: Fri, 9 Mar 2018 18:17:32 +0100 Subject: [PATCH 1/3] Where-Object: add parameter 'Not' Fix #3529 --- .../engine/InternalCommands.cs | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 63e4c063f72..5cfa2e5584c 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -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 { @@ -1199,6 +1200,16 @@ public SwitchParameter IsNot get { return _binaryOperator == TokenKind.IsNot; } } + /// + /// Binary operator -Not + /// + [Parameter(Mandatory = true, ParameterSetName = "Not")] + public SwitchParameter Not + { + set { _binaryOperator = TokenKind.Not; } + get { return _binaryOperator == TokenKind.Not; } + } + #endregion binary operator parameters private readonly CallSite> _toBoolSite = @@ -1211,6 +1222,16 @@ private static Func GetCallSiteDelegate(ExpressionType e return (x, y) => site.Target.Invoke(site, x, y); } + private static Func 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>.Create(binder: PSBinaryOperationBinder.Get(expressionType, ignoreCase)); + return (x, y) => site.Target.Invoke(site, y, x); + } + private static Tuple>, CallSite>> GetContainsCallSites(bool ignoreCase) { var enumerableSite = CallSite>.Create(PSEnumerableBinder.Get()); @@ -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>.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: @@ -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: @@ -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 From e0b5bfff43f9bd2bdad8998972ae778ca3467b68 Mon Sep 17 00:00:00 2001 From: Simon Wahlin Date: Thu, 22 Mar 2018 06:49:48 +0100 Subject: [PATCH 2/3] Tests for Where-Object Cmdlet (#3529) --- .../Where-Object.Tests.ps1 | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 new file mode 100644 index 00000000000..ac97eb3b85f --- /dev/null +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 @@ -0,0 +1,129 @@ +# 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 -BeExactly 2 + } + + It 'Where-Object -FilterScript {$true -ne $_.Prop}' { + $Result = $Computers | Where-Object -FilterScript {$true -ne $_.IPAddress} + $Result.Count | Should -BeExactly 2 + } + + It "Where-Object Prop" { + $Result = $Computers | Where-Object 'IPAddress' + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object -FilterScript {$true -eq $_.Prop}' { + $Result = $Computers | Where-Object -FilterScript {$true -eq $_.IPAddress} + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object -FilterScript {$_.Prop -contains Value}' { + $Result = $Computers | Where-Object -FilterScript {$_.Drives -contains 'D'} + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object Prop -contains Value' { + $Result = $Computers | Where-Object Drives -contains 'D' + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object -FilterScript {$_.Prop -in $Array}' { + $Array = 'SPC-1234','BGP-5678' + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -in $Array} + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object $Array -in Prop' { + $Array = 'SPC-1234','BGP-5678' + $Result = $Computers | Where-Object ComputerName -in $Array + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object -FilterScript {$_.Prop -ge 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -ge 2} + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object Prop -ge 2' { + $Result = $Computers | Where-Object NumberOfCores -ge 2 + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object -FilterScript {$_.Prop -gt 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -gt 2} + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object Prop -gt 2' { + $Result = $Computers | Where-Object NumberOfCores -gt 2 + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object -FilterScript {$_.Prop -le 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -le 2} + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object Prop -le 2' { + $Result = $Computers | Where-Object NumberOfCores -le 2 + $Result.Count | Should -BeExactly 2 + } + + It 'Where-Object -FilterScript {$_.Prop -lt 2}' { + $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -lt 2} + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object Prop -lt 2' { + $Result = $Computers | Where-Object NumberOfCores -lt 2 + $Result.Count | Should -BeExactly 1 + } + + # Like Match + It 'Where-Object -FilterScript {$_.Prop -Like Value}' { + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -like 'MGC-9101'} + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object Prop -like Value' { + $Result = $Computers | Where-Object ComputerName -like 'MGC-9101' + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object -FilterScript {$_.Prop -Match Pattern}' { + $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -match '^MGC.+'} + $Result.Count | Should -BeExactly 1 + } + + It 'Where-Object Prop -like Value' { + $Result = $Computers | Where-Object ComputerName -match '^MGC.+' + $Result.Count | Should -BeExactly 1 + } +} + From 7b61668955b1f35617bfd8a92c157dd3eb5276fa Mon Sep 17 00:00:00 2001 From: Simon Wahlin Date: Fri, 23 Mar 2018 17:14:32 +0100 Subject: [PATCH 3/3] Address PR feedback --- .../engine/InternalCommands.cs | 4 +- .../Where-Object.Tests.ps1 | 43 +++++++++---------- 2 files changed, 23 insertions(+), 24 deletions(-) diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 5cfa2e5584c..ea04bee4091 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -1201,7 +1201,7 @@ public SwitchParameter IsNot } /// - /// Binary operator -Not + /// Binary operator -Not. /// [Parameter(Mandatory = true, ParameterSetName = "Not")] public SwitchParameter Not @@ -1848,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. -} \ No newline at end of file +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 index ac97eb3b85f..18e53c2e74a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 @@ -1,5 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. + Describe "Where-Object" -Tags "CI" { BeforeAll { $Computers = @( @@ -25,105 +26,103 @@ Describe "Where-Object" -Tags "CI" { It "Where-Object -Not Prop" { $Result = $Computers | Where-Object -Not 'IPAddress' - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object -FilterScript {$true -ne $_.Prop}' { $Result = $Computers | Where-Object -FilterScript {$true -ne $_.IPAddress} - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It "Where-Object Prop" { $Result = $Computers | Where-Object 'IPAddress' - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object -FilterScript {$true -eq $_.Prop}' { $Result = $Computers | Where-Object -FilterScript {$true -eq $_.IPAddress} - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object -FilterScript {$_.Prop -contains Value}' { $Result = $Computers | Where-Object -FilterScript {$_.Drives -contains 'D'} - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object Prop -contains Value' { $Result = $Computers | Where-Object Drives -contains 'D' - $Result.Count | Should -BeExactly 2 + $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 -BeExactly 2 + $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 -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object -FilterScript {$_.Prop -ge 2}' { $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -ge 2} - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object Prop -ge 2' { $Result = $Computers | Where-Object NumberOfCores -ge 2 - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object -FilterScript {$_.Prop -gt 2}' { $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -gt 2} - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object Prop -gt 2' { $Result = $Computers | Where-Object NumberOfCores -gt 2 - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object -FilterScript {$_.Prop -le 2}' { $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -le 2} - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object Prop -le 2' { $Result = $Computers | Where-Object NumberOfCores -le 2 - $Result.Count | Should -BeExactly 2 + $Result.Count | Should -Be 2 } It 'Where-Object -FilterScript {$_.Prop -lt 2}' { $Result = $Computers | Where-Object -FilterScript {$_.NumberOfCores -lt 2} - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object Prop -lt 2' { $Result = $Computers | Where-Object NumberOfCores -lt 2 - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } - # Like Match It 'Where-Object -FilterScript {$_.Prop -Like Value}' { $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -like 'MGC-9101'} - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object Prop -like Value' { $Result = $Computers | Where-Object ComputerName -like 'MGC-9101' - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object -FilterScript {$_.Prop -Match Pattern}' { $Result = $Computers | Where-Object -FilterScript {$_.ComputerName -match '^MGC.+'} - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } It 'Where-Object Prop -like Value' { $Result = $Computers | Where-Object ComputerName -match '^MGC.+' - $Result.Count | Should -BeExactly 1 + $Result.Count | Should -Be 1 } } -