powershellmultiple-conditionsselect-string

select-string with multiple conditions with powershell


I'm looking for a way to find 2 different lines in a file and only if those 2 line exist I need to perform a task. So far this is my code

$folderPath = c:\test
$files = Get-ChildItem $Folderpath -Filter *.txt
$find = 'stringA'
$find2 = 'StringB'
$replace = 'something to replace with string b'
if ($files.Length -gt 0  ) {
    $files |
    select -ExpandProperty fullname |
    foreach {
        If(Select-String -Path $_ -pattern $find , $find2 -quiet )
        {
            (Get-Content $_) |
            ForEach-Object {$_ -replace $find2, $replace } |
            Set-Content $_
            write-host "File Changed : " $_    
        } 
    }
}
else {
    write-host "no files changed"
}

Currently if I run it once it will change the files but if I run it again it will also notify me that it changed the same files instead of the output "no files changed"

Is there a simpler way to make it happen? Thanks


Solution

  • The Select-String cmdlet selects lines matching any of the patterns supplied to it. This means that the following file contains a match:

    PS> Get-Content file.txt
    This file contains only stringA
    
    PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt
    file.txt:1:This file contains only stringA
    

    Passing the -Quiet flag to Select-String will produce a boolean result instead of a list of matches. The result is $True even though only one of the patterns is present.

    PS> Get-Content file.txt
    This file contains only stringA
    
    PS> Select-String -Pattern 'stringA', 'stringB' -Path file.txt -Quiet
    True
    

    In your case, Select-String chooses all the files containing either 'stringA' or 'stringB', then replaces all instances of 'stringB' in those files. (Note that replacements are also performed in files you did not want to alter)

    Even after the replacements, files containing only 'stringA' still exist: these files are caught and reported by your script the second time you run it.

    One solution is to have two separate conditions joined by the -and operator:

    If (
        (Select-String -Path $_ -Pattern 'stringA' -Quiet) -and
        (Select-String -Path $_ -Pattern 'stringB' -Quiet)
    )
    

    After this the script should work as intended, except that it won't report "no files changed" correctly.

    If you fix your indentation you'll realise that the final else clause actually checks if there are no .txt files in the folder:

    $files = Get-ChildItem $Folderpath -Filter *.txt
    ...
    if ($files.length -gt 0) {
        ...
    } else {
        # will only display when there are no text files in the folder!
        Write-Host "no files changed"
    }
    

    The way to resolve this would be to have a separate counter variable that increments every time you find a match. Then at the end, check if this counter is 0 and call Write-Host accordingly.

    $counter = 0
    ...
    foreach {
        if ((Select-String ...) ...) {
            ...
            $counter += 1
        }
    }
    if ($counter -eq 0) {
        Write-Host "no files changed"
    }