$ LastExitCode = 0, но $? = False в PowerShell. Перенаправление stderr в stdout дает NativeCommandError

Почему PowerShell демонстрирует удивительное поведение во втором примере ниже?

Во-первых, пример вменяемого поведения:

PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True

Без сюрпризов. Я печатаю сообщение со стандартной ошибкой (используяcmd& APOS; secho). Я проверяю переменные$? а также$LastExitCode, Они равны Истине и 0 соответственно, как и ожидалось.

Однако, если я прошу PowerShell перенаправить стандартную ошибку на стандартный вывод по первой команде, я получу NativeCommandError:

PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<<  /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
    + CategoryInfo          : NotSpecified: (Hello from standard error :String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

$LastExitCode=0 and $?=False

Мой первый вопрос, почему NativeCommandError?

Во-вторых, почему$? Ложь, когдаcmd бежал успешно и$LastExitCode это 0? Документация PowerShellоб автоматических переменных явно не определяет$?, Я всегда предполагал, что это правда, если и только если$LastExitCode 0, но мой пример противоречит этому.

Вот как я сталкивался с таким поведением в реальном мире (упрощенно). Это действительно FUBAR. Я вызывал один сценарий PowerShell из другого. Внутренний скрипт:

cmd /c "echo Hello from standard error 1>&2"
if (! $?)
{
    echo "Job failed. Sending email.."
    exit 1
}
# Do something else

Запуск это просто как.\job.ps1, он работает нормально, и электронная почта не отправляется. Тем не менее, я вызывал его из другого скрипта PowerShell, входя в файл.\job.ps1 2>&1 > log.txt, В этом случае письмо отправляется! То, что вы делаете вне скрипта с потоком ошибок, влияет на внутреннее поведение скрипта.Observing a phenomenon changes the outcome. Это похоже на квантовую физику, а не на сценарии!

[Что интересно:.\job.ps1 2>&1 может или не взорвать в зависимости от того, где вы запускаете]

 Joey19 мая 2012 г., 17:05
Рэймонд, возможно связанный, но на самом деле не дубликат. Было бы хорошо, если бы Джеффри Сновер вмешался здесь сWord of God, хоть :-)
 Joey30 мая 2012 г., 09:21
Мэтт: Конечно, они должны использовать это в качестве аргумента. В приведенном случае это был аргументshell cmd, Если вы хотите перенаправления, вам нужна оболочка, которая понимает это.nslookup это просто команда, которая делает свое дело, но это не оболочка.
 Andy Arismendi20 мая 2012 г., 03:15
Похоже, что обходной путь - это избежать оператора перенаправления:& cmd /c "echo Hello from standard error 1>&2" 2`>`&1
 Raymond Chen19 мая 2012 г., 16:57
возможный дубликатPowershell difference between $? and $LastExitCode
 Colonel Panic19 мая 2012 г., 17:11
Я задал другой вопрос, чтобы убедиться, что я понял ожидаемое поведение, прежде чем демонстрировать неожиданное.

Ответы на вопрос(4)

