Unable to Pipe Multiple Files

I am trying to go through all the files under a folder (‘C:\Users\thchen\Desktop\ReplaceTests’) to look for a particular string pattern (‘FindString’) and replace it with another (‘ReplaceString’) using the following code:

Get-ChildItem 'C:\Users\thchen\Desktop\ReplaceTests' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_
}

Even if I copied the folder and placed it under a different location such as “C:\ReplaceTests” and reboot my PC

Get-ChildItem "C:\ReplaceTests" -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_
}

It would always gives me the following error:

Get-Content : Cannot find path ‘C:\windows\system32\SecondLayer’ because it does not exist.
At line:1 char:79

  • … rs\thchen\Desktop\ReplaceTests -Recurse | ForEach {(Get-Content $_ )}
  • CategoryInfo : ObjectNotFound: (C:\windows\system32\SecondLayer:String) [Get-Content], ItemNotFoundException
  • FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand

I don’t understand. I did not say anything about the folder "C:\windows\system32" anywhere in my code. Why is it going to that folder to look for a file?

And If I specify just one file using the codes below, it goes to the file, finds the pattern and replaces it without problem.

Get-Item -path C:\Users\thchen\Desktop\ReplaceTests\ReplaceTest.txt | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_
}

Not sure what I am doing wrong. Anybody sees anything obvious that I am missing? Thanks for any help.

It gets confusing using $_ and depending on where you are in the pipeline it could not be what you expect, so first thing is use $file in your first loop to ensure the correct context is used. Next, you were referencing $_ for Get-Content, which is an object, not a path, so updated to .FullName. Last, you are recursing through a directory structure, so you need to specify -File to only process files:

foreach ($file in Get-ChildItem -Path ('{0}\ReplaceTests' -f [environment]::GetFolderPath("Desktop")) -File -Recurse) {
   (Get-Content $file.FullName | 
   foreach  { $_ -replace 'FindString', 'ReplaceString' }) |
   Set-Content $file.FullName
}

Thanks Rob, I really appreciate your help.

I did not fully understand the first line of your code. So I did this instead:

foreach ($file in Get-Childitem -Path 'C:\Users\thchen\Desktop\ReplaceTests' -File -Recurse) 
{(Get-Content $file.FullName | foreach { $_ -replace 'FindString', 'ReplaceString'}) |
Set-Content $file.FullName
}

Now everything works, thanks to your help.

I understand “[environment]::GetFolderPath(“Desktop”)” but what does ‘{0}\ReplaceTests’ mean?

It is a string format:

Hey @thchen, just some clarification on your original code. The only problem you had is that your get-content and set-content commands did not accept the input object because it expects a string.

Get-ChildItem 'C:\Users\thchen\Desktop\ReplaceTests' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_
}

Get-ChildItem outputs System.IO.FileInfo objects

This is being input into Get-Content and Set-Content in Position 0, which is the -Path Parameter. The Path parameter; however, requires a string or an array of strings, not an Object.

    -Path 
        
        Required?                    true
        Position?                    0
        Accept pipeline input?       true (ByPropertyName)
        Parameter set name           Path
        Aliases                      None
        Dynamic?                     false

Really all the Path parameter needs is the path to the file, which is part of your object, so you just need to tell it to use that property from your object.

Get-ChildItem 'C:\Users\thchen\Desktop\ReplaceTests' -Recurse | ForEach {
     (Get-Content $_.FullName | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_.FullName
}

This gets you 99% of the way there. The only other problem is if you have sub directories in your path, you will get an access denied when you try to “Get-Content” on the directory path. This is because a directory is not a file and you can’t get it’s content. To resolve this you should tell Get-ChildItems to only return file objects, not directory objects, using the -File parameter.

Get-ChildItem 'C:\Users\thchen\Desktop\ReplaceTests' -Recurse -File | ForEach {
     (Get-Content $_.FullName | ForEach  { $_ -replace 'FindString', 'ReplaceString' }) |
     Set-Content $_.FullName
}

Note: These are all the same things that Rob did with his provided example, just keeping $_ instead of switching to another variable. I just wanted to show it was just the .FileName and -File adjustments he made that was the difference. Switching to another variable was not necessary, but it does make the code easier to read.

Thank you Curtis. It is now making a lot more sense. I appreciate your help.