您可以使用 PowerShell 作为批处理子进程来创建命名管道,并使用它在 Windows 上的批处理和其他批处理/子系统之间构建 IPC。
这是一个使用快速着色批处理输出的示例:PowerShell as a subprocess through named pipe
编辑:将其设置为消息模式并使其对@Bbb 更具可读性
EDIT2:添加缺少的 NOP 和安全警告。
小心安全问题。使用命名管道的凭据并注意 fd 5 可由服务器上的任何控制台/进程读取。使用真正的双向管道或其他命名管道而不是 fd 5 来避免这种情况。不要在野外使用这个“原样”。
通过 PowerShell 创建“双向”命名管道的示例批处理“代码”:
::
:: Launch a PowerShell child process in the background linked to the console and
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
:: [ Name] : A name for the named pipe.
:: Return :
:: 0 if the child PowerShell has been successfully launched and the named pipe is available.
:: 1 if it fails.
:: 3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
SET LOCALV_NAME=
IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
powershell -command "$_" 2>&1 >NUL
IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools.
REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous !
(ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO Try { & ^
ECHO While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO Try { & ^
ECHO If ^^^($msg.Length -gt 0^^^) { & ^
ECHO $disp='Server got :'+$msg; & ^
ECHO Write-Host^^^($disp^^^); & ^
ECHO $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO $reply_flag=0; & ^
ECHO While^^^($reply_flag -eq 0^^^) { & ^
ECHO Try { & ^
ECHO Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO $reply_flag=1; & ^
ECHO } Catch [System.IO.IOException] { & ^
ECHO # Deal as you whish with potential errors & ^
ECHO }; & ^
ECHO }; & ^
ECHO } & ^
ECHO } Finally { & ^
ECHO $npipeServer.Disconnect^^^(^^^); & ^
ECHO $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO } & ^
ECHO }; & ^
ECHO } Catch [System.IO.IOException] { & ^
ECHO # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO } & ^
ECHO } Finally { & ^
ECHO If ^^^($npipeServer^^^) { & ^
ECHO $npipeServer.Dispose^^^(^^^); & ^
ECHO } & ^
ECHO # We close the fd & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
SET /A LOCALV_TRY=20 >NUL
:LaunchPowerShellSubProcessPipe_WaitForPipe
powershell -nop -c "& {sleep -m 50}"
SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\\.\pipe\!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
IF "!LOCALV_TRY!" == "0" EXIT /B 1
REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
powershell -nop -c "& {sleep -m 250}"
EXIT /B 0
和一个实现的例子:
@echo off
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 (
ECHO Can't use extensions
EXIT /B 1
)
::
SETLOCAL ENABLEDELAYEDEXPANSION
IF ERRORLEVEL 1 (
ECHO Can't use expansion
EXIT /B 1
)
REM We create 'MyNamedPipe'
CALL:LaunchPowerShellSubProcessPipe "MyNamedPipe"
SET "LOCALV_RET=!ERRORLEVEL!"
IF NOT "!LOCALV_RET!" == "0" (
ECHO Failed to create the named pipe... Exit code from LaunchPowerShellSubProcessPipe : !LOCALV_RET!
EXIT /B 1
)
REM Sending something through the pipe to the PowerShell subprocess that can be used as an IPC gate for other processes
ECHO Batch send hi to the PowerShell subprocess
ECHO Hi from the batch>\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
REM At this time, the PowerShell subprocess will take few cycles to write on the console and then write the response through fd 5.
REM We can check the pipe availability in the same way it's done in :LaunchPowerShellSubProcessPipe, but a direct read will suffice here.
REM If we've read from the pipe, it should have ben a blocked read as we've created a synchronous pipe.
REM As we read reply from fd 5, it's not synchronous, so we need to wait until we get the reply.
:WaitForReply
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
ECHO Batch got a reply from SubProcess : %%R
SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply
ECHO caller is happy >\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply2
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
ECHO Batch got a 2nd reply from SubProcess : %%R
SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply2
REM Waiting one second for visible time delta
powershell -nop -c "& {sleep -m 1000}"
ECHO This is my leave. >\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply3
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
ECHO Batch got a 3th reply from SubProcess : %%R
SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply3
REM We can now tell goodbye to the PowerShell subprocess
ECHO QUIT>\\.\pipe\MyNamedPipe
EXIT /B 0
::
:: Launch a PowerShell child process in the background linked to the console and
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
:: [ Name] : A name for the named pipe.
:: Return :
:: 0 if the child PowerShell has been successfully launched and the named pipe is available.
:: 1 if it fails.
:: 3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
SET LOCALV_NAME=
IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
powershell -command "$_" 2>&1 >NUL
IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools.
REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous !
(ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO Try { & ^
ECHO While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO Try { & ^
ECHO If ^^^($msg.Length -gt 0^^^) { & ^
ECHO $disp='Server got :'+$msg; & ^
ECHO Write-Host^^^($disp^^^); & ^
ECHO $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO $reply_flag=0; & ^
ECHO While^^^($reply_flag -eq 0^^^) { & ^
ECHO Try { & ^
ECHO Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO $reply_flag=1; & ^
ECHO } Catch [System.IO.IOException] { & ^
ECHO # Deal as you whish with potential errors & ^
ECHO }; & ^
ECHO }; & ^
ECHO } & ^
ECHO } Finally { & ^
ECHO $npipeServer.Disconnect^^^(^^^); & ^
ECHO $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO } & ^
ECHO }; & ^
ECHO } Catch [System.IO.IOException] { & ^
ECHO # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO } & ^
ECHO } Finally { & ^
ECHO If ^^^($npipeServer^^^) { & ^
ECHO $npipeServer.Dispose^^^(^^^); & ^
ECHO } & ^
ECHO # We close the fd & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
SET /A LOCALV_TRY=20 >NUL
:LaunchPowerShellSubProcessPipe_WaitForPipe
powershell -nop -c "& {sleep -m 50}"
SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\\.\pipe\!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
IF "!LOCALV_TRY!" == "0" EXIT /B 1
REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
powershell -nop -c "& {sleep -m 250}"
EXIT /B 0
预期输出:
批量发送 hi 到 PowerShell 子进程
Batch 得到了 SubProcess 的回复 : 回复 Hi from the batch,我在这里,时间是 14:15:34
Batch 收到 SubProcess 的第二个回复:回复调用者很高兴,我在这里,时间是 14:15:34
Batch 收到了来自 SubProcess 的第三个回复:回复 This is my leave。, 我在这里,时间是 14:15:35