To complement Santiago's helpful answer, which offers an effective solution that additionally speeds up the operation with the use of Get-Content
's -Raw
switch, which reads a given text file in full, as a single, (typically) multiline string.
An additional optimization is possible:
.NET's .Replace()
string-replacement method returns the input instance as-is if no actual replacement was performed (the same applies analogously to PowerShell's regex-based -replace
operator).
Thus, if the input string and the result string are the exact same [string]
instance, the implication is that NO substitutions were performed, from which you can infer that the search string wasn't present.
Therefore, using this technique saves you from having to search the file contents twice (once to look for the string, and again during replacement), because you can infer from the result of the .Replace()
call whether the search string was present in the file at hand:
Get-ChildItem -LiteralPath $dirPath -Recurse -Filter *.config | ForEach-Object {
$content = $_ | Get-Content -Raw
# Perform the desired replacement.
$modifiedContent = $content.Replace($oldString, $newString)
# Were actual replacements made? If not, the implication is that
# $oldString wasn't present in the file at hand.
$actuallyModified = -not [object]::ReferenceEquals($content, $modifiedContent)
if ($actuallyModified) {
$modifiedContent | Set-Content -NoNewLine -LiteralPath $_.FullName # Save modified content.
$_.FullName # Output the path of the modified file to the pipeline.
}
} | Set-Content -LiteralPath $filePathName
Note the use of -LiteralPath
to ensure that all paths are treated literally (verbatim); by default, with the (positionally impied) -Path
parameter, paths are interpreted as wildcard expressions, which notably causes problems with literal paths that contain [
and ]
. Due to a bug in Windows PowerShell (since fixed in PowerShell (Core) 7+), -LiteralPath
cannot be combined with -Include
- see this answer - which is why -Filter
is used instead (which is actually a blessing, because it is much faster than -Include
).
Character-encoding caveat:
Get-Content
reads text files into .NET strings, without storing information about the file's original character encoding.
PowerShell's file-writing cmdlets such as Set-Content
and Out-File
/ >
operate on .NET strings and by default use their default character encoding (which is fixed an unrelated to any input):
In Windows PowerShell, it is ANSI for Set-Content
and "Unicode" (UTF-16LE) for Out-File
/ >
(other cmdlets may have different defaults - see the bottom section of this answer).
More sensibly, PowerShell (Core) 7+ now uses the same default character encoding for all cmdlets, which is BOM-less UTF-8 (in Windows PowerShell you can use -Encoding utf8
, but you always get a BOM - see this answer for a workaround).
Upshot:
You may have to use the -Encoding
parameter to get the desired output character encoding.
If you need to match an input file's encoding, you must know what it is (so you can pass its name to -Encoding
).