315

Using PowerShell, I want to replace all exact occurrences of [MYID] in a given file with MyValue. What is the easiest way to do so?

Cœur
  • 32,421
  • 21
  • 173
  • 232
amateur
  • 40,217
  • 59
  • 181
  • 303
  • For a more effective solution in terms of memory consumption than offered in the answers to this question, see [Find and Replace in a Large File](https://stackoverflow.com/q/2783837/850848#59972564). – Martin Prikryl Mar 25 '20 at 09:55

13 Answers13

472

Use (V3 version):

(Get-Content c:\temp\test.txt).replace('[MYID]', 'MyValue') | Set-Content c:\temp\test.txt

Or for V2:

(Get-Content c:\temp\test.txt) -replace '\[MYID\]', 'MyValue' | Set-Content c:\temp\test.txt
SFlagg
  • 369
  • 5
  • 10
Loïc MICHEL
  • 22,418
  • 8
  • 68
  • 94
  • 3
    Thanks - I get an error "replace : Method invocation failed because [System.Object[]] doesn't contain a method named 'replace'." though? – amateur Jun 17 '13 at 09:42
  • \ as escape works in ps v4 I just discovered. Thanks. – ErikE Nov 21 '13 at 15:39
  • 4
    @rob pipe the result to set-content or out-file if you want to save the modification – Loïc MICHEL Dec 06 '13 at 20:38
  • 3
    I got the error _**"Method invocation failed because [System.Object[]] doesn't contain a method named 'replace'."**_ because I was trying to run the V3 version on a machine that only had V2. – SFlagg Nov 19 '15 at 21:17
  • 7
    Warning: Running these scripts against large files (a couple hundred megabytes or so) can eat up a fair amount of memory. Just be sure you have enough head room if you a running on a production server :D – neoscribe May 09 '16 at 23:36
  • on the missing replace() method error ... you may need to cast the content to string. [string](Get-Content c:\temp\test.txt) – Conrad B Jun 29 '17 at 10:10
  • Better to use '-replace', this way you can use regex with it. – Thibault Loison Jan 15 '19 at 11:00
89
(Get-Content file.txt) | 
Foreach-Object {$_ -replace '\[MYID\]','MyValue'}  | 
Out-File file.txt

Note the parentheses around (Get-Content file.txt) is required:

Without the parenthesis the content is read, one line at a time, and flows down the pipeline until it reaches out-file or set-content, which tries to write to the same file, but it's already open by get-content and you get an error. The parenthesis causes the operation of content reading to be performed once (open, read and close). Only then when all lines have been read, they are piped one at a time and when they reach the last command in the pipeline they can be written to the file. It's the same as $content=content; $content | where ...

David Clarke
  • 12,002
  • 8
  • 80
  • 105
Shay Levy
  • 107,077
  • 26
  • 168
  • 192
  • 5
    I'd change my upvote to a downvote if I could. In PowerShell 3 this silently deletes all the content from the file! Using `Set-Content` instead of `Out-File` yuou get a warning like *"The process cannot access the file '123.csv' because it is being used by another process."*. – Iain Samuel McLean Elder Sep 17 '13 at 14:36
  • 10
    It should't happen when the get-content is in parenthesis. They cause the operation to open,read and close the file so the error you get shouldn't happen. Can you test it again with a sample text file? – Shay Levy Sep 17 '13 at 15:18
  • 2
    With `Get-Content` in parenthesis it works. Can you explain in your answer why the parenthesis is necessary? I would still replace `Out-File` with `Set-Content` because it's safer; it protects you from wiping out the target file if you forget the parenthesis. – Iain Samuel McLean Elder Sep 17 '13 at 16:38
  • 7
    Problem with ***file encoding UTF-8***. When saves the file, changes the encoding. Not the same. http://stackoverflow.com/questions/5596982/using-powershell-to-write-a-file-in-utf-8-without-the-bom. I think **set-content** considers Encoding file (like UTF-8). but not **Out-File** – Kiquenet Jan 26 '15 at 15:34
  • 2
    This solution is unnecesarily misleading and caused problems when I used it. I was updating a config file which was immediatelly used by installation process. The config file was still held by process and the installation failed. Using `Set-Content` instead of `Out-File` is much better and safer solution. Sorry have to downvote. – Martin Basista Aug 11 '15 at 13:49
  • Warning: This script uses about twice as much memory as the more popular answer and still runs more than twice as slow when you are working with large files (200MB or so). – neoscribe May 09 '16 at 23:38
  • Careful! The `-replace` operator uses regex, so you'll get some unexpected results if you're trying to replace any special characters (like "*") – Gabriel Luci Jun 08 '18 at 18:35
  • this should be the accepted answer because op want probably save the file with the replaced text and not only get the replaced text – anion Sep 27 '18 at 12:29
86

I prefer using the File-class of .NET and its static methods as seen in the following example.

$content = [System.IO.File]::ReadAllText("c:\bla.txt").Replace("[MYID]","MyValue")
[System.IO.File]::WriteAllText("c:\bla.txt", $content)

This has the advantage of working with a single String instead of a String-array as with Get-Content. The methods also take care of the encoding of the file (UTF-8 BOM, etc.) without you having to take care most of the time.

Also the methods don't mess up the line endings (Unix line endings that might be used) in contrast to an algorithm using Get-Content and piping through to Set-Content.

So for me: Fewer things that could break over the years.

A little-known thing when using .NET classes is that when you have typed in "[System.IO.File]::" in the PowerShell window you can press the Tab key to step through the methods there.

Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
rominator007
  • 1,162
  • 9
  • 14
  • You can also see the methods with the command `[System.IO.File] | gm` – fbehrens Feb 10 '16 at 11:57
  • Why does this method assume a relative path from `C:\Windows\System32\WindowsPowerShell\v1.0`? – Adrian Mar 01 '16 at 16:53
  • Is that so? That probably has something to do with the way a .NET AppDomain is started within the PowerShell. Might be, that the current path does not get updated when using cd. But this is no more than an educated guess. I did not test this or look it up. – rominator007 Mar 03 '16 at 06:37
  • 2
    This is also a lot easier than writing different code for different versions of Powershell. – Willem van Ketwich Nov 18 '16 at 02:28
  • This method also appears to be the fastest. Couple that with the noted benefits and the question should be, "why would you want to use anything else?" – DBADon Jan 14 '20 at 15:39
  • Yeah this is the one. Go .NET. +1! – Gaspa79 Feb 28 '20 at 08:55
23

The one above only runs for "One File" only, but you can also run this for multiple files within your folder:

Get-ChildItem 'C:yourfile*.xml' -Recurse | ForEach {
     (Get-Content $_ | ForEach  { $_ -replace '[MYID]', 'MyValue' }) |
     Set-Content $_
}
Shaun Luttin
  • 107,550
  • 65
  • 332
  • 414
John V Hobbs Jr
  • 287
  • 2
  • 5
  • notice that I used .xml but you can replace with .txt – John V Hobbs Jr Jul 20 '16 at 20:05
  • Nice. Alternatively to using the inner `foreach` you can do this `Get-ChildItem 'C:\folder\file*.xml' -Recurse | ForEach { (Get-Content $_).Replace('[MYID]', 'MyValue') | Set-Content $_ }` – KCD Dec 05 '16 at 21:26
  • 1
    Actually, you do need that inner `foreach`, because Get-Content does something you might not expect... It returns an array of strings, where each string is a line in the file. If you're looping through a directory (and sub-directories) that are in a different location than your running script, you'll want something like this: `Get-ChildItem $Directory -File -Recurse | ForEach { (Get-Content $_.FullName) | ForEach { $_ -replace '[MYID]', 'MyValue' } | Set-Content $_.FullName }` where `$Directory` is the directory containing the files you want to modify. – birdamongmen Dec 16 '16 at 00:51
  • 1
    What answer is "the one above"? – Peter Mortensen Mar 28 '17 at 16:25
10

You could try something like this:

$path = "C:\testFile.txt"
$word = "searchword"
$replacement = "ReplacementText"
$text = get-content $path 
$newText = $text -replace $word,$replacement
$newText > $path
Richard
  • 6,160
  • 5
  • 39
  • 57
7

This is what I use, but it is slow on large text files.

get-content $pathToFile | % { $_ -replace $stringToReplace, $replaceWith } | set-content $pathToFile

If you are going to be replacing strings in large text files and speed is a concern, look into using System.IO.StreamReader and System.IO.StreamWriter.

try
{
   $reader = [System.IO.StreamReader] $pathToFile
   $data = $reader.ReadToEnd()
   $reader.close()
}
finally
{
   if ($reader -ne $null)
   {
       $reader.dispose()
   }
}

$data = $data -replace $stringToReplace, $replaceWith

try
{
   $writer = [System.IO.StreamWriter] $pathToFile
   $writer.write($data)
   $writer.close()
}
finally
{
   if ($writer -ne $null)
   {
       $writer.dispose()
   }
}

(The code above has not been tested.)

There is probably a more elegant way to use StreamReader and StreamWriter for replacing text in a document, but that should give you a good starting point.

Nathan Rice
  • 3,022
  • 1
  • 18
  • 30
  • I think set-content considers Encoding file (like UTF-8). but not Out-File http://stackoverflow.com/questions/5596982/using-powershell-to-write-a-file-in-utf-8-without-the-bom – Kiquenet Jan 26 '15 at 15:28
4

I found a little known but amazingly cool way to do it from Payette's Windows Powershell in Action. You can reference files like variables, similar to $env:path, but you need to add the curly braces.

${c:file.txt} = ${c:file.txt} -replace 'oldvalue','newvalue'
js2010
  • 13,551
  • 2
  • 28
  • 40
  • What if the filename is in a variable such as `$myFile`? – ΩmegaMan Jun 24 '19 at 22:44
  • @ΩmegaMan hmm only this so far `$a = 'file.txt'; invoke-expression "\`${c:$a} = \`${c:$a} -replace 'oldvalue','newvalue'"` – js2010 Jun 24 '19 at 23:39
  • Nice trick! Shame it needs either the absolute file path or at least the disk letter... Or is there a way to say that the file is located in the current directory? – Ed'ka Apr 15 '21 at 10:46
2

If You Need to Replace Strings in Multiple Files:

It should be noted that the different methods posted here can be wildly different with regard to the time it takes to complete. For me, I regularly have large numbers of small files. To test what is most performant, I extracted 5.52 GB (5,933,604,999 bytes) of XML in 40,693 separate files and ran through three of the answers I found here:

## 5.52 GB (5,933,604,999 bytes) of XML files (40,693 files) 

#### Test 1 - Plain Replace
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 103.725113128333
#>

#### Test 2 - Replace with -Raw
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
(Get-Content $xml -Raw).replace("'", " ") | Set-Content $xml
}
$end   = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 10.1600227983333
#>

