Почему [Console]::Error.WriteLine перехватывает выходные данные цикла For из Power Shell EncodedCommand в пакетном цикле For?

Всякий раз, когда я использую [Console]::Error.WriteLine для принудительной записи вывода в STDErr при внедрении в PowerShell EncodedCommand, который вызывается изнутри Batch Script For Loop, весь текст STDErr выглядит так, как если бы он был «загружен спереди», тогда как ВСЕ остальные функции/процессы появляются запустить ПОСЛЕ всего STDErr Вывод давно завершен визуально!

Я ЗНАЮ, что это так, потому что после отображения всех результатов PowerShell EncodedCommand остается в зависшем состоянии, но когда я Tail файл журнала, я ясно вижу код выполнения файла, который уже должен был быть выполняется, когда отображается видимый вывод! (Я также тестировал отправку STDErr в тот же файл журнала и действительно - ВСЕ словосочетания STDErr обрабатываются до завершения реальных процессов.) Является ли это стандартным поведением для перехвата вывода команды PowerShell с использованием Batch Script For Loops? Что мне не хватает?

Технические данные/примеры кода и т. д.

Рассмотрим это PowerShell Script ниже. Он преобразуется в EndodedCommand. Обратите внимание: я исключил эту ерунду из примера.

  Function Write_Log { Param([string]$textOut) write-Host $textOut; write-Output $textOut | Out-File -FilePath ("$Env:log" -replace '"','') -Append -Encoding default }
  
  #This Function - Write_StdErr is what I am seeing bizarre effects from.  No Errors.
  Function Write_StdErr { Param([string]$textOut) If ([int]$Env:isGiveStatus) {Clear-Host; [Console]::Error.WriteLine($textOut); Start-Sleep -Milliseconds 250 } }
  
  For ($i=1; $i -le 100; $i++) {
    #This line will not be parsed by Batch Script for Loop, but will be displayed to user:
    Write_StdErr "Checking for Object [$i] Values"
    
    #Do Stuff
    
    #This Line is parsed by Batch Script for Loop but not displayed to user:
    Write_Log "Current Object being checked: [$i]" $true
    
    #Do More stuff.
  }
  Exit 0
 }

Сам сценарий прекрасно завершается при запуске через For Loop в Batch Script.

  • Мой вывод при работе в For Loop ТОЛЬКО:

    Checking for Object [1] Values
    Current Object [1] is an AUDIT Object
    Checking for Object [2] Values
    Current Object [2] Data Found
    ...
    Checking for Object [8] Values
    Current Object [8] Data Found
    

После завершения последней итерации цикла в PowerShell оказывается, что данные из процесса PowerShell все еще остаются для обработки пакетным скриптом.

set "isGiveStatus=1" & REM This is to ensure the "Write_StdErr" Function sends Output.

REM This For Loop pulls data into various variables from the PowerShell's STDOut Output using this for Loop.
REM The Write_StdErr function in PowerShell is used to bypass the For Loop's processes and display the text raw to the user.
for /f "Tokens=2 Delims=[]" %%A in ('Powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand !newEncodeCMD!') do (
  cls
  ping 127.0.0.1 -n 1 >nul
  ping 127.0.0.1 -n 1 >nul
  ping 127.0.0.1 -n 1 >nul
  If "%~7"= = "CustomFunc" (
    If "%%~A"= = "" (
      Call :WriteOut "The parameter from the PowerShell Script was empty." %log% "Yes"
    ) else (
      REM Sub-Routine of Batch script provided as 8th argument like ":SubRoutineName"
      Call %~8 "%%~A"
      REM Note - Routine called has no values written to the Console - only data is logged to Log file.
      set "Powershell_Rslt=!errorlevel!"
    )
  ) else (
    set "Powershell_Rslt=!errorlevel!"
    REM Echo %%A
    set "%~8=%%A"
  )
)

РЕДАКТИРОВАТЬ

Я попытался реализовать процесс Aacini, описанный ниже, и в основном он работает прекрасно: осталось только выяснить, как обойти ограничение set /p в 1023 символа и двигаться дальше. . . Из-за этого я постоянно останавливался на 6-й итерации цикла. Подсчет подтвержден в N++. . . Хотя я понимаю, что мог бы просто изменить вывод, чтобы в нем было намного меньше символов, я надеюсь, что есть решение, позволяющее обойти это ограничение.

В пакетном файле команда for /F работает следующим образом: сначала она запускает команды в теле for и сохраняет весь вывод во временном файле. После завершения команд в теле цикла for обрабатывается временный файл. Возможным решением было бы не использовать цикл FOR/F, а направить вывод в подпрограмму, которая обрабатывает каждую строку с помощью goto ассемблированного цикла. Однако у этого решения есть и свои недостатки...

Aacini 31.07.2024 12:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
60
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В пакетном файле команда for /F работает следующим образом: сначала она запускает команды из тела for и сохраняет весь вывод во временном файле. После завершения команд в теле цикла for обрабатывается временный файл. Таким образом, ваш вывод в реальном времени как Stdout, так и Stderr не работает.

Возможным решением было бы не использовать цикл FOR/F, а направить вывод в подпрограмму, которая обрабатывает каждую строку с помощью ассемблированного цикла goto. Однако это решение также имеет свои проблемы с синхронизацией.

