powershellfor-loopbatch-file

Missing terminator in PowerShell CLI within a for loop using expanded variables


I want to use a for loop to replace only the 2nd match of a string in a file. To achieve this I use a PowerShell CLI command which has worked outside the for loop.

Now since I have to iterate over multiple files defined in %files% I use for /f "tokens=1" to separate file 1 from file 2 because both strings I want to replace are in file 1.

This is the for loop I am using:

for /l %%a in (1,1,2) do (
    for /f "tokens=1" %%b in ("!files!") do (
        set "file=%apb%\%%~b"
        echo !string[%%a]! --^> !replace[%%a]!
        rem Use PowerShell to replace only the second occurrence of a match, here `$matches[1]`.
        PowerShell -NoLogo -NoProfile -Command ^
            "$file = '!file!';" ^
            "$string = '(?m)^!string[%%a]!=.*';" ^
            "$content = Get-Content -Raw -Encoding UTF8 $file;" ^
            "$matches = [regex]::Matches($content, $string);" ^
            "if ($matches.Count -ge 2) {" ^
            "   $stringTwo = $matches[1];" ^
            "   $stringTwoPosition = $stringTwo.Index;" ^
            "   $stringTwoLength = $stringTwo.Length};" ^
            "Set-Content -NoNewLine $file -Value $content.Remove($stringTwoPosition, $stringTwoLength).Insert($stringTwoPosition, '!string[%%a]!=!string[%%a]!')"
    )
)

My issue with the for loop is, that it returns the following error:

The string is missing the terminator: '.
    + CategoryInfo          : ParserError: (:) [], ParentContainsErrorRecordException
    + FullyQualifiedErrorId : TerminatorExpectedAtEndOfString

All terminators are set though. I have read somewhere that expanded variables may cause issues withing PowerShell CLI commands but I have no idea how to bypass expanding the variables and getting the result I want to achieve.

I have tried calling the values of !string[%%a]! and !replace[%%a]! through non-expanded variables %stringAlias% and %replaceAlias% which did not work.

This is the rest of the snippet to prepare for the final for loop:

@echo off
setlocal EnableDelayedExpansion
rem Set target files.
set "files=Engine\Config\BaseEngine.ini APBGame\Config\DefaultEngine.ini"
rem Initialize variables.
set "replace="
set "string="
call :subroutine.ping_optimization "%files%"
if !errorlevel! equ 0 (
    set "count=1"
    for %%a in (
        "1.0"
        "5.0"
        rem ...
    ) do (
        set "replace[!count!]=%%~a"
        set /a "count+=1"
    )
)
if !errorlevel! equ 1 (
    set "count=1"
    for %%a in (
        "0.001"
        "0.001"
        rem ...
    ) do (
        set "replace[!count!]=%%~a"
        set /a "count+=1"
    )
)
set "count=1"
for %%a in (
    "AckTimeout"
    "KeepAliveTime"
    rem ...
) do (
    set "string[!count!]=%%~a"
    set /a "count+=1"
)
rem for loop causing issues goes here...

Solution

  • One of the many unfortunate behaviors of cmd.exe (the interpreter of batch files) is that commands behave differently inside the (...)-enclosed bodies of for /f loops.

    Specifically, the intra-line ^ character in "$string = '(?m)^!string[%%a]!=.*';" ^ - even though it is inside an "..." string - is inexplicably not used verbatim and requires escaping as ^^.

    That is, replacing the line
    "$string = '(?m)^!string[%%a]!=.*';" ^ in the code in your question with
    "$string = '(?m)^^!string[%%a]!=.*';" ^ should fix your problem.