2

I am trying to replace some text in a bunch of ps1 script files. Normally this is no issue, but i'm having some difficulty when the replace string is containing both special characters like "$" along with single quotes. I have tried to escape the special characters but just cannot get it working. The specific text i want to replace is..

'WORKVPN1*')

with

'WORKVPN1*') -and ($_.VPN -notlike 'WORKVPN2*')

I have tried the following code which doesnt work.

Get-ChildItem "C:\temp\*.ps1" | ForEach-Object -Process {(Get-Content $_) -Replace "'WORKVPN1*')" , "WORKVPN1*'`) -and `(`$_.VPN -notlike 'WORKVPN2*'`)" | Set-Content $_}
Johnny Cakes
  • 133
  • 1
  • 8

2 Answers2

1
  • For verbatim (literal) replacements, -replace, the regular-expression-based string replacement operator, requires escaping of both the regex operand and the substitution operand, albeit using different mechanisms.

  • Unless you need up-front string expansion (interpolation), before the results are passed to the underlying .NET APIs, avoid use of expandable (double-quoted) strings ("...") with -replace (and -match and switch -Regex).

    • Even then it's best to use verbatim (single-quoted) strings ('...') and - if needed - use them as part of expressions to splice in the dynamic parts
      (e.g. 'a ' + $var + ' b' instead of "a $var b")

      • Using '...' quoting avoids potential confusion that may arise with "..." strings over which parts of the string are expanded (interpolated) by PowerShell up front vs. what the .NET regex engine ends up seeing.

      • Notably, in the substitution operand $_ has special meaning to the regex engine, but using it inside "..." would expand the automatic $_ variable instead; compare 'can' -replace '^', '$_' - which doubles the input string, as intended (cancan) - with 'can' -replace '^', "$_", which yields just can, because the automatic $_ variable expanded to the empty string in the absence of a pipeline context.

Applied to your case:

Get-ChildItem C:\temp -Filter *.ps1 | 
  ForEach-Object {
    ($_ | Get-Content -Raw) -replace '''WORKVPN1\*''\)', 
                                     '$& -and ($$_.VPN -notlike ''WORKVPN2*'')"' | 
      Set-Content -LiteralPath $_.FullName
  }

Note:

  • '' is needed to escape ' chars. embedded inside '...' strings

  • Regex metacharacters * and ) are individually \-escaped.

  • $& in the substitution operand refers to what the regex captured (that is, verbatim 'WORKVPN1*')

  • $$ is used to escape the $ character in the substitution operand meant to be used verbatim - $ characters are the only characters that require escaping in a substitution operand - see Substitutions in Regular Expressions


An alternative is to use verbatim (literal) replacements using the [string] type's .Replace() method, which directly and exclusively performs verbatim replacements:

  • However, this necessitates repeating verbatim parts in the replacement string.

  • Also, this method is case-sensitive, invariably in Windows PowerShell, and by default in PowerShell (Core) 7+.

    • By contrast, PowerShell's -replace operator is case-insensitive by default, and offers case-sensitivity via its -creplace variant.

See this answer for guidance on when to use -replace vs. .Replace().

mklement0
  • 382,024
  • 64
  • 607
  • 775
0

-replace using regex, so escape the regex characters. This method does it for you.

[regex]::escape("'WORKVPN1*')")

'WORKVPN1\*'\)

.replace() doesn't use regex, but this overload is case sensitive:

$string.replace("'WORKVPN1*')", "whatever")

I'm just backslashing the star and the parens. You don't have to backquote the parens in the replacement text. $$ is the code for a literal $ in -replace.

"'WORKVPN1*')" -Replace "'WORKVPN1\*'\)", 
  "WORKVPN1*') -and (`$`$_.VPN -notlike 'WORKVPN2*')"

WORKVPN1*') -and ($_.VPN -notlike 'WORKVPN2*')

By the way, I'm not sure where these -replace codes are buried in the docs:

$number Substitutes the last submatch matched by group number.
${name} Substitutes the last submatch matched by a named capture of the form
(?<name>).
$$ Substitutes a single "$" literal.
$& Substitutes a copy of the entire match itself.
$` Substitutes all the text from the argument string before the matching portion.
$' Substitutes all the text of the argument string after the matching portion.
$+ Substitutes the last submatch captured.
$_ Substitutes the entire argument string.

In .replace(), you would still have to backquote the dollar sign, because of the double-quotes.

"'WORKVPN1*')".Replace("'WORKVPN1*')",
  "WORKVPN1*') -and (`$_.VPN -notlike 'WORKVPN2*')")

WORKVPN1*') -and ($_.VPN -notlike 'WORKVPN2*')
js2010
  • 23,033
  • 6
  • 64
  • 66
  • i understand what your saying, but this isnt working for my get-childitem specific use case where i need to replace the text in multiple files like my example code. – Johnny Cakes May 01 '23 at 20:33