#### Test 3 - .NET, System.IO
$start = get-date
$xmls = (Get-ChildItem -Path "I:\TestseT\All_XML" -Recurse -Filter *.xml).FullName
foreach ($xml in $xmls)
{
$txt = [System.IO.File]::ReadAllText("$xml").Replace("'"," ") 
[System.IO.File]::WriteAllText("$xml", $txt)
}
$end   = get-date
NEW-TIMESPAN –Start $Start –End $End
<#
TotalMinutes: 5.83619516833333
#>
DBADon
  • 327
  • 3
  • 8
1

Credit to @rominator007

I wrapped it into a function (because you may want to use it again)

function Replace-AllStringsInFile($SearchString,$ReplaceString,$FullPathToFile)
{
    $content = [System.IO.File]::ReadAllText("$FullPathToFile").Replace("$SearchString","$ReplaceString")
    [System.IO.File]::WriteAllText("$FullPathToFile", $content)
}

NOTE: This is NOT case sensitive!!!!!

See this post: String.Replace ignoring case

Kolob Canyon
  • 4,727
  • 1
  • 35
  • 59
0

This worked for me using the current working directory in PowerShell. You need to use the FullName property, or it won't work in PowerShell version 5. I needed to change the target .NET framework version in ALL my CSPROJ files.

