2

I am fairly new to Powershell and have run into a bit of a pickle. I have created a list of all files in multiple directories with their extensions with the aim of doing a string pattern match to retrieve the files (with their extension and matching string pattern) that I am interested in.

$directory = 'c:\test', 'd:\test\test'
$files = Get-ChildItem -file $directory -recurse | Select-Object Fullname, extension
$patterns = Get-Content -Path 'C:\Patterns.txt' 
$files | Select-String -pattern $patterns | Select-Object Pattern, Line

When I run a Select-String to match, my output has @{Fullname = C:\test\test0001.txt; Extension = .txt} etc.

Is there a way I can get the output to look more like the below and export it as a CSV file?

Pattern Fullname Extension
00001 C:\test\test00001.txt .txt
00002 D:\test\test\00002.docx .docx
2
  • 1
    Try Format-Table and ConvertTo-CSV. Commented Apr 24, 2022 at 8:57
  • 1
    @HsuPu, use of Format-* cmdlets in order to output data is the wrong approach - they're for for-display formatting only. Commented Apr 24, 2022 at 16:28

2 Answers 2

3

Try the following, which combines use of the common -PipelineVariable parameter with calculated properties:

Get-ChildItem -PipelineVariable file -File $directory -Recurse |
  Select-String -Pattern (Get-Content -Path 'C:\Patterns.txt') | 
    Select-Object Pattern,
                  @{ Name='Fullname';  Expression = { $file.FullName } },                  
                  @{ Name='Extension'; Expression = { $file.Extension } }

Pipe the above to Export-Csv as needed.

  • -PipelineVariable file stores each file-info object emitted by Get-ChildItem in variable $file, allowing it to be referenced in a script block in a later pipeline segment.

  • The expressions defining the values of the calculated properties in the Select-Object call reference the file-info object stored in pipeline variable $file in order to create output objects that combine the Pattern property from the Select-String output objects with property values from the input file at hand.


As for what you tried:

  • Your primary problem was your attempt to pipe custom objects ([pscustomobject] instances) to Select-String, which causes the latter to search through the stringified representation of the custom objects (which yields a string like '@{Fullname = ...; extension = ...}', as shown in your question[1]) - rather than treating them as representing files whose content you want to search.

    • To achieve that, you must use file-info objects ([System.IO.FileInfo] instances) as input, as directly emitted by Get-ChildItem.

    • For the sake of completeness:

      • If your non-file-info input objects have a property reflecting the file path of interest, such as in your case, you can make this work, namely via a delay-bind script block passed to Select-String's -LiteralPath parameter:
        $files | Select-String -LiteralPath { $_.Fullname } ...
      • Similarly, if your custom objects had a LiteralPath (or PSPath or, in PowerShell (Core) 7+, LP) property containing the file path of interest, piping it Select-String should work as-is, because that property's value should automatically bind to the -LiteralPath parameter (this mechanism, in fact, is how System.IO.FileInfo instances bind to -LiteralPath - see this answer for an explanation); however, as of PowerShell 7.2.2, this appears to be broken with Select-String, specifically: see GitHub issue #17188.
  • The secondary problem is that your Select-Object call operates only on the output objects emitted by Select-String, which are [Microsoft.PowerShell.Commands.MatchInfo] instances, which do not have .Fullname and .Extension properties.


[1] Note that this string representation is the result of simple .ToString() stringification; it is not the rich representation you would see in the console, courtesy of PowerShell's for-display output formatting system. This surprising behavior is discussed in detail in this answer.

Sign up to request clarification or add additional context in comments.

Comments

0

This feels wrong somehow, but it works. I have a feeling there's a better way to convert the Line property to a hashtable object, but I tried both Invoke-Expression and ConvertFrom-StringData with no success.

$directory = 'c:\test', 'd:\test\test'   
$files = Get-ChildItem -File $directory | Select-Object FullName, Extension
$exportObj = $files | Select-String -Pattern $patterns | Select-Object Pattern, Line | %{
    $line = $_.Line -replace "[@,{,}]","" -split ";"
    [PSCustomObject]@{
            Pattern=$_.Pattern
            FullName=($line[0] -split "=")[1]
            Extension=($line[1] -split "=")[1]
        }
    }
$exportObj | Export-Csv -Path C:\temp\test.csv -NoTypeInformation

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.