Start-Process в PowerShell: Интуитивный запуск процесса

Чтобы понять, почему команды Start-Process и все эти другие команды ограничены, сначала вам нужно понять, как работает типичный файл EXE. Когда файл EXE запускается, он выполняет ту операцию, для которой был разработан; он делает пинг (ping) чего-то, запускает программу-установщик (setup), ищет запись DNS (nslookup), что угодно. Для этой части и команды Start-Process и другие команды для запуска процесса отлично подходят. Это просто. Ограничение возникает, когда этот EXE возвращает какой-то вывод.

Мы можем запускать процесс в PowerShell множеством различных способов. У нас есть командлеты PowerShell Start-Process и Invoke-Expression, мы можем вызвать исполняемый файл напрямую или использовать амперсанд (&) для вызова выражений. Самый распространенный способ – использовать Start-Process, потому что, вероятно, это наиболее интуитивный способ. PowerShell известен своим интуитивным подходом к именованию команд, и Start-Process – отличный выбор. Однако эта команда имеет ограничения.

Потоки

Файл EXE, как и многие другие исполняемые файлы, не ограничивается Windows и имеет концепцию стандартных потоков. Стандартные потоки – это способ, с помощью которого исполняемые файлы возвращают вывод. Эти потоки поставляются в трех вариантах: stdin, stdout и stderr. Stdin – это поток, который может быть передан в исполняемый файл, о котором мы здесь не будем говорить. Stdout – это поток, который исполняемый файл использует для отправки обычного, не ошибочного вывода.

В терминах PowerShell stdout – это то, что возвращает Write-Output. Когда происходит ошибка (в зависимости от того, правильно ли её написал разработчик исполняемого файла), исполняемый файл должен возвращать вывод через stderr. Этот поток предназначен для возврата любой информации об ошибке.

Эти потоки позволяют пользователям этих исполняемых файлов различать типичный вывод и ошибки. Потоки существуют уже десятилетия, и поэтому Windows, а следовательно, и PowerShell, хорошо знают о них и приняли их собственные.

Коды завершения

Код завершения, код возврата или результат – это ещё одна старая концепция, которую также следуют исполняемые файлы. Код завершения – это целое число, которое позволяет исполняемому файлу сигнализировать пользователю о состоянии при завершении.

Существуют некоторые стандартные коды завершения, которым программы должны следовать, например, если код завершения равен 0, всё в порядке, код завершения 3010 означает, что требуется перезагрузка, и так далее. PowerShell может захватывать этот код завершения несколькими способами, например, с помощью автоматической переменной $LastExitCode. Коды завершения – ещё один метод связи исполняемого файла с пользователем.

Захват потоков и кодов завершения

Теперь, когда вы понимаете, с чем мы работаем, давайте воспользуемся примером. Чтобы упростить, я воспользуюсь добрым старым ping.exe. Сначала я пингану google.com, что вернёт успешный результат. Мы не используем здесь запуск процесса PowerShell.

PS C:\> ping google.com
Pinging google.com [2607:f8b0:4004:80b::200e] with 32 bytes of data:

Reply from 2607:f8b0:4004:80b::200e: time=61ms
Reply from 2607:f8b0:4004:80b::200e: time=65ms
Reply from 2607:f8b0:4004:80b::200e: time=80ms
Reply from 2607:f8b0:4004:80b::200e: time=78ms

Ping statistics for 2607:f8b0:4004:80b::200e:

Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 61ms, Maximum = 80ms, Average = 71ms

PS> $LastExitCode
0
PS> $?
True

I’ll now purposefully ping a host that doesn’t resolve a DNS name and will fail. Notice the value of $LastExitCode.

PS> ping googlllll.com

Ping request could not find host googlllll.com. Please check the name and try again.
PS> $LASTEXITCODE
1

Вы видели, что код завершения отличается, потому что результат ping был разным, но вы не видели потоки, потому что изначально PowerShell не понимает разницу, когда речь идет о выполняемых файлах. Текст, который вы видите в консоли, белый; не красный текст ошибки, который вы знаете и любите.

Мы можем захватывать потоки и перенаправлять их в файлы несколькими различными способами, например, используя > и 2>, как показано ниже. Это старомодный способ.

PS> ping googllll.com > output.msg 2> output.err
PS> Get-Content .\output.err
PS> Get-Content .\output.msg

Ping request could not find host googllll.com. Please check the name and try again.

Заметьте, что, в данном случае, даже если ping.exe возвращает код завершения 1 (как показано выше), этот вывод все равно будет направлен в stdout. Это обычное явление. Какой поток и код завершения возвращаются, полностью зависит от разработчика и может иметь много различных стилей, к сожалению.

Если вы хотите перейти по “новому пути”, вы можете использовать Start-Process и параметры -RedirectStandardError и -RedirectStandardOutput, но они все равно попадут только в файлы.

Ограничения Start-Process

Вы видите, что запуск процесса и возврат некоторого общего вывода не слишком распространен. Кроме того, PowerShell не очень хорошо обрабатывает потоки и коды завершения. Если бы я использовал Start-process для запуска ping.exe и хотел отследить, что произошло в результате, мне пришлось бы сделать что-то вроде этого каждый раз, когда я хотел бы выполнить исполняемый файл.

PS> Start-Process -FilePath -NoNewWindow ping -ArgumentList 'google.com' -RedirectStandardOutput output.txt -RedirectStandardError err.txt
PS> $LastExitCode
PS> Get-Content -Path output.txt
PS> Get-Content -Path err.txt

I still wouldn’t get my error text either!

Давайте все это исправим. Загрузите небольшую функцию, которую я создал, называемую Invoke-Process, из PowerShell Gallery.

PS> Install-Script 'Invoke-Process'
PS> . Invoke-Process.ps1

Теперь запустите ping, используя допустимый хост, и посмотрите, что произойдет.

Invoke-Process -FilePath ping -ArgumentList ‘google.com’

Приглядитесь, как мы получаем одинаковый вывод. Это хорошо!

Теперь запустите ping, используя недопустимый хост.

Invoke-Process -FilePath ping -ArgumentList ‘googlddde.com’

Обратите внимание, что мы получаем типичный красный текст ошибки, как мы бы ожидали, если что-то пошло не так. Invoke-Process использует Start-Process внутри для захвата stdout, stderr и кода завершения, а затем отправляет вывод в тот поток, куда он должен попасть. Именно так это и должно быть!

Жаль, что PowerShell не облегчил работу со потоками и кодами завершения из исполняемых файлов, но, полагаю, в наши дни мы можем добавить это сами, теперь, когда он открыт для общего доступа!

Source:
https://adamtheautomator.com/start-process/