(Я использую PowerShell v2.

& Apos;$?& APOS; переменная задокументирована вabout_Automatic_Variables:

$?
  Contains the execution status of the last operation

Это относится к самой последней операции PowerShell, а не к последней внешней команде, в которую вы входите$LastExitCode.

В вашем примере$LastExitCode 0, потому что последняя внешняя команда былаcmd, который былsuccessful в повторении некоторого текста. Но2>&1 вызывает сообщенияstderr для преобразования в записи ошибок в выходном потоке, который сообщает PowerShell, что во время последнего произошла ошибкаoperation, вызывая$? бытьFalse.

Чтобы проиллюстрировать это немного подробнее, рассмотрим это:

> java -jar foo; $?; $LastExitCode
Unable to access jarfile foo
False
1

$LastExitCode равно 1, потому что это был код завершения java.exe.$? Неверно, потому что последнее, что сделала оболочка, не удалось.

Но если все, что я делаю, это переключаю их:

> java -jar foo; $LastExitCode; $?
Unable to access jarfile foo
1
True

... затем$? Верно, потому что последнее, что сделала оболочка, была печать$LastExitCode хозяину, который был успешным.

В заключение:

> &{ java -jar foo }; $?; $LastExitCode
Unable to access jarfile foo
True
1

... что кажется немного нелогичным, но$? являетсяTrue теперь, потому что выполнение блока скрипта былоsuccessful, даже если команды запуска внутри нее не было.

Возвращаясь к2>&1 перенаправление ...., которое вызывает запись ошибки в выходном потоке, что и дает этот многословный блоб оNativeCommandError, Оболочка сбрасывает всю запись об ошибке.

Это может быть особенно неприятно, когда все, что вы хотите сделать, это трубаstderr and stdout вместе, чтобы они могли быть объединены в файл журнала или что-то. Кто хочет, чтобы PowerShell вмешался в их файл журнала ??? Если я сделаюant build 2>&1 >build.log, то любые ошибки, которые идут наstderr есть PowerShellnosey $ 0.02 добавлено, вместо того, чтобы получать чистые сообщения об ошибках в моем файле журнала.

Но выходной поток не являетсяtext поток! Перенаправления являются еще одним синтаксисом дляobject трубопровод. Записи об ошибках являются объектами, поэтому все, что вам нужно сделать, это преобразовать объекты в этом потоке вstrings перед перенаправлением:

От:

> cmd /c "echo Hello from standard error 1>&2" 2>&1
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd &2" 2>&1
    + CategoryInfo          : NotSpecified: (Hello from standard error :String [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Для того, чтобы:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" }
Hello from standard error

... и с перенаправлением в файл:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt
Hello from standard error

...или просто:

> cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
 Colonel Panic12 окт. 2012 г., 22:42
Эй, люби это2>&1 | %{ "$_" } Обходной путь, я буду этим пользоваться, спасибо.
 07 апр. 2015 г., 12:33
Я объединил это с проверкой $ LASTEXITCODE, в противном случае команда считается неудачной:command 2>&1 | %{ "$_" }; if ($LASTEXITCODE -ne 0) { throw "Command returned exit code $LASTEXITCODE" } else { Write-Host "Command finished successfully" }
 20 дек. 2013 г., 16:41
Это верно. Помещение объекта потока в кавычки (& quot; $ _ & quot;) преобразуется в строку.
 13 дек. 2013 г., 20:14
But, the output stream is not a text stream! Redirects are just another syntax for the object pipeline. The error records are objects, so all you have to do is convert the objects on that stream to strings before redirecting +1
 06 июн. 2014 г., 02:25
в версии 2.02>&1 | %{ "$_" } не работает, если для ErrorActionPref установлено значение «stop». Есть ли способ запустить команду и переопределить глобальное значение советника?

При запуске из ISE я устанавливаю $ ErrorActionPreference = & quot; Stop & quot; в первых строках, и это перехватывало событие "все" с добавлением * & gt; & amp; 1 в качестве параметров к вызову.

Итак, сначала у меня была эта строка:

& $exe $parameters *>&1

Что, как я уже сказал, не сработало, потому что у меня было $ ErrorActionPreference = & quot; Stop & quot; ранее в файле (или он может быть установлен глобально в профиле для пользователя, запускающего скрипт).

Поэтому я попытался обернуть его в выражение-выражение, чтобы вызвать ErrorAction:

Invoke-Expression -Command "& `"$exe`" $parameters *>&1" -ErrorAction Continue

И это тоже не работает.

Поэтому мне пришлось отступить, чтобы взломать с временным переопределением ErrorActionPreference:

$old_error_action_preference = $ErrorActionPreference

try
{
    $ErrorActionPreference = "Continue"
    & $exe $parameters *>&1
}
finally
{
    $ErrorActionPreference = $old_error_action_preference
}

Который работает на меня.

И я обернул это в функцию:

<#
    .SYNOPSIS

    Executes native executable in specified directory (if specified)
    and optionally overriding global $ErrorActionPreference.
#>
function Start-NativeExecutable
{
    [CmdletBinding(SupportsShouldProcess = $true)]
    Param
    (
        [Parameter (Mandatory = $true, Position = 0, ValueFromPipelinebyPropertyName=$True)]
        [ValidateNotNullOrEmpty()]
        [string] $Path,

        [Parameter (Mandatory = $false, Position = 1, ValueFromPipelinebyPropertyName=$True)]
        [string] $Parameters,

        [Parameter (Mandatory = $false, Position = 2, ValueFromPipelinebyPropertyName=$True)]
        [string] $WorkingDirectory,

        [Parameter (Mandatory = $false, Position = 3, ValueFromPipelinebyPropertyName=$True)]
        [string] $GlobalErrorActionPreference,

        [Parameter (Mandatory = $false, Position = 4, ValueFromPipelinebyPropertyName=$True)]
        [switch] $RedirectAllOutput
    )

    if ($WorkingDirectory)
    {
        $old_work_dir = Resolve-Path .
        cd $WorkingDirectory
    }

    if ($GlobalErrorActionPreference)
    {
        $old_error_action_preference = $ErrorActionPreference
        $ErrorActionPreference = $GlobalErrorActionPreference
    }

    try
    {
        Write-Verbose "& $Path $Parameters"

        if ($RedirectAllOutput)
            { & $Path $Parameters *>&1 }
        else
            { & $Path $Parameters }
    }
    finally
    {
        if ($WorkingDirectory)
            { cd $old_work_dir }

        if ($GlobalErrorActionPreference)
            { $ErrorActionPreference = $old_error_action_preference }
    }
}
 08 дек. 2015 г., 16:57
Существует активная ошибка, открытая об этом поведении с Microsoft с 2011 года:connect.microsoft.com/PowerShell/feedback/details/645954, Лучшее решение для перенаправления stderr и предотвращения ошибок - использовать команду cmd / c & quot; 2 & gt; & amp; 1 & quot;
 24 дек. 2017 г., 19:38
Увы, эта ссылка для подключения кажется мертвой ....
Решение Вопроса

PowerShell для обработки ошибок, поэтому, скорее всего, она никогда не будет исправлена. Если ваш сценарий воспроизводится только с другими сценариями PowerShell, вы в безопасности. Однако, если ваш скрипт взаимодействует с приложениями из большого мира, эта ошибка может укусить.

PS> nslookup microsoft.com 2>&1 ; echo $?

False

Попался! Однако после некоторого болезненного царапания вы никогда не забудете урок.

Use ($LastExitCode -eq 0) instead of $?
 17 апр. 2016 г., 00:31
Также совет должен бытьUse ($LastExitCode -eq 0) instead of $? for native commands, При запуске сценариев / командлетов PowerShell,$? это путь -$LastExitCode даже не будет установлен, если сценарий не содержит явногоExit-PSSession вызов.
 22 мар. 2016 г., 19:04
@ piers7 или вы можете записать stderr в переменную (stackoverflow.com/questions/24222088/…), а затем проверить оба$LastExitCode а также$stderr.
 18 нояб. 2015 г., 01:29
Разумеется, обратное - вы можете получить приложения (особенно консольные сценарии), которые пишут в stdErr, ноdont установить код выхода, и в этом случае выmust обнаружить эти ошибки, используя $ ?. Так что ответ ... это зависит.

(Note: This is mostly speculation; I rarely use many native commands in PowerShell and others probably know more about PowerShell internals than me)

Я полагаю, вы обнаружили несоответствие в консоли хоста PowerShell.

If PowerShell picks up stuff on the standard error stream it will assume an error and throw a NativeCommandError. PowerShell can only pick this up if it monitors the standard error stream. PowerShell ISE has to monitor it, because it is no console application and thus a native console application has no console to write to. This is why in the PowerShell ISE this fails regardless of the 2>&1 redirection operator. The console host will monitor the standard error stream if you use the 2>&1 redirection operator because output on the standard error stream has to be redirected and thus read.

Я предполагаю, что консольный хост PowerShell является ленивым и просто передает консольным командам консоль, если ей не требуется выполнять какую-либо обработку их вывода.

Я действительно считаю, что это ошибка, потому что PowerShell ведет себя по-разному в зависимости от хост-приложения.

 20 мая 2012 г., 00:08
Просто искал спецификацию для этого. Ничего, что могло бы объяснить поведение, которое вы видите. Также немного неясно, что именно является ошибкой для$?.
 19 мая 2012 г., 17:48
Я согласен с оценкой Джо и считаю, что поведение PowerShell.exe должно быть улучшено.
 Colonel Panic19 мая 2012 г., 23:15
Есть ли какие-нибудь документы по Powershell, касающиеся пункта 1? ИМХО, это плохое проектное решение, многие программы выводят отладочную информацию на стандартную ошибку, это не означает, что они потерпели неудачу.
 Colonel Panic19 мая 2012 г., 23:00
Вы говорите мне в PowershellISEОбе мои команды взрывают. Теперь я в замешательстве!
 20 мая 2012 г., 02:26
Я сделал быстрый скрипт на python, который просто пишет сообщение в stderr консоли и выполнил его сpowershell.exe а также$? было правдой. Сценарий Python был просто импорт системы, а затемsys.stderr.write("Hi There\n"), Ну, это похоже$? Ложно, только если собственный код завершения команды не равен нулю.

Ваш ответ на вопрос