Home > Software design >  How to escape the following regex, so its usable in PowerShell?
How to escape the following regex, so its usable in PowerShell?

Time:02-16

As far as I know, there is no way in PowerShell to execute an exe with parameters specified in a variable and direct the return of the exe into a variable. Therefore I am currently writing a small function to make this possible. But now I am stuck at the point that the parameters have to be passed individually when calling with &. (This is not necessary for all programs, but some programs cause problems if you pass all parameters as a string in a variable) Therefore I want to use a split to write the parameters passed to my function into an array. And then pass the array with the parameters in my exe call.

For this I have the following regex:

[^\s"'] |"([^"]*)"|'([^']*)'

This regex allows that single and double quotes are taken into account when passing parameters and that a text with spaces inside them is not split.

But unfortunately I don't have the slightest idea how to best escape this regex so that it doesn't cause any problems in the PowerShell script.

Here then still my function to make it a little easier to understand: The function executes the file passed in the $Path parameter with the parameters from the $Arguments. Before the execution i try to split the $Arguments with the regex. As return of the function, you get an object with the ExitCode and the output of the executed file. Here you can see my attempt, but the quotes cause problems with the following code.

function Invoke-Process ($Path,$Arguments){
    [PsObject[]] $ReturnValue = @()
    $Params=$Arguments -split([regex]::escape([^\s"'] |"([^"]*)"|'([^']*)'))
    $ExCode = 0
    try {
        $ProcOutput = & $Path $Params | out-string
    }catch{
        $ProcOutput = "Failed: $($_.Exception.Message)"
        $ExCode = 1
    }
    $ReturnValue  = [PsObject]@{ ExitCode = $ExCode; Output = $ProcOutput}
    Return $ReturnValue
}

The function is called as follows:

$ExePath = "C:\arcconf\arcconf.exe"
$ExeParams = "getconfig 1"
$Output = Invoke-Process $ExePath $ExeParams 

I hope you can understand my problem. I am also open to other ways of writing the function.

Greetings

CodePudding user response:

There's nothing you need to escape - the pattern is perfectly valid.

All you need is a string literal type that won't treat the quotation marks as special. For this, I would suggest a verbatim here-string:

@'
This is a single-line string ' that " can have all sorts of verbatim quotation marks
'@

The qualifiers for a here-string is @' as the last token on the preceding line and '@ as the first token on the following line (for an expandable here-string, use @" and "@).

Try running it with valid sample input:

$pattern = @'
[^\s"'] |"([^"]*)"|'([^']*)'
'@
'getconfig 1 "some string" unescaped stuff 123' |Select-String $pattern -AllMatches |ForEach-Object {$_.Matches.Value}

Which, as expected, returns:

gesture
1
"some string"
unescaped
stuff
123

As an alternative to a here-string, the best alternative is a regular verbatim string literal. The only character you need to escape is ', and the escape sequence is simply two in a row '', so your source code becomes:

$pattern = '[^\s"''] |"([^"]*)"|''([^'']*)'''

CodePudding user response:

Mathias R. Jessen's helpful answer answers your immediate question well.

Taking a step back:

there is no way in PowerShell to execute an exe with parameters specified in a variable and direct the return of the exe into a variable

No: PowerShell does support both of those things:

  • $output = someExternalProgram ... captures the stdout output from an external program in variable $output; use redirection 2>&1 to also capture stderr output; use >$null or 2>$null to selectively discard stdout or stderr output - see this answer for details.

  • someExternalProgram $someVariable ... passes the value of variable $someVariable as an argument to an external program; if $someVariable is an array (collection) of values, each element is passed as a separate argument. Instead of a variable reference, you may also use the output from a command or expression, via (...); e.g., someExternalProgram (1 2) passes 3 as the argument - see this answer for details.

    • Note: An array's elements getting passed as individual arguments only happens for external programs by default; to PowerShell-native commands, arrays are passed as a whole, as a single argument - unless you explicitly use splatting, in which case you must pass @someVariable rather than $someVariable. For external programs, @someVariable is effectively the same as $someVariable. While array-based splatting also works for PowerShell-native commands, for passing positional arguments only, the typical and more robust and complete approach is to use hashtable-based splatting, where the target parameters are explicitly identified.

    • A general caveat that applies whether or not you use variables or literal values as arguments: Up to at least PowerShell 7.2.x, passing empty-string arguments or arguments with embedded " chars. to external programs is broken - see this answer.

With the above in mind you can rewrite your function as follows, which obviates the need for - ultimately brittle - regex parsing:

function Invoke-Process {
  # Split the arguments into the executable name / path and all
  # remaining arguments.
  $exe, $passThruArgs = $args
  try {
    # Call the executable with the pass-through arguments.
    # and capture its *stdout* output.
    $output = & $exe @passThruArgs
    $exitCode = $LASTEXITCODE      # Save the process' exit code.
  } catch {
    # Note: Only If $exe is empty or isn't a valid executable name or path
    #       does a trappable statement-terminating error occur.
    #       By contrast, if the executable can be called in principle, NO
    #       trappable error occurs if the process exit code is nonzero.
    $exitCode = 127         # Use a special exit code
    $output = $_.ToString() # Use the exception message
  }
  # Construct and output a custom object containing
  # the captured stdout output and the process' exit code.
  [pscustomobject] @{
    ExitCode = $exitCode
    Output = $output
  }
}

Sample calls:

Invoke-Process cmd /c 'echo hi!'

# Equivalent, using an array variable for the pass-through arguments.
$argsForCmd = '/c', 'echo hi!'
Invoke-Process cmd @argsForCmd

Note:

  • Since Invoke-Process is a PowerShell command, splatting (@argsForCmd) is necessary here in order to pass the array elements as individual arguments, which inside the function are then reflected in the automatic $args variable variable.

  • The automatic $args variable is only available in simple functions, as opposed to advanced ones, which behave like cmdlets and therefore automatically support additional features, such as common parameters; to make your function and advanced one, replace the line $exe, $passThruArgs = $args at the top of the function with the following:

  [CmdletBinding()]
  param(
    [Parameter(Mandatory)]
    [string] $exe,
    [Parameter(ValueFromRemainingArguments)]
    [string[]] $passThruArgs
  )
  • Related