Выполнение внешней команды из 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) не совсем прошел параметры, как вы могли ожидать.