4

I have this code snippet where I try tro replace some strings in all files of a directory. I thought I could nest the foreach in the ForEach-Object, but this does not seem to work.

The error I get is:

InvalidArgument: (:) [ForEach-Object], ParameterBindingException


$files = Get-ChildItem $testdir\reference *.* -recurse
$replacementMap = @{"Fruit::Apple" = "NewApple";"Fruit::Banana" = "NewBanana"}

foreach ($file in $files)
    {
    If (Get-Content $($file.FullName) | Select-String -Pattern "Fruit::")
        {
        $content = Get-Content $($file.FullName) | ForEach-Object
               { 
               $line = $_
               foreach ($entry in $replacementMap.GetEnumerator())
                   {
                   $line -replace $($entry.Name),$($entry.Value)
                   }
                }
        $content = $content -join "`r`n"
        $content | Set-Content $($file.FullName)
     }

This code worked without the

foreach ($entry in $replacementMap.GetEnumerator())
    {
    $line -replace $($entry.Name),$($entry.Value)
    }

part. Anyone has a clue what I'm doing wrong? Thanks in advance

1
  • 1
    The ForEach-Object cmdlet and a foreach(){} loop are two different kinds of statements. However, your issue seem to be the newline between ForEach-Object and { - move the { up on the same line Commented Sep 12, 2017 at 8:59

2 Answers 2

2

You missed a curly brace closure and formatting issue on the foreach-object. You need to take care of foreach and foreach-object in a different way:

Replace your existing foreach part with this:

foreach ($file in $files)
{
    If(Get-Content $($file.FullName) | Select-String -Pattern "Fruit::")
    {
        $content = Get-Content $($file.FullName) | %{ 
                   $line = $_
                   foreach ($entry in $replacementMap.GetEnumerator())
                   {
                    $line -replace $($entry.Name),$($entry.Value)
                   }
        }
        $content = $content -join "`r`n"
        $content | Set-Content $($file.FullName)
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

Instead of processing the files line-wise, just do the replacement operation on the entire file content at once. If the content has changed, overwrite the file.

$replacementMap = @{
    "Fruit::Apple" = "NewApple"
    "Fruit::Banana" = "NewBanana"
}

Get-ChildItem $testdir\reference -File -Recurse | foreach {
    $content = Get-Content $_
    $dirty = $false
    foreach ($key in $replacementMap.Keys) {
        $content = $content -replace $key,$replacementMap.$key
        $dirty = $true
    }
    if ($dirty) { $content | Set-Content $_ }
}

6 Comments

How will this affect the newlines in the file? A known issue with my approach now is that it might change the line endings
It won't affect newlines at all, unless you put newlines into your search patterns.
Thanks a lot, I'll use this approach from now on!
It should also be quite a bit faster than the line-wise approach.
There seems to be an issue, the replacement does not work. When I have two entries in my replacement map, the replace seems to happen only once (I added a popup in the foreach loop, and it only fires once). When showing the value of $_.Name in the popup, the string is empty. Any idea how this is possible?
|

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.