I finally narrowed down the problem, but need help understanding why.
Invoking Powershell from a batch file, the following works to replace a string within a file:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*Backup.).*.(?<bar>..Backup.)', '${foo}Enabled${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
Now if I try this, the command fails:
Powershell -Command "$foobar=[IO.File]::ReadAllText("$Env:File") ;
$foobarred= $foobar -replace '(?<foo>.*$Env:StrStart.).*.(?<bar>..$Env:StrEnd.)', '${foo}$Env:StrVal${bar}' ;
[IO.File]::WriteAllText("$Env:File", $foobarred); "
If I use a variable passed in from Batch, it fails everytime. If I use PlainText in the command instead for the replacement value, it works just fine. Why does this happen?
CodePudding user response:
My observations would be
- you need to
[regex]::Escape()
arbitrary values when you build regular expressions dynamically. - PowerShell does not do any string interpolation in single-quoted strings, so things like
'${foo}$Env:StrVal${bar}'
will not work the way you want.
I'd use the following command:
(Get-Content "filename" -Raw) -replace (
'(.*' [regex]::Escape("start string") '.).*.(..' [regex]::Escape("end string") '.)'
),(
'$1' "replacement string" '$2'
) | Set-Content "filename"
called ad-hoc from a batch file as follows (compressed onto one line):
@echo off
setlocal
set "FILENAME=filename"
set "START=start string"
set "END=end string"
set "REPLACEMENT=replacement string"
set "PSCMD=(gc $Env:FILENAME -Raw) -replace ('(.*' [regex]::Escape($Env:START) '.).*.(..' [regex]::Escape($Env:END) '.)'),('$1' $Env:REPLACEMENT '$2') | sc $Env:FILENAME"
powershell -NoLogo -Command "&{%PSCMD%}"
But this is disproportionately hard to maintain.
I'd recommend writing a .ps1 file and passing named arguments, instead of juggling environment variables.
# MyReplace.ps1
param(
[string]$Filename,
[string]$Start,
[string]$End,
[string]$Replacement
)
$ErrorActionPreference = "Stop"
$content = Get-Content $Filename -Raw
$content = $content -replace ('(.*' [regex]::Escape($Start) '.).*.(..' [regex]::Escape($End) '.)'),('$1' $Replacement '$2')
$content | Set-Content $Filename
and in batch
powershell -NoLogo -File MyReplace.ps1 -Filename "filename" -Start "start string" -End "end string" -Replacement "replacement string"
That seems more manageable to me.
CodePudding user response:
At the advice of @Gerhard, adding what I found into the fray as an answer, but did ultimately giving @Tomalak the credit for the better answer overall.
In order to accept variables into both the match pattern and the replace pattern, you have to concatenate the strings (similar to how it is done in Visual Basic).
Reference below:
I have split the command up into multiple lines for readability - if you use this, place it on one line.
Important - when using this, be sure to remove the line-breaks if you use it in a batch file. Also - be wary - I have had some circumstances where even using Set-Content can change the Encoding of the file. I much prefer the secondary solution offered down below.
Powershell -Command "$pattern= '(?<RangeStart>.*' [regex]::Escape($Env:StrStart) '.).*.(?<RangeEnd>..' [regex]::Escape($Env:StrEnd) '.)' ;
$repl= '${RangeStart}' $Env:StrVal '${RangeEnd}' ;
$fil2parse=(Get-Content $Env:FileTOParse) -replace $pattern, $repl | Set-Content $Env:FileTOParse; "
This solution works as well, but I have had much fewer issues with it changing Encoding.
Powershell -Command "$pattern= '(?<RangeStart>.*' [regex]::Escape($Env:StrStart) '.).*.(?<RangeEnd>..' [regex]::Escape($Env:StrEnd) '.)' ;
$repl= '${RangeStart}' $Env:StrVal '${RangeEnd}' ;
$fil2parse=[IO.File]::ReadAllText("$Env:FileTOParse") ;
$filParsed= $fil2parse -replace $pattern, $repl ;
[IO.File]::WriteAllText("$Env:FileTOParse", $filParsed); "