PowerShell: почему иногда я не могу перенаправить stdout и stderr внешних методов? - PullRequest
0 голосов
/ 02 июня 2018

У меня проблемы с перенаправлением вывода из вызовов методов, отличных от .NET:

В приведенном ниже коде вы видите одно успешное перенаправление с классом .NET System.Net.Dns и два неудачных перенаправления.

Один со встроенным типом C #, а другой - скомпилированный VS .dll, который содержит только то же содержимое, что и кодовый блок $ cs_code.

Пока что мой единственный обходной путь - перехватить их вывод с помощью [Console] :: SetOut и [Console] :: SetError.

Но почему они терпят неудачу и как я могу перенаправить / захватить эти выходные данные потока?

# .NET Version                   4.7.2
# PSVersion                      5.1.16299.431                                                                                                                                                                                                                     
# PSEdition                      Desktop                                                                                                                                                                                                                           
# PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0...}                                                                                                                                                                                                           
# BuildVersion                   10.0.16299.431                                                                                                                                                                                                                    
# CLRVersion                     4.0.30319.42000                                                                                                                                                                                                                   
# WSManStackVersion              3.0                                                                                                                                                                                                                               
# PSRemotingProtocolVersion      2.3                                                                                                                                                                                                                               
# SerializationVersion           1.1.0.1    


if ($psISE) { cls }

$cs_code = @"
using System;
static public class demo
{
    static public void go()
    {       
        Console.WriteLine("***Console.WriteLine***");

        Console.Out.WriteLine("***Console.Out.WriteLine***");
        //Console.Out.Flush(); // no effect here

        Console.Error.WriteLine("***Console.Error.WriteLine***"); // no output in ISE !
        //Console.Error.Flush(); // no effect here
    }
}
"@
Add-Type -TypeDefinition $cs_code -Language CSharp

#[Console]::SetOut((New-Object IO.StringWriter))   # this would catch all stdout            
#[Console]::SetError((New-Object IO.StringWriter)) # this would catch all stderr

&{ [demo]::go() } 1> $null 2> $NULL              # no redirection, why ?

# &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL # works as expected

exit 0


Add-Type -AssemblyName 'mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' `
         -ErrorAction Stop

Add-Type -Path "c:\_ClassLibraryDemo.dll" `
         -ErrorAction Stop

&{ [MyLib.Demo]::Go() } 1> $null 2> $null // no effect here

1 Ответ

0 голосов
/ 02 июня 2018

tl; др :

  • Если вы хотите захватить вывод из внутрипроцессного кода, который производит вывод через [Console]API, вы должны использовать явные перенаправления через [Console]::SetOut() и [Console]::SetError(), метод, который вы упомянули в вопросе.

  • См. Ниже, почему это необходимо.


PowerShell позволяет захватывать / перенаправлять только стандартные stdout и stderr потоки внешних (консольных) программ ,внутри которого, в случае программ на основе .NET, Console.WriteLine() и Console.Out.WriteLine() записывают в stdout, а Console.Error.WriteLine() записывает в stderr.

При запуске в окне консоли PowerShell по умолчанию передает потоки stdout и stderr внешней программы на консоль (экран);Напротив, ISE отправляет вывод stderr в поток ошибок PowerShell [1] .

> или 1> перенаправляет вывод внешней программы (либо в файл, либо в $null).чтобы подавить его), 2> перенаправляет stderr [2] .
Кроме того, назначение вывода из внешней программы переменной захватывает ее вывод stdout и отправляет внешние программывывод по конвейеру перенаправляет свой вывод stdout в поток вывода успешных результатов PowerShell.


В отличие от этого, вы используете методы вывода типа [Console] in-process , где невозможен такой захват , потому что такие вызовы методапросто выведите на ту же консоль, на которой работает сам PowerShell, без ведома PowerShell. [3]

Вы можете отключить посредника для проверки этого поведения:

PS> [Console]::WriteLine('hi')  *> $null # Try to suppress ALL output streams.
hi  # !! Still prints to the console - PowerShell streams were bypassed.

Единственный способ (временно) перенаправить вывод [Console] в процессе - это явный вызов .SetOut() и .SetError(), как указано в вопросе.

Причина, по которой 2> $NULL в &{ [System.Net.Dns]::Resolve('bla') } 2> $NULL работает , заключается в том, что метод генерирует исключение , которое PowerShell выводит в свой поток ошибок (номер потока 2), выход которого 2> $NULL эффективно подавляет.
Обратите внимание, что, поскольку выдается исключение , 2> $NULL действует только потому, что вызов метода заключен в & { ... };в противном случае исключение также прервало бы и само перенаправление.
Однако в отношении внутрипроцессного поведения [Console] без каких-либо исключений, независимо от того, задействовано & { ... } или нет, разницы нет.


Следовательно, для того, чтобы ваш пользовательский метод C # интегрировался с потоками PowerShell - если не использовать API-интерфейсы PowerShell непосредственно в коде C # - выполните следующие действия:

  • use return для того, что должно идти в поток успеха PowerShell (номер потока 1)

  • выдает исключение для того, что должно идти в поток ошибок PowerShell (потокчисло 2), но обратите внимание, что необработанное исключение по умолчанию прервет вмещающий оператор в целом.


В качестве альтернативы скомпилируйте ваш код C # в внешняя программа , скажем, godemo.exe:

# With an external executable, redirections work as expected.
godemo.exe 1> $null 2> $null 

[1] Такое расходящееся поведение в ISE проблематично;это обсуждается в этой проблеме GitHub .

[2] Если действительно действует $ErrorActionPreference = 'Stop', любое перенаправление 2> неожиданно вызывает завершение скриптаошибка;это проблемное поведение обсуждается в этой проблеме GitHub .

[3] Методы записи в потоки stdout и stderr текущей консоли, которые вотсутствие перенаправления external , печать на экран.

...