gci -Recurse -Filter *.csproj |
% { (get-content "$($_.FullName)")
.Replace('<TargetFramework>net47</TargetFramework>', '<TargetFramework>net462</TargetFramework>') |
 Set-Content "$($_.FullName)"}
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123
SliverNinja - MSFT
  • 29,007
  • 10
  • 98
  • 161
0

A bit old and different, as I needed to change a certain line in all instances of a particular file name.

Also, Set-Content was not returning consistent results, so I had to resort to Out-File.

Code below:


$FileName =''
$OldLine = ''
$NewLine = ''
$Drives = Get-PSDrive -PSProvider FileSystem
foreach ($Drive in $Drives) {
    Push-Location $Drive.Root
        Get-ChildItem -Filter "$FileName" -Recurse | ForEach { 
            (Get-Content $_.FullName).Replace($OldLine, $NewLine) | Out-File $_.FullName
        }
    Pop-Location
}

This is what worked best for me on this PowerShell version:

Major.Minor.Build.Revision

5.1.16299.98

Community
  • 1
  • 1
0

Here's a fairly simple one that supports multiline regular expressions, multiple files (using the pipeline), specifying output encoding, etc. Not recommended for very large files due to the ReadAllText method.

# Update-FileText.ps1

#requires -version 2

<#
.SYNOPSIS
Updates text in files using a regular expression.

