Execute “real life” command line from variable in Powershell

前端 未结 3 598
暗喜
暗喜 2020-12-10 21:36

When I for example read some uninstall string from the registry like \"C:\\Program Files (x86)\\Opera\\Launcher.exe\" /uninstall I can copy it to the Powershell

相关标签:
3条回答
  • 2020-12-10 22:00

    You wrote:

    $var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    & $var
    

    You've noticed that this doesn't work. That's because the argument to the & operator is a string representing the command to execute (but not parameters). The correct way to write it would be:

    $var = "C:\Program Files (x86)\Opera\Launcher.exe"
    & $var /uninstall
    

    If you want to read a command line string from the registry (or somewhere else) and execute it as-is, one way to avoid Invoke-Expression or invoking cmd.exe is to tokenize the string. Here's a function that does that:

    function Split-String {
      param(
        [String] $inputString
      )
      [Regex]::Matches($inputString, '("[^"]+")|(\S+)') | ForEach-Object {
        if ( $_.Groups[1].Success ) {
          $_.Groups[1].Value
        }
        else {
          $_.Groups[2].Value
        }
      }
    }
    

    The first element in the returned array is the executable's name, and the remaining elements (if any) are the executable's parameters. The quote marks around the array elements are preserved.

    You could use the function to run an executable using the & operator as follows:

    $var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    $tokens = @(Split-String $var)
    $command = $tokens[0] -replace '^"?([^"]+)"?$', '$1'
    if ( $tokens.Count -eq 1 ) {
      & $command
    }
    else {
      & $command $tokens[1..$($tokens.Count - 1)]
    }
    

    The third line in the code removes leading and trailing " characters from the command name.

    If you want to run the executable asynchronously, you can use Start-Process. Example:

    $var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    $tokens = @(Split-String $var)
    $startArgs = @{
      "FilePath" = $tokens[0] -replace '^"?([^"]+)"?$', '$1'
    }
    if ( $tokens.Count -gt 1 ) {
      $startArgs.ArgumentList = $tokens[1..($tokens.Count - 1)]
    }
    Start-Process @startArgs
    
    0 讨论(0)
  • 2020-12-10 22:24

    In order to use & (or direct invocation if the command name/path is an unquoted literal), the command name/path must be passed separately from its arguments.
    When invoking an external program, you may pass these arguments as an array.

    The solution below leverages PowerShell's own Write-Output cmdlet in combination with a - safe - invocation of Invoke-Expression[1] in order to parse the string into its constituent arguments.

    # Gets the arguments embedded in the specified string as an array of literal tokens 
    # (applies no interpolation). 
    # Note the assumption is that the input string contains no NUL characters 
    # (characters whose code point is `0x0`) - which should be a safe assumption
    # Example: 
    #   get-EmbeddedArguments '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    # yields array @( 'C:\Program Files (x86)\Opera\Launcher.exe', '/uninstall' )
    function get-EmbeddedArguments ([string] $String) {
      (Invoke-Expression "Write-Output -- $($String -replace '\$', "`0")") -replace "`0", '$'            #"
    }
    
    # Input string.
    $var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    
    # Extract the command name (program path) and arguments.
    # Note the destructuring assignment that stores the return array's 1st element
    # in $exe, and collects the remaining elements, if any, in $exeArgs.
    $exe, $exeArgs = get-EmbeddedArguments $var
    
    # Use & to invoke the command (name / program path) ($exe) and pass
    # it all arguments as an array.
    & $exe $exeArgs
    

    [1] Invoke-Expression should generally be avoided, as Bill points out, because it presents a security risk and there are typically safer and more robust options available. Here, however, there is no simple alternative, and the security risk is avoided by temporarily replacing all $ instances in the input string so as to prevent unintended string interpolation.

    0 讨论(0)
  • 2020-12-10 22:25

    I think Bill Stewart's answer is the right way to do it. If for some reason you can't get Bill's answer to work you could use Invoke-Expression to do it though.

    $var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
    Invoke-Expression "& $var"
    
    0 讨论(0)
提交回复
热议问题