Выполнить "реальную" командную строку из переменной в Powershell
Например, когда я читаю некоторую строку удаления из реестра, например "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
процесс?
3 ответа
В ответе Билла Стюарта есть несколько хороших замечаний, но если целью является выполнение прямого, синхронного выполнения с &
нет веских причин для привлечения Start-Process
, который (а) в корне меняет природу вызова (асинхронный, без связи с потоками вызывающего) и (б) имеет свои собственные проблемы с цитированием.
Для того, чтобы использовать &
(или прямой вызов, если имя / путь команды является литералом без кавычек), имя / путь команды следует передавать отдельно от ее аргументов.
При вызове внешней программы вы можете передать эти аргументы в виде массива.
В приведенном ниже решении используется собственный PowerShell. Write-Output
Командлет в сочетании с - безопасным - вызовом 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
как правило, следует избегать, как указывает Билл, поскольку это представляет угрозу безопасности, и обычно существуют более безопасные и надежные варианты. Здесь, однако, нет простой альтернативы, и риск безопасности устраняется путем временной замены всех $
экземпляры во входной строке, чтобы предотвратить непреднамеренную интерполяцию строки.
Я думаю, что ответ Билла Стюарта - правильный способ сделать это. Если по какой-то причине вы не можете получить ответ Билла на работу, вы можете использовать Invoke-Expression
чтобы сделать это хотя.
$var = '"C:\Program Files (x86)\Opera\Launcher.exe" /uninstall'
Invoke-Expression "& $var"
Вы написали:
$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