Почему [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++. . . Хотя я понимаю, что мог бы просто изменить вывод, чтобы в нем было намного меньше символов, я надеюсь, что есть решение, позволяющее обойти это ограничение.

🤔 А знаете ли вы, что...
PowerShell может использоваться для создания отчетов и аналитических данных из различных источников.


60
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