.DESCRIPTION
Updates text in files using a regular expression.

.PARAMETER Pattern
Specifies the regular expression pattern.

.PARAMETER Replacement
Specifies the regular expression replacement pattern.

.PARAMETER Path
Specifies the path to one or more files. Wildcards are not supported. Each file is read entirely into memory to support multi-line searching and replacing, so performance may be slow for large files.

.PARAMETER CaseSensitive
Specifies case-sensitive matching. The default is to ignore case.

.PARAMETER SimpleMatch
Specifies a simple match rather than a regular expression match (i.e., the Pattern parameter specifies a simple string rather than a regular expression).

.PARAMETER Multiline
Changes the meaning of ^ and $ so they match at the beginning and end, respectively, of any line, and not just the beginning and end of the entire file. The default is that ^ and $, respectively, match the beginning and end of the entire file.

.PARAMETER UnixText
Causes $ to match only linefeed (\n) characters. By default, $ matches carriage return+linefeed (\r\n). (Windows-based text files usually use \r\n as line terminators, while Unix-based text files usually use only \n.)

.PARAMETER Overwrite
Overwrites a file by creating a temporary file containing all replacements and then replacing the original file with the temporary file. The default is to output but not overwrite.

.PARAMETER Force
Allows overwriting of read-only files. Note that this parameter cannot override security restrictions.

.PARAMETER Encoding
Specifies the encoding for the file when -Overwrite is used. Possible values for this parameter are ASCII, BigEndianUnicode, Unicode, UTF32, UTF7, and UTF8. The default value is ASCII.

.INPUTS
System.IO.FileInfo.

.OUTPUTS
System.String (single-line file) or System.String[] (file with more than one line) without the -Overwrite parameter, or nothing with the -Overwrite parameter.

.LINK
about_Regular_Expressions

.EXAMPLE
C:\> Update-FileText.ps1 '(Ferb) and (Phineas)' '$2 and $1' Story.txt

This command replaces the text 'Ferb and Phineas' with the text 'Phineas and Ferb' in the file Story.txt and outputs the content. Note that the pattern and replacement strings are enclosed in single quotes to prevent variable expansion.

.EXAMPLE
C:\> Update-FileText.ps1 'Perry' 'Agent P' Story2.txt -Overwrite

This command replaces the text 'Perry' with the text 'Agent P' in the file Story2.txt.
#>

[CmdletBinding(SupportsShouldProcess = $true,ConfirmImpact = "High")]
param(
  [Parameter(Mandatory = $true,Position = 0,ValueFromPipeline = $true)]
  [String[]] $Path,

  [Parameter(Mandatory = $true,Position = 1)]
  [String] $Pattern,

  [Parameter(Mandatory = $true,Position = 2)]
  [AllowEmptyString()]
  [String] $Replacement,

  [Switch] $CaseSensitive,

  [Switch] $SimpleMatch,

  [Switch] $Multiline,

  [Switch] $UnixText,

  [Switch] $Overwrite,

  [Switch] $Force,

  [ValidateSet("ASCII","BigEndianUnicode","Unicode","UTF32","UTF7","UTF8")]
  [String] $Encoding = "ASCII"
)

