Всякий раз, когда я использую [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 может использоваться для создания отчетов и аналитических данных из различных источников.
В пакетном файле команда 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