1

Currently on version 4.0. I am easily able to find files containing one string. Trying to do something slightly more complex with two strings on different lines has not worked out. The problem is apparently due to the way lines are evaluated individually and results are piped as objects between commands making the obvious solutions do not work.

# Works, so simple
Get-ChildItem | Select-String -Pattern "Test1" -List | Select Path

# Fail
Get-ChildItem | Select-String -Pattern "Test1.+Test2" -List | Select Path

# Fail
Get-ChildItem | Select-String -Pattern "Test1" -List | Select-String -Pattern "Test2" -List | Select Path

Conceptually this is so simple, but I've been spinning my wheels on finding a solution. Might be easier in newer version but not able to update on server involved. I could write a script to get to the solution in Python, but at this point I want to make PowerShell do it out of spite.

Knowing what is returned from each command, how to get underlying object data, how to then pipe that data to subsequent commands, etc has repeatedly been an issue for me with PowerShell :(

Dan Roberts
  • 4,664
  • 3
  • 34
  • 43
  • 2
    I don't have a version 4 to test but I think it should be the same. First: you don't need `Get-ChildItem` in front of the `Select-String`. You can provide a path directly. Second: the parameter `-Pattern` takes an array of patterns. If I got it right it should work for your requirement. ;-) – Olaf Sep 14 '21 at 20:37

2 Answers2

1

The following commands use Where-Object to test for our conditions.


Get-ChildItem | Where-Object { 
    ($_ | Select-String -Pattern 'test1' -Quiet) -and
    ($_ | Select-String -Pattern 'test2' -Quiet) 
}

This line checks if the file piped from Get-ChildItem (represented by $_) matches both 'test1' AND 'test2'. Using -Quiet parameter will make it so that Select-String returns true if it finds a match or false if it doesn't.


Get-ChildItem -File | Where-Object {
    ($_ | Select-String -Pattern 'test1', 'test2' | 
        Group-Object -Property 'Pattern' ).Count -eq 2
    }

With this line I give Select-String 2 patterns to look for. This will not ensure that both patterns are matched, but instead will return results for each that are matched. On the [Microsoft.PowerShell.Commands.MatchInfo] objects returned by Select-String is a property called Pattern. If I use Group-Object to group all the MatchInfo results I got back by the property 'Pattern' I can then check to see if I end up with 2 group objects, one for each of my patterns. If true, the object is returned by Where-Object


Just as with Python or any other languages there are commands/methods and parameters you need to discover and learn that will make your life easier and usually there is more than one way to do something.

Make use of the built-in help PowerShell provides to get to know the command and the objects that these commands produce. Get-Command, Get-Help, Get-Member are arguably 3 of the most useful cmdlets, especially to people new to or struggling with PowerShell

Daniel
  • 4,792
  • 2
  • 7
  • 20
  • 1
    Nicely done, and good tips on discovery. It is possible to make do with a single `Select-String` call overall, however (though, depending on file size and intra-file location of the matches, even a two-calls-per-file solution could be faster, due to the short-circuiting that `-Quiet` provides). Can I suggest spreading your commands across multiple lines, so that getting to the interesting parts doesn't require horizontal scrolling? – mklement0 Sep 15 '21 at 15:55
  • 1
    Thank you @mklement0. I did also consider this double-call to the file and came to the same conclusion as you, that by using '-Quiet` the call will return as soon as the first match is found possibly cutting down on the time taken to complete. I guess the least performant scenario would be that a match is found for the first term somewhere near the bottom of the file and then no match is found for the 2nd term resulting in almost full processing of the file twice – Daniel Sep 15 '21 at 16:50
0

As Olaf points out, Select-String's -Pattern parameter accepts an array of patterns.

Such a command returns lines that match any of the given patterns.

If you want to determine if a given file matches all of the given patterns (at least once), more work is needed:

$patterns = 'Test1', 'Test2'
Get-ChildItem -File |
  Select-String -Pattern $patterns | 
    Group-Object -Property Path | 
      Where-Object { 
        ($_.Group.Pattern | Select-Object -Unique).Count -eq $patterns.Count
      } | ForEach-Object Name

The above outputs the full paths of those files that match all patterns.

  • The Microsoft.PowerShell.Commands.MatchInfo instances that Select-String outputs ...

  • ... are grouped by their .Path property (the input file's full path) using the Group-Object cmdlet.

  • $_.Group.Pattern extracts the array of patterns that triggered each match using member-access enumeration, based on the Microsoft.PowerShell.Commands.GroupInfo instances output by Group-Object.

  • Select-Object -Unique shrinks the array of patterns that actually matched to the unique (distinct) patterns it contains.

  • The resulting array matching the count of input patterns (-eq $patterns.Count) implies that all input patterns were found (at least once), and using that in a script block passed to Where-Object means that only matching groups are output.

  • Finally, ForEach-Object Name outputs each matching group's .Name property, which contains the (stringified) value of the grouping property passed to Group-Object -Property, i.e. the full path of each input file in the case at hand.

mklement0
  • 382,024
  • 64
  • 607
  • 775