diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index 09536ea00f..110dea324d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs @@ -289,10 +289,18 @@ namespace Microsoft.PowerShell.Commands /// /// A cmdlet to search through strings and files for particular patterns. /// - [Cmdlet(VerbsCommon.Select, "String", DefaultParameterSetName = "File", HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113388")] - [OutputType(typeof(MatchInfo), typeof(bool))] + [Cmdlet(VerbsCommon.Select, "String", DefaultParameterSetName = ParameterSetFile, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=113388")] + [OutputType(typeof(bool), typeof(MatchInfo), ParameterSetName = new[] { ParameterSetFile, ParameterSetObject, ParameterSetLiteralFile })] + [OutputType(typeof(string), ParameterSetName = new[] { ParameterSetFileRaw, ParameterSetObjectRaw, ParameterSetLiteralFileRaw })] public sealed class SelectStringCommand : PSCmdlet { + private const string ParameterSetFile = "File"; + private const string ParameterSetFileRaw = "FileRaw"; + private const string ParameterSetObject = "Object"; + private const string ParameterSetObjectRaw = "ObjectRaw"; + private const string ParameterSetLiteralFile = "LiteralFile"; + private const string ParameterSetLiteralFileRaw = "LiteralFileRaw"; + /// /// A generic circular buffer. /// @@ -965,7 +973,8 @@ namespace Microsoft.PowerShell.Commands /// /// Gets or sets the current pipeline object. /// - [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = "Object")] + [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = ParameterSetObject)] + [Parameter(ValueFromPipeline = true, Mandatory = true, ParameterSetName = ParameterSetObjectRaw)] [AllowNull] [AllowEmptyString] public PSObject InputObject @@ -988,7 +997,8 @@ namespace Microsoft.PowerShell.Commands /// Gets or sets files to read from. /// Globbing is done on these. /// - [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "File")] + [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetFile)] + [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetFileRaw)] [FileinfoToString] public string[] Path { get; set; } @@ -996,7 +1006,8 @@ namespace Microsoft.PowerShell.Commands /// Gets or sets literal files to read from. /// Globbing is not done on these. /// - [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = "LiteralFile")] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetLiteralFile)] + [Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, ParameterSetName = ParameterSetLiteralFileRaw)] [FileinfoToString] [Alias("PSPath", "LP")] public string[] LiteralPath @@ -1011,6 +1022,15 @@ namespace Microsoft.PowerShell.Commands private bool _isLiteralPath; + /// + /// Gets or sets a value indicating if only string values containing matched lines should be returned. + /// If not (default) return MatchInfo (or bool objects, when Quiet is passed). + /// + [Parameter(Mandatory = true, ParameterSetName = ParameterSetObjectRaw)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetFileRaw)] + [Parameter(Mandatory = true, ParameterSetName = ParameterSetLiteralFileRaw)] + public SwitchParameter Raw { get; set; } + /// /// Gets or sets a value indicating if a pattern string should be matched literally. /// If not (default) search using pattern as a Regular Expression. @@ -1028,7 +1048,9 @@ namespace Microsoft.PowerShell.Commands /// Gets or sets a value indicating if the cmdlet will stop processing at the first successful match and /// return true. If both List and Quiet parameters are given, an exception is thrown. /// - [Parameter] + [Parameter(ParameterSetName = ParameterSetObject)] + [Parameter(ParameterSetName = ParameterSetFile)] + [Parameter(ParameterSetName = ParameterSetLiteralFile)] public SwitchParameter Quiet { get; set; } /// @@ -1160,7 +1182,10 @@ namespace Microsoft.PowerShell.Commands private int _postContext = 0; - private IContextTracker GetContextTracker() => (_preContext == 0 && _postContext == 0) ? _noContextTracker : new ContextTracker(_preContext, _postContext); + // When we are in Raw mode or pre- and postcontext are zero, use the _noContextTracker, since we will not be needing trackedLines. + private IContextTracker GetContextTracker() => (Raw || (_preContext == 0 && _postContext == 0)) + ? _noContextTracker + : new ContextTracker(_preContext, _postContext); // This context tracker is only used for strings which are piped // directly into the cmdlet. File processing doesn't need @@ -1425,8 +1450,14 @@ namespace Microsoft.PowerShell.Commands return false; } - // If -quiet is specified but not -list return true on first match - if (Quiet && !List) + if (Raw) + { + foreach (MatchInfo match in contextTracker.EmitQueue) + { + WriteObject(match.Line); + } + } + else if (Quiet && !List) { WriteObject(true); } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 index d7b9052453..861d5b1146 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Select-String.Tests.ps1 @@ -68,7 +68,7 @@ Describe "Select-String" -Tags "CI" { } it "Should return an array of non matching strings when the switch of NotMatch is used and the string do not match" { - $testinputone | Select-String -Pattern "goodbye" -NotMatch | Should -Be "hello", "hello" + $testinputone | Select-String -Pattern "goodbye" -NotMatch | Should -BeExactly "hello", "Hello" } it "Should return the same as NotMatch" { @@ -76,7 +76,16 @@ Describe "Select-String" -Tags "CI" { $secondMatch = $testinputone | Select-String -pattern "goodbye" -n $equal = @(Compare-Object $firstMatch $secondMatch).Length -eq 0 - $equal | Should -Be True + $equal | Should -BeTrue + } + + It "Should return a string type when -Raw is used" { + $result = $testinputtwo | Select-String -Pattern "hello" -CaseSensitive -Raw + $result | Should -BeOfType "System.String" + } + + It "Should return ParameterBindingException when -Raw and -Quiet are used together" { + { $testinputone | Select-String -Pattern "hello" -Raw -Quiet -ErrorAction Stop } | Should -Throw -ExceptionType ([System.Management.Automation.ParameterBindingException]) } } @@ -106,13 +115,13 @@ Describe "Select-String" -Tags "CI" { It "Should return the name of the file and the string that 'string' is found if there is only one lines that has a match" { $expected = $testInputFile + ":1:This is a text string, and another string" - Select-String $testInputFile -Pattern "string" | Should -Be $expected + Select-String $testInputFile -Pattern "string" | Should -BeExactly $expected } It "Should return all strings where 'second' is found in testfile1 if there is only one lines that has a match" { $expected = $testInputFile + ":2:This is the second line" - Select-String $testInputFile -Pattern "second"| Should -Be $expected + Select-String $testInputFile -Pattern "second"| Should -BeExactly $expected } It "Should return all strings where 'in' is found in testfile1 pattern switch is not required" { @@ -121,10 +130,10 @@ Describe "Select-String" -Tags "CI" { $expected3 = "This is the third line" $expected4 = "This is the fourth line" - (Select-String in $testInputFile)[0].Line | Should -Be $expected1 - (Select-String in $testInputFile)[1].Line | Should -Be $expected2 - (Select-String in $testInputFile)[2].Line | Should -Be $expected3 - (Select-String in $testInputFile)[3].Line | Should -Be $expected4 + (Select-String in $testInputFile)[0].Line | Should -BeExactly $expected1 + (Select-String in $testInputFile)[1].Line | Should -BeExactly $expected2 + (Select-String in $testInputFile)[2].Line | Should -BeExactly $expected3 + (Select-String in $testInputFile)[3].Line | Should -BeExactly $expected4 (Select-String in $testInputFile)[4].Line | Should -BeNullOrEmpty } @@ -175,6 +184,24 @@ Describe "Select-String" -Tags "CI" { Select-String 'matc*' $testInputFile -ca | Should -Match $expected } + + It "Should return all strings where 'in' is found in testfile1, when -Raw is used." { + $expected1 = "This is a text string, and another string" + $expected2 = "This is the second line" + $expected3 = "This is the third line" + $expected4 = "This is the fourth line" + + (Select-String in $testInputFile -Raw)[0] | Should -BeExactly $expected1 + (Select-String in $testInputFile -Raw)[1] | Should -BeExactly $expected2 + (Select-String in $testInputFile -Raw)[2] | Should -BeExactly $expected3 + (Select-String in $testInputFile -Raw)[3] | Should -BeExactly $expected4 + (Select-String in $testInputFile -Raw)[4] | Should -BeNullOrEmpty + } + + It "Should ignore -Context parameter when -Raw is used." { + $expected = "This is the second line" + Select-String second $testInputFile -Raw -Context 2,2 | Should -BeExactly $expected + } } Push-Location $currentDirectory }