【问题标题】:Execute "real life" command line from variable in Powershell从 Powershell 中的变量执行“现实生活”命令行
【发布时间】:2018-05-22 16:47:29
【问题描述】:

例如,当我从注册表中读取一些卸载字符串(如 "C:\Program Files (x86)\Opera\Launcher.exe" /uninstall)时,我可以将其复制到 Powershell 命令行,在其前面加上调用运算符并执行它。

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

但是

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

不起作用。当然我只能说

cmd /c $var

但是如果没有额外的cmd 进程,真的没有办法做到这一点吗?

【问题讨论】:

    标签: powershell


    【解决方案1】:

    为了使用&(如果命令名称/路径是不带引号的文字,则直接调用),命令名称/路径必须单独与其参数传递。
    调用外部程序时,您可以将这些参数作为 array 传递。

    以下解决方案利用 PowerShell 自己的 Write-Output cmdlet 与 Invoke-Expression 的安全调用结合使用[1] 为了将字符串解析为其组成参数。

    # 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。然而,这里没有简单的替代方案,通过临时替换输入字符串中的所有$实例来避免安全风险,以防止意外的字符串插值。

    【讨论】:

      【解决方案2】:

      我认为比尔·斯图尔特的回答是正确的做法。如果由于某种原因您无法得到 Bill 的工作答案,您可以使用 Invoke-Expression 来解决。

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

      【讨论】:

      【解决方案3】:

      你写道:

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

      您已经注意到这不起作用。这是因为& 运算符的参数是一个字符串,表示要执行的命令(但不是参数)。正确的写法是:

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

      如果您想从注册表(或其他地方)读取命令行字符串并按原样执行,避免Invoke-Expression 或调用cmd.exe 的一种方法是对字符串进行标记。这是一个执行此操作的函数:

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

      返回数组中的第一个元素是可执行文件的名称,其余元素(如果有)是可执行文件的参数。数组元素周围的引号被保留。

      您可以使用该函数通过& 运算符运行可执行文件,如下所示:

      $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)]
      }
      

      代码中的第三行从命令名称中删除前导和尾随 " 字符。

      如果要异步运行可执行文件,可以使用Start-Process。示例:

      $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
      

      【讨论】:

        猜你喜欢
        • 2023-03-26
        • 1970-01-01
        • 2012-08-03
        • 1970-01-01
        • 1970-01-01
        • 2020-12-18
        • 1970-01-01
        • 2021-11-18
        • 2014-08-27
        相关资源
        最近更新 更多