37

I am trying to read replace a line in a configuration file using PowerShell. Sometimes this script works, but most of the time it does not replace the line.

(Get-Content D:\home\App_Config\Sitecore.config) `
    | %{ $_ -replace '  <setting name="Media.MediaLinkServerUrl" value=" "/>',' <setting name="Media.MediaLinkServerUrl" value="https://newurl.com"/>'} `
    | Set-Content D:\home\App_Config\Sitecore.config

4 Answers 4

63

Try the following:

$file = 'D:\home\App_Config\Sitecore.config'
$regex = '(?<=<setting name="Media\.MediaLinkServerUrl" value=")[^"]*'
# Update the target file in-place - be sure to make a backup first.
(Get-Content -Raw $file) -replace $regex, 'https://newurl.com' | 
  Set-Content -NoNewLine $file

Notes on the file-reading and -writing aspect of the solution:

  • Re Get-Content: Use of the -Raw switch (v3+)[1] reads the entire file in full into memory, as a single, multiline string, which greatly speeds up the operation and would be a necessity if you wanted to match across multiple lines.Thanks, deadlydog.

    • By contrast, omit -Raw for (much slower) line-by-line processing; complementarily, omit -NoNewLine from the Set-Content call.

    • Either way, note the required parentheses ((...), the grouping operator) around the Get-Content call to ensure that the pipeline can write back to the same file that Get-Content has read from (in the context of applying an operator such as -replace to Get-Content output, you need the parentheses anyway).

  • Re Set-Content:

    • In Windows PowerShell it uses your system's legacy ANSI encoding by default (typically a fixed, single-byte character encoding such as Windows-1252), so you may want to use -Encoding to control the output file's encoding explicitly.
      PowerShell (Core) 7 fortunately defaults to BOM-less UTF-8, across all cmdlets.
      Similarly, Windows PowerShell's Get-Content interprets a BOM-less input file as ANSI-encoded too, so an -Encoding argument may be needed there as well.
      Notably, Get-Content does not preserve the input file's character encoding, and Set-Content invariably applies its default on writing; see this answer for details.

    • -NoNewLine,(v5+)[1] in combination with Get-Content's -Raw, writes the multiline input string as-is (back) to the target file, without appending a (trailing) newline.

Due to using a regex (regular expression) to match your existing setting in the context of the -replace operator (explained in detail below), any text currently inside value="..." is matched, so this command will work even when run repeatedly, with different replacement URLs, if needed (and repeated runs with the same replacement URL are, in effect, benign no-ops).

By contrast, what you tried uses an effectively literal string (... value=" ") to find what to replace, and after the 1st - potentially successful - run, that literal no longer matches, so subsequent runs do not perform the intended replacement.

The command above uses a streamlined approach to replacement:

  • (?<=<setting name="Media.MediaLinkServerUrl" value=") is a lookbehind assertion ((?<=...)) that matches, but doesn't capture what it matches: it finds the part up to and including the opening " of the value you're trying to replaces, without making that prefix a part of what will get replaced.

  • [^"]* then matches the entire value, up to, but not including the closing ". ([^"] is a character set that matches any character other than (^) a ", and * finds any (possibly empty) sequence of such characters.

  • Therefore, because the regex captured only the value itself, all you need to specify as the replacement string is the new value.


[1] These version numbers refer to - now long obsolete - versions of Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and final version is 5.1). The features in question are equally available in (all versions of) PowerShell (Core) 7, the modern, cross-platform, install-on-demand edition of PowerShell.

Sign up to request clarification or add additional context in comments.

Comments

28

Use the Replace method like this:

$file = 'D:\home\App_Config\Sitecore.config'
$find = '  <setting name="Media.MediaLinkServerUrl" value=" "/>'
$replace = ' <setting name="Media.MediaLinkServerUrl" value="https://newurl.com"/>'

(Get-Content $file).replace($find, $replace) | Set-Content $file

6 Comments

.Replace() is a method of .NET type [string], not an operator. Unlike PowerShell's -replace operator, which uses regular expressions, .Replace() performs literal substring replacements, which I suspect may be neha's problem: after an initial (successful) run of the script, the literal doesn't match anymore.
To put it differently: Even though your code is more concise and more efficient, it is in essence the same as the OP's and doesn't explain the intermittent failures. (The only - negligible - functional difference is that the . in the search string in the OP's command, due to use of -replace, is the match-any-character regex metacharacter, whereas it is a literal in your command, as it should be.)
code provide by me some time works perfectlly but sometime even though search string is : <setting name="Media.MediaLinkServerUrl" value=" "/> still it is not replacing it. I will try your script but confused why mine is not working !
@mklement0 's answer is better than mine, he knows the dark art of regex. I missed that class in wizarding school ;)
@mklement no this is not the case...I am manually changing value ="newurl" to value=" " everytime before running script. But still facing the issue..Anyway will work with the script given by you. Thanks :)
|
1

This Function worked for me

I am trying to replace anything come after Infile enter image description here

Function:

function Write-FileContent {
        [cmdletBinding()]
        param(
            [parameter(Mandatory = $true)]
            [string]$FileToReplacePath,
            [parameter(Mandatory = $true)]
            [string]$TextToReplaceWith,
            [parameter(Mandatory = $true)]
            [string]$LineNumber,
            [parameter(Mandatory = $true)]
            [string]$TextToBeingWith        
        )
        $Read = Get-Content -Path $FileToReplacePath
        $Read | ForEach-Object { if ($_.ReadCount -eq $LineNumber) { $_ -replace "'$TextToBeginWith'=.+'", "$TextToReplaceWith" }   else { $_ } } | Set-Content $FileToReplacePath
    }

Testing Parameter

$CsvFilePath="C:\msydatfgdfa.csv"
Write-FileContent -FileToReplacePath D:\test.txt -TextToReplaceWith "'$CsvFilePath'" -LineNumber 2 -TextToBeingWith "Infile"

Comments

0

Here's an example that preserves the PSPath property, so you don't have to specify the path to set-content:

(Get-Content -raw input) | ForEach-Object {
    $_ -replace 111,222 |
    Add-Member NoteProperty PSPath $_.PSPath -PassThru
} | Set-Content -nonewline

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.