Всякий раз, когда я использую [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 обрабатывается временный файл. Таким образом, ваш вывод в реальном времени как 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
при вызове подпрограммы? Я правильно это понял?
Да, ты прав. Суть этого решения состоит в том, чтобы иметь два процесса: процесс Powershell, который отображает ошибки в Stderr (экране) и перенаправляет вывод в файл канала, и пакетный процесс, который считывает данные из файла канала и отображает результаты на экране...
Хорошо, я постараюсь найти способ реализовать это, чтобы увидеть, чем отличается производительность. С нетерпением жду возможности увидеть эту работу в действии. Я сообщу вам, как все пройдет, как только смогу. Как всегда, очень ценю ваш вклад.
Так . . . При тестировании более чем в 50% случаев я получаю только около половины данных, перенаправленных на STDin
, и процесс просто останавливается, даже несмотря на то, что сгенерированный выходной файл содержит все ожидаемые данные. Это потому, что данные, добавленные в файл, завершились, поэтому перенаправление на STDin
просто заканчивается?
Я понял, что происходит! похоже, мы находимся во власти ограничения длины set /p
! stackoverflow.com/questions/48844883/… — Вскоре я отредактирую свой вопрос здесь (редактируя при этом некоторые длинные фрагменты). Теперь нужно выяснить, как разделить эту строку перед помещением ее в set /p
. . . В противном случае, я думаю, это будет работать, пока я не смогу решить эту проблему.
Кроме того, чего бы это ни стоило - PowerShell Write-Host
при перенаправлении из CMD запишет файл в UNIX Line Endings
- если вместо этого я изменю свой скрипт на Write-Output
, он даже не завершит обработку первого элемента из файла.
Итак, возвращаясь к моему первоначальному упоминанию о том, что я не получил все данные, в конечном итоге я удалил тонну своего пустого словоблудия, поэтому общий вывод в файл составит -LT
750 символов, если я вызову подпрограмму для обработки <PipeFile.txt
, пока выполняется сценарий powershell. работает по-прежнему, независимо от того, запускается ли он даже за 1 или 2 секунды до завершения сценария PowerShell, в извлеченных данных все равно будет отсутствовать некоторая информация. Даже при использовании проверок, найденных в этом ответе, чтобы гарантировать, что может быть учтено более одной пустой строки: stackoverflow.com/a/14837162/9135863
В пакетном файле команда
for /F
работает следующим образом: сначала она запускает команды в теле for и сохраняет весь вывод во временном файле. После завершения команд в теле цикла for обрабатывается временный файл. Возможным решением было бы не использовать цикл FOR/F, а направить вывод в подпрограмму, которая обрабатывает каждую строку с помощьюgoto
ассемблированного цикла. Однако у этого решения есть и свои недостатки...