begin {
  function Get-TempName {
    param(
      $path
    )
    do {
      $tempName = Join-Path $path ([IO.Path]::GetRandomFilename())
    }
    while ( Test-Path $tempName )
    $tempName
  }

  if ( $SimpleMatch ) {
      $Pattern = [Regex]::Escape($Pattern)
  }
  else {
    if ( -not $UnixText ) {
      $Pattern = $Pattern -replace '(?<!\\)\$','\r$'
    }
  }

  function New-Regex {
    $regexOpts = [Text.RegularExpressions.RegexOptions]::None
    if ( -not $CaseSensitive ) {
      $regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::IgnoreCase
    }
    if ( $Multiline ) {
      $regexOpts = $regexOpts -bor [Text.RegularExpressions.RegexOptions]::Multiline
    }
    New-Object Text.RegularExpressions.Regex $Pattern,$regexOpts
  }

  $Regex = New-Regex

  function Update-FileText {
    param(
      $path
    )
    $pathInfo = Resolve-Path -LiteralPath $path
    if ( $pathInfo ) {
      if ( (Get-Item $pathInfo).GetType().FullName -eq "System.IO.FileInfo" ) {
        $fullName = $pathInfo.Path
        Write-Verbose "Reading '$fullName'"
        $text = [IO.File]::ReadAllText($fullName)
        Write-Verbose "Finished reading '$fullName'"
        if ( -not $Overwrite ) {
          $regex.Replace($text,$Replacement)
        }
        else {
          $tempName = Get-TempName (Split-Path $fullName -Parent)
          Set-Content $tempName $null -Confirm:$false
          if ( $? ) {
            Write-Verbose "Created file '$tempName'"
            try {
              Write-Verbose "Started writing '$tempName'"
              [IO.File]::WriteAllText("$tempName",$Regex.Replace($text,$Replacement),[Text.Encoding]::$Encoding)
              Write-Verbose "Finished writing '$tempName'"
              Write-Verbose "Started copying '$tempName' to '$fullName'"
              Copy-Item $tempName $fullName -Force:$Force -ErrorAction Continue
              if ( $? ) {
                Write-Verbose "Finished copying '$tempName' to '$fullName'"
              }
              Remove-Item $tempName
              if ( $? ) {
                Write-Verbose "Removed file '$tempName'"
              }
            }
            catch [Management.Automation.MethodInvocationException] {
              Write-Error $Error[0]
            }
          }
        }
      }
      else {
        Write-Error "The item '$path' must be a file in the file system." -Category InvalidType
      }
    }
  }
}

process {
  foreach ( $PathItem in $Path ) {
    if ( $Overwrite ) {
      if ( $PSCmdlet.ShouldProcess("'$PathItem'","Overwrite file") ) {
        Update-FileText $PathItem
      }
    }
    else {
      Update-FileText $PathItem
    }
  }
}

Also available as a gist on Github.

Bill_Stewart
  • 18,984
  • 4
  • 42
  • 53
-1

Small correction for the Set-Content command. If the searched string is not found the Set-Content command will blank (empty) the target file.

You can first verify if the string you are looking for exist or not. If not it will not replace anything.

If (select-string -path "c:\Windows\System32\drivers\etc\hosts" -pattern "String to look for") `
    {(Get-Content c:\Windows\System32\drivers\etc\hosts).replace('String to look for', 'String to replace with') | Set-Content c:\Windows\System32\drivers\etc\hosts}
    Else{"Nothing happened"}
WickedFan
  • 356
  • 2
  • 12
dan D
  • 3
  • 3
  • 3
    Welcome to StackOverflow! Please use formatting, you can read [this article](http://stackoverflow.com/help/formatting) if you need help. – CodenameLambda Dec 28 '16 at 19:27
  • 1
    This is not true, if one uses the correct answer and the replace is not found, it still writes the file, but there is no changes. E.g. `set-content test.txt "hello hello world hello world hello"` and then `(get-content .\test.txt).Replace("something", "awesome") | set-content .\test.txt` will not empty the file as suggested in this. – Ciantic Jan 23 '17 at 16:04