Чтобы избежать таких проблем с каналом, просто используйте временный файл вместо канала:

set "isGiveStatus=1" & REM This is to ensure the "Write_StdErr" Function sends Output.

::: REM This For Loop pulls data into various variables from the PowerShell's STDOut Output using this for Loop.
::: REM The Write_StdErr function in PowerShell is used to bypass the For Loop's processes and display the text raw to the user.

REM Execute PowerShell and send its output to an auxiliary file that works like a pipe
REM this is done because the direct reading of lines from a pipeline have other synchro problems
start "The Powershell Process" Powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand !newEncodeCMD!  ^>  PipeFile.txt

REM Wait a couple seconds to give the Powershell window chance to start
ping 127.0.0.1 -n 4 >nul

REM Then, read the "pipe file" in :readLines subroutine in an asynchronous way
call :readLines < PipeFile.txt

goto :EOF


:readLines

SET "line = "
SET /P "line = "
IF NOT DEFINED line exit /B

for /f "Tokens=2 Delims=[]" %%A in ("%line%") do (
  cls
  ping 127.0.0.1 -n 1 >nul
  ping 127.0.0.1 -n 1 >nul
  ping 127.0.0.1 -n 1 >nul
  If "%~7"= = "CustomFunc" (
    If "%%~A"= = "" (
      Call :WriteOut "The parameter from the PowerShell Script was empty." %log% "Yes"
    ) else (
      REM Sub-Routine of Batch script provided as 8th argument like ":SubRoutineName"
      Call %~8 "%%~A"
      REM Note - Routine called has no values written to the Console - only data is logged to Log file.
      set "Powershell_Rslt=!errorlevel!"
    )
  ) else (
    set "Powershell_Rslt=!errorlevel!"
    REM Echo %%A
    set "%~8=%%A"
  )
)

goto readLines

Возможно, команде start нужен cmd.exe следующим образом:

start "Powershell" cmd /C Powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand !newEncodeCMD!  ^>  PipeFile.txt

Ожидание запуска окна Powershell можно выполнить с помощью команды waitfor. Просмотрите waitfor /? на экране справки.

Обратите внимание, что конец цикла наступает при чтении пустой строки. Если Powershell может вывести пустую строку, этот метод необходимо изменить. Возможно, Powershell сможет вывести строку с END в конце процесса, а затем идентифицировать такую ​​конечную строку.

Если ваш код представляет собой подпрограмму, которая получает параметры (и использует параметр №7, например: If "%~7"= = "CustomFunc" (), то подпрограмму :readLines необходимо вызывать с исходными параметрами или сохранить такой параметр в переменной и использовать переменную внутри :readLines

Прежде чем я попытаюсь реализовать это - по сути, поскольку файл записывается сценарием Powershell, запущенным в отдельном процессе CMD с использованием команды Start, который может работать сам по себе, без задержки всего сценария, и до тех пор, пока этот файл постоянно обновляется, содержимое передается с использованием перенаправления STDIn при вызове подпрограммы? Я правильно это понял?

k1dfr0std 31.07.2024 16:47

Да, ты прав. Суть этого решения состоит в том, чтобы иметь два процесса: процесс Powershell, который отображает ошибки в Stderr (экране) и перенаправляет вывод в файл канала, и пакетный процесс, который считывает данные из файла канала и отображает результаты на экране...

Aacini 31.07.2024 22:23

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

k1dfr0std 01.08.2024 00:44

Так . . . При тестировании более чем в 50% случаев я получаю только около половины данных, перенаправленных на STDin, и процесс просто останавливается, даже несмотря на то, что сгенерированный выходной файл содержит все ожидаемые данные. Это потому, что данные, добавленные в файл, завершились, поэтому перенаправление на STDin просто заканчивается?

k1dfr0std 01.08.2024 07:36

Я понял, что происходит! похоже, мы находимся во власти ограничения длины set /p! stackoverflow.com/questions/48844883/… — Вскоре я отредактирую свой вопрос здесь (редактируя при этом некоторые длинные фрагменты). Теперь нужно выяснить, как разделить эту строку перед помещением ее в set /p. . . В противном случае, я думаю, это будет работать, пока я не смогу решить эту проблему.

k1dfr0std 01.08.2024 08:52

Кроме того, чего бы это ни стоило - PowerShell Write-Host при перенаправлении из CMD запишет файл в UNIX Line Endings - если вместо этого я изменю свой скрипт на Write-Output, он даже не завершит обработку первого элемента из файла.

k1dfr0std 01.08.2024 08:54

Итак, возвращаясь к моему первоначальному упоминанию о том, что я не получил все данные, в конечном итоге я удалил тонну своего пустого словоблудия, поэтому общий вывод в файл составит -LT 750 символов, если я вызову подпрограмму для обработки <PipeFile.txt, пока выполняется сценарий powershell. работает по-прежнему, независимо от того, запускается ли он даже за 1 или 2 секунды до завершения сценария PowerShell, в извлеченных данных все равно будет отсутствовать некоторая информация. Даже при использовании проверок, найденных в этом ответе, чтобы гарантировать, что может быть учтено более одной пустой строки: stackoverflow.com/a/14837162/9135863

k1dfr0std 01.08.2024 13:23

Другие вопросы по теме