Unable to Pipe Multiple Files

Tagged: 

This topic contains 5 replies, has 3 voices, and was last updated by Profile photo of Ted Chen Ted Chen 2 weeks, 1 day ago.

  • Author
    Posts
  • #70240
    Profile photo of Ted Chen
    Ted Chen
    Participant

    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.

  • #70246
    Profile photo of Rob Simmers
    Rob Simmers
    Participant

    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
    }
    
    • #70252
      Profile photo of Ted Chen
      Ted Chen
      Participant

      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?

  • #70255
    Profile photo of Rob Simmers
    Rob Simmers
    Participant
  • #70265
    Profile photo of Curtis Smith
    Curtis Smith
    Participant

    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.***

    • #70292
      Profile photo of Ted Chen
      Ted Chen
      Participant

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

You must be logged in to reply to this topic.