Regex to match "here strings" in PowerShell code

Viewed 171

I'm looking for a regex to match here strings in PowerShell @'...'@ and @"..."@ are here strings

Rules:

  1. Always new line is followed after start(@' or @")
  2. There's no character after end ('@ or "@), it always is at the line start, however more text can follow it up
  3. The outer @' .. '@ may include an inner @" "@ but in this case the outer will be matched.

Examples

  1. Example where outer (including hello) will be matched
$MyString = @"
hello
@'
'@
bye
"@
  1. Other example where outer (including hello) will be matched
$MyString = @'
hello
@"
"@
bye
'@
  1. Example from Microsoft
$page = [XML] @"
    <command:command xmlns:maml="http://schemas.microsoft.com/maml/2004/10"
    xmlns:command="http://schemas.microsoft.com/maml/dev/command/2004/10" 
    xmlns:dev="http://schemas.microsoft.com/maml/dev/2004/10">
    <command:details>
            <command:name>
                   Format-Table
            </command:name>
            <maml:description>
                <maml:para>Formats the output as a table.</maml:para>
            </maml:description>
            <command:verb>format</command:verb>
            <command:noun>table</command:noun>
            <dev:version></dev:version>
    </command:details>
    ...
    </command:command>
"@

I'd appreciate any help. I'm trying solve inlining for better PowerShell templating support to privacy.sexy so your help will be scaled to more in community.

2 Answers

You're better off using PowerShell's language parser, System.Management.Automation.Language.Parser, rather than a regex-based solution.[1]

I'm assuming you're always interested in the outer here-string, not one that happens to be nested inside another.

Assuming a file file.ps1 with the following verbatim content:

$MyString1 = @'
hello
@"
"@
bye
'@

$MyString2 = @"
hello
@'
'@
bye
"@

The following command:

$ast = [System.Management.Automation.Language.Parser]::ParseInput(
  (Get-Content -Raw file.ps1), 
  [ref] $null,
  [ref] $null
)

$ast.FindAll(
  { 
    $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and
      $args[0].StringConstantType -in 'SingleQuotedHereString', 'DoubleQuotedHereString' 
  },
  $true
) | Format-Table StringConstantType, Value -Wrap

outputs:

    StringConstantType Value
    ------------------ -----
SingleQuotedHereString hello
                       @"
                       "@
                       bye
DoubleQuotedHereString hello
                       @'
                       '@
                       bye

[1] A regex-based solution can yield false positives, which are virtually impossible to avoid. See MikeM's answer for an example; another one are here-strings that happen to be commented out via <# ... #>

A regex solution, such as, for example,

"@(""|')\r?\n[\s\S]*?\r?\n\1@(?=\r?\n|$)"

or

"(?ms)@(""|')\r?$.*?^\1@(?=\r?$)"

may be good enough, although there is the potential for false matches.

For example, there is no actual here-string in the following valid Powershell code, but the regex above will produce a match.

$MyString = "Definitely @'
not a
'@
here-string
"
Related