Выполнение внешней команды из PowerShell не принимает параметр
Я выполняю следующий код, пытаясь выполнить команду 7z.exe, чтобы разархивировать файлы.
$ dir содержит пользовательский ввод пути к zip файлу, который, конечно, может содержать пробелы! И $ dir\temp2 ниже - это каталог, который я ранее создал.
Get-ChildItem -path $dir -Filter *.zip |
ForEach-Object {
$zip_path = """" + $dir + "\" + $_.name + """"
$output = " -o""$dir\temp2"""
&7z e $zip_path $output
}
Когда я выполняю его, я получаю следующее от 7z.exe:
7-Zip [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18
Processing archive: C:\test dir\test.zip
No files to process
Files: 0
Size: 0
Compressed: 50219965
Если я затем скопирую значение из $ zip_path и $ output, чтобы сформировать собственную строку cmd, это сработает!
Например:
7z e "c:\test dir\test.zip" -o"c:\test output"
Теперь я могу воспроизвести то же сообщение "нет файлов для обработки", которое появляется при выполнении в PowerShell, используя следующий cmd в cli.
7z e "c:\test dir\test.zip" o"c:\test output"
Итак, похоже, что PowerShell удаляет черту из моей опции -o. И да, это должен быть -o "C:\test output", а не -o "c:\test output" с 7z.exe, между параметром -o и его значением нет пробела.
Я в тупике. Я делаю что-то не так или я должен делать это по-другому?
Ответы
Ответ 1
Я никогда не смогу заставить Invoke-Expression (alias = &) работать правильно, поэтому я узнал, как использовать объект процесса
$7ZExe = (Get-Command -CommandType Application -Name 7z )
$7ZArgs = @(
('-o"{0}\{1}"' -f $dir, $_.Name),
('"{0}\{1}"' -f $dir, 'temp2')
)
[Diagnostics.ProcessStartInfo]$7Zpsi = New-Object -TypeName:System.Diagnostics.ProcessStartInfo -Property:@{
CreateNoWindow = $false;
UseShellExecute = $false;
Filename = $7ZExe.Path;
Arguments = $7ZArgs;
WindowStyle = 'Hidden';
RedirectStandardOutput = $true
RedirectStandardError = $true
WorkingDirectory = $(Get-Location).Path
}
$proc = [System.Diagnostics.Process]::Start($7zpsi)
$7ZOut = $proc.StandardOutput
$7ZErr = $proc.StandardError
$proc.WaitForExit()
Ответ 2
Я смог точно продублировать проблему и перепробовал множество комбинаций, избегая переключателя -o
и избегая кавычек "
, а что нет.
Но в одном из ответов упоминались Sysinternals, и я использовал Process Monitor, чтобы выяснить формат, который он передавал 7z.exe. Вещи, которые работают в простой командной строке, не работают в PowerShell таким же образом.
Например, если бы я попытался сконструировать параметры внутри PowerShell точно так же, как cmdline, это не получилось бы. Т.е., -o"C:\scripts\so\new folder"
не работает. Но если вы включите переключатель -o
в кавычки, тогда PowerShell передает строку "-oC:\scripts\so\new folder"
, которую 7z.exe
с радостью примет. Итак, я узнал, что 7z.exe будет принимать оба формата, такие как
"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" -o"C:\scripts\so\new folder"
и
"C:\Program Files\7-zip\7z.exe" e "C:\scripts\so\new folder.zip" "-oC:\scripts\so\new folder"
И оба примера содержат пробелы.
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"
Get-ChildItem -path $dir -Filter *.zip | % {
[array]$marguments = "e",$_.FullName,"-o$output";
& $pathtoexe $marguments
}
Другой подход в PowerShell V3 - отключить функцию синтаксического анализа PowerShell. Вы можете использовать команду --%
, чтобы заставить PowerShell прекратить анализ таких команд, как эта.
$zipfile = "C:\scripts\so\newfolder.zip"
$destinationfolder = "C:\scripts\so\New Folder"
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
& $pathtoexe --% e "C:\scripts\so\newfolder.zip" -o"C:\scripts\so\new folder"
Используя синтаксис --%
, вы вводите команды так же, как вы вводите их в командной строке. Я проверил эту логику, и она извлекает файлы в папку назначения.
Чтобы узнать больше о --%
, проверьте PS> help about_parsing
.
Проблема с этим подходом заключается в том, что после --%
невозможно включить переменную. Решение этой проблемы состоит в том, чтобы просто включить --%
в качестве другой строковой переменной и передать ее следующим образом. И этот подход похож на подход командной строки, который изначально не работал.
[string]$pathtoexe = "C:\Program Files\7-Zip\7z.exe"
$dir = "C:\scripts\so"
$output = "$dir\new folder"
Get-ChildItem -path $dir -Filter *.zip | % {
$zipfile = $_.FullName;
[string]$formatted = [System.String]::Concat("e ", """$zipfile"""," -o""$output""");
[string]$stopparser = '--%';
& $pathtoexe $stopparser $formatted;
}
Ответ 3
Используя превосходный Process Explorer из пакета Windows Sysinternals, я смог наблюдать очень интересное поведение. Я немного упростил вашу командную строку, как показано ниже:
dir -Path $dir -Filter *.zip |
select FullName |
% { & 7za.exe e $_ "-o$dir\tmp" }
Это фактически вызывало следующую командную строку в соответствии с Process Explorer:
C:\temp\7za.exe @{FullName="C:\temp\test.zip"} -oC:\temp\test
Указание PowerShell расширить свойство FullName
выводит его из хеш-карты и обрабатывает как обычную строку, с которой может работать 7-Zip:
dir -Path $dir -Filter *.zip |
select -ExpandProperty FullName |
% { & 7za.exe e $_ "-o$dir\tmp" }
Могут быть и другие проблемы, такие как пробелы в именах файлов, которые я действительно не рассматривал или не учитывал, но я подумал, что стоит добавить примечание, что PowerShell (в данном случае v2) не совсем прошел параметры, как вы могли ожидать.