Directly reading from pipe by the parent CMD process

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Directly reading from pipe by the parent CMD process

#16 Post by jfl » 22 Jan 2019 04:43

And here's an improved version that can create multiple pipes:

Code: Select all

@echo off
:# Rerun self in a sub-shell, to avoid breaking the original shell file handles
echo %0 | findstr :: >nul || (cmd /d /c ^""%~dp0\::\..\%~nx0" %*^" & exit /b)
goto :start

:# Pipe creation routine. Assumes all allocated handles are consecutive, with no holes.
:CreatePipe %1=PipeIn name; %2=PipeOut name; %3=Number of handles in use (optional)
set "%1=%3"			&:# PipeIn handle
if not defined %1 set "%1=3"	&:# By default, assume handles 0, 1, 2 in use
set /a "%2=%1+1"		&:# PipeOut handle
setlocal EnableDelayedExpansion
set "hIn=!%1!"			&:# PipeIn handle
set "hOut=!%2!"			&:# PipeOut handle
set /a "hTmp=hIn+3"		&:# Temporary handle, released after use
endlocal & rundll32 1>&%hOut% %hOut%>&%hTmp% | rundll32 0>&%hIn% %hIn%>&%hTmp%
exit /b

:start
call :CreatePipe P1IN P1OUT	&:# There are 3 handles in use before this call
call :CreatePipe P2IN P2OUT 5	&:# There are 5 handles in use before this call

>&%P1OUT% echo From child 1 on pipe 1	&:# Write to pipe 1
>&%P2OUT% echo From child 2 on pipe 2	&:# Write to pipe 2

<&%P1IN% set /p "READ="			&:# Read value from pipe 1
echo Read on pipe 1: %READ%

<&%P2IN% set /p "READ="			&:# Read value from pipe 2
echo Read on pipe 2: %READ%
This :CreatePipe routine version assumes that all handles in use are consecutive, with no holes.
This is true in normal cases, where only handles 0, 1, and 2 are in use.
This is also true after :CreatePipe runs: Each time, the number of handles in use grows by 2.

I'm still looking for a way to automatically count the number of handles in use.
(Or better still the exact list, even if there are holes... But I'm afraid that will be far more difficult to do.)

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Directly reading from pipe by the parent CMD process

#17 Post by dbenham » 22 Jan 2019 08:45

jfl wrote:
22 Jan 2019 04:43
I'm still looking for a way to automatically count the number of handles in use.
(Or better still the exact list, even if there are holes... But I'm afraid that will be far more difficult to do.)
Somewhere on the DosTips forum is an extensive thread where we attempted to achieve this. We got close, but I can't find the thread, and I don't remember what the original subject was :?

Dave Benham

dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: Directly reading from pipe by the parent CMD process

#18 Post by dbenham » 22 Jan 2019 09:03

dbenham wrote:
22 Jan 2019 08:45
jfl wrote:
22 Jan 2019 04:43
I'm still looking for a way to automatically count the number of handles in use.
(Or better still the exact list, even if there are holes... But I'm afraid that will be far more difficult to do.)
Somewhere on the DosTips forum is an extensive thread where we attempted to achieve this. We got close, but I can't find the thread, and I don't remember what the original subject was :?

Dave Benham
Found it, starting at the bottom of the first post at - viewtopic.php?f=3&t=2836&hilit=handle&start=45#p57866

There are multiple subsequent posts dealing with free handle detection.

That larger thread is packed with info. It is also where I first discovered the wacky mechanics behind Windows redirection that leads to permanent redirection. It also is a step in the direction of this thread that deals with permanent pipe "redirection"


Dave Benham

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Directly reading from pipe by the parent CMD process

#19 Post by jfl » 22 Jan 2019 17:50

dbenham wrote:
22 Jan 2019 09:03
Found it, starting at the bottom of the first post at - viewtopic.php?f=3&t=2836&hilit=handle&start=45#p57866
Thanks Dave for the link!
With it, I've been able to fully resolve the problem, and write a resilient :CreatePipe, that adapts to ANY previous combination of open handles. :D
(Note that I've improved upon your last script, as there are cases where you thought the redirected handle could not be identified, but in fact it can - I'll post a comment in the other thread about that.)

The following test script supports N optional arguments, specifying the handle numbers that should be reserved before attempting the pipe creations.
Passing the list of known used handles to :CreatePipe allows to optimize the use of remaining ones.
But even if there are unlisted ones used, it will still work in most cases: The only limitation then is that in some cases it will only find 3 of the 4 free handles left, and give up where success could have been possible.

Code: Select all

@echo off
:# Rerun self in a sub-shell, to avoid breaking the original shell file handles
echo %0 | findstr :: >nul || (cmd /d /c ^""%~dp0\::\..\%~nx0" %*^" & exit /b)
goto :main

:#----------------------------------------------------------------------------#
:# Handle enumeration routine - Return lists of used and free file I/O handles
:EnumHandles %1=Optional list of already allocated handles (Not including 0 1 2)
:#	     Returns %freeHandles%, %usedHandles%, %nUnknownHandles%
setlocal EnableExtensions EnableDelayedExpansion
%ECHO.D% call :EnumHandles "%~1"
set "freeHandles="	&:# List of free file handles
set "usedHandles="	&:# List of used file handles
set "nUnknownHandles=1" &:# 1=One of the used handles is actually free, but we don't know which one

2>nul ( :# Try redirecting handles 3 to 8, to see if they exist. 2>nul hides error messages if they don't.
  for /L %%h in (3,1,8) do call :TryHandleRedir 9 %%h
)
:# If none of the handles 3 to 8 are free then there are three possibilities for handle 9
:# 1. It is occupied before us, then we MAY have one free handle which is occupied by redirection of 9
:# 2. It is occupied by redirection of stderr, then we have no free handles
:# 3. It is free, then have only one free handle which is handle 9
if defined freeHandles 2>nul call :TryHandleRedir 1 9
if defined freeHandles 2>nul (
  set /a "highUsed=%usedHandles:~-1%"
  if "!highUsed!"=="9" (
    set /a "highFree=%freeHandles:~-1%"
    set "freeHandles="
    set "usedHandles="
    for /L %%h in (3,1,!highFree!) do call :TryHandleRedir !highFree! %%h
    set /a "nextUsed=!highFree!+1"
    for /L %%h in (!nextUsed!,1,9) do (
      set "usedHandles=!usedHandles! %%h"
    )
  )
)
:# If the first used handle is followed by the first free handle, then we know
:# it's the one that was used by the 2>NUL redirection. So it's actually free.
:# More generally, if all used handles before the first free one are known used
:# handles passed in %1, except for one, then that unknown one is actually free.
if defined freeHandles if defined usedHandles (
  set "firstFreeHandle=%freeHandles:~1,1%"
  %ECHO.D% firstFreeHandle=!firstFreeHandle!
  set /a "nUnknownHandles=firstFreeHandle-3"
  %ECHO.D% nUnknownHandles=!nUnknownHandles!
  set "knownUsedHandles=%~1"
  %ECHO.D% knownUsedHandles=!knownUsedHandles!
  set "unknownHandles="
  if not !firstFreeHandle!==3 (
    set /a "lastUnknowHandle=firstFreeHandle-1"
    for /l %%h in (3,1,!lastUnknowHandle!) do set "unknownHandles=!unknownHandles! %%h"
  )
  if defined knownUsedHandles for %%h in (!knownUsedHandles!) do (
    set /a "DIF=%%h-firstFreeHandle" &:# If DIF < 0 then %%h < firstFreeHandle
    %ECHO.D% DIF=!DIF!
    if "!DIF:~0,1!"=="-" (
      set /a "nUnknownHandles-=1"
      set "unknownHandles=!unknownHandles: %%h=!"
    )
  )
  %ECHO.D% unknownHandles=!unknownHandles!
  if !nUnknownHandles!==1 ( :# OK, this single used handle is actually free.
    set "nUnknownHandles=0"
    set "freeHandles=!unknownHandles!%freeHandles%"
    for %%h in ("!unknownHandles!") do set "usedHandles=!usedHandles:%%~h=!"
  )
)
:# Cleanup and return
for %%v in (freeHandles usedHandles) do if defined %%v set "%%v=!%%v:~1!" &:# Remove the head space
endlocal & (
  set "freeHandles=%freeHandles%"
  set "usedHandles=%usedHandles%"
  set "nUnknownHandles=%nUnknownHandles%"
) & (%ECHO.D% return) & exit /b

:TryHandleRedir %1=Handle to redirect; %2=Handle to duplicate; Returns 0=Used, 1=Free
break %1>&%2 && (
  set "usedHandles=!usedHandles! %2"
  (call,) &rem Clear ERRORLEVEL
) || (
  set "freeHandles=!freeHandles! %2"
)
exit /b

:#----------------------------------------------------------------------------#
:# Pipe creation routine.
:CreatePipe %1=PipeIn name; %2=PipeOut name
call :EnumHandles "%knownPipeHandles%"
%ECHO.D% freeHandles=%freeHandles%
%ECHO.D% nUnknownHandles=%nUnknownHandles%
if "%freeHandles:~6,1%"=="" exit /b 1 &:# Not enough free handles. There must be 4 or more. Ex: "0 2 4 6"
set "PROTECT="
if not %nUnknownHandles%==0 set "PROTECT=2>NUL"
for /f "tokens=1,2,4" %%a in ("%freeHandles%") do (
  set "%1=%%a"			&:# PipeIn handle
  set "%2=%%b"			&:# PipeOut handle
  set "knownPipeHandles=%knownPipeHandles% %%a %%b"
  setlocal
  set "hIn=%%a"			&:# PipeIn handle
  set "hOut=%%b"		&:# PipeOut handle
  set "hTmp=%%c"		&rem Temporary handle, released after use
)
endlocal & %PROTECT% (rundll32 1>&%hOut% %hOut%>&%hTmp% | rundll32 0>&%hIn% %hIn%>&%hTmp%)
exit /b

:#----------------------------------------------------------------------------#
:main
setlocal EnableExtensions EnableDelayedExpansion
set "ECHO.D=if "%DEBUG%"=="1" echo"
set "ARGS=%*"
set "knownPipeHandles=%ARGS%" &:# List of known used handles, passed to :CreatePipe for optimizing results
set "HIDE_HANDLES="
if defined ARGS (
  set "HIDE_HANDLES=%ARGS% "
  set "HIDE_HANDLES=!HIDE_HANDLES: =>NUL !"
)
%HIDE_HANDLES% (
call :CreatePipe P1IN P1OUT	&:# There are 3 handles in use before this call
if errorlevel 1 echo Error: Failed to find 4 free handles for pipe 1 & exit /b 1
echo P1IN=!P1IN! P1OUT=!P1OUT!

call :CreatePipe P2IN P2OUT	&:# There are 5 handles in use before this call
if errorlevel 1 echo Error: Failed to find 4 free handles for pipe 2 & exit /b 1
echo P2IN=!P2IN! P2OUT=!P2OUT!
)

%ECHO.D% Starting I/Os to pipes

>&%P1OUT% echo From child 1 on pipe 1	&:# Write to pipe 1
>&%P2OUT% echo From child 2 on pipe 2	&:# Write to pipe 2

<&%P1IN% set /p "READ="			&:# Read value from pipe 1
echo Read on pipe 1: %READ%

<&%P2IN% set /p "READ="			&:# Read value from pipe 2
echo Read on pipe 2: %READ%
Set "DEBUG=1" to enable debug messages if you want to understand what's going on.

Code: Select all

C:\JFL\Temp>test12
P1IN=3 P1OUT=4
P2IN=5 P2OUT=6
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 3
P1IN=4 P1OUT=5
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 4
P1IN=3 P1OUT=5
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 5
P1IN=3 P1OUT=4
P2IN=6 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 6
P1IN=3 P1OUT=4
P2IN=5 P2OUT=7
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 7
P1IN=3 P1OUT=4
P2IN=5 P2OUT=6
Read on pipe 1: From child 1 on pipe 1
Read on pipe 2: From child 2 on pipe 2

C:\JFL\Temp>test12 3 5
P1IN=4 P1OUT=6
Error: Failed to find 4 free handles for pipe 2

C:\JFL\Temp>

jfl
Posts: 226
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: Directly reading from pipe by the parent CMD process

#20 Post by jfl » 25 Jan 2019 11:39

Here's an updated version of my :CreatePipe test script, with the following changes:
  • Added an argument to :CreatePipe to avoid using a global variable for known handles.
  • Rewrote :EnumHandles for better reliability. For explanations, see this post.
  • Added several new testing options, and a help screen. It's now possible to specify both known and hidden handles on the command line.
  • Improved debug macros.

Code: Select all

@echo off
:# TestCreatePipe.bat - Test the :CreatePipe routine
:# 2019-01-23 JFL V2 published at https://www.dostips.com/forum/viewtopic.php?p=58707#p58707
:# 2019-01-25 JFL V3 published at https://www.dostips.com/forum/viewtopic.php?p=58729#p58729
:#                Changes:
:#		  - Added an argument to :CreatePipe to avoid using a global variable for known handles
:#		  - Rewritten :EnumHandles. For explanations, see https://www.dostips.com/forum/viewtopic.php?p=58721#p58721
:#		  - Added several new testing options, and a help screen.
:#		  - Improved debug macros.

:# Rerun self in a sub-shell, to avoid breaking the original shell file handles
echo %0 | findstr :: >nul || (cmd /d /c ^""%~dp0\::\..\%~nx0" %*^" & exit /b)
setlocal EnableExtensions EnableDelayedExpansion
call :debug.init
goto :main

:debug.init
set "IFDEBUG=if "%DEBUG%"=="1""
set "ECHO.D=%IFDEBUG% echo"
set "ECHOVARS.D=%IFDEBUG% call :echovars"
exit /b

:echovars %*=variables names
setlocal EnableExtensions EnableDelayedExpansion
for %%s in (%*) do echo set "%%s=!%%s!"
endlocal & exit /b

:# Note: In the routines below, we call "lists" batch variables with elements separated by a space.
:# The advantage of such lists is that they can be parsed, or looped on, using just the "for" command.
:# We build lists in a way that inserts an extra space ahead of the first element.
:# For performance reasons, we do not bother removing that space all along.
:# We remove it only in the final list returned in the end.
:# Also, we rely on the fact that elements in a list of handles here are all 1-digit numbers.

:#----------------------------------------------------------------------------#
:# Handle enumeration routine - Return lists of used and free file I/O handles
:EnumHandles %1=freeListVar %2=usedListVar %3=quantumLevelVar %4=knownList (Not including 0 1 2)
:#	     Returns %freeHandles%, %usedHandles%, %nUnknownHandles%
setlocal EnableExtensions EnableDelayedExpansion
%ECHO.D% call %0 %*

:# Search the top free handles
:# Our :TryRedir routine uses 2>NUL, so itself always uses the first free handle.
:# => Handle 3 will always be found in use: Either it was already, or 2>NUL will use it.
:# Using handle 3 for redirection tests forces cmd.exe to use the second free handle to save it.
:# => Handle 4 will always be found in use: Either it was already, or 3>&%%h will use it.
:# Try duplicating handles 5 to 9, to see if they exist.
set "freeHandles="	&:# List of free file handles
set "usedHandles= 3 4"	&:# List of used file handles. 3 and 4 will be in use.
for /L %%h in (5,1,9) do call :TryRedir 3 %%h freeHandles usedHandles
%ECHOVARS.D% freeHandles usedHandles

:# Search the second missed free handle, used for saving handle 3 above
if defined freeHandles ( :# This can only work by using another free handle
  set "firstFreeHandle=!freeHandles:~1,1!" &:# The first free one we've found so far
  set /a "tryLast=firstFreeHandle-1" &:# The last used handle before the first free one
  set "freeHandle="	&:# 1-element list with the free handle we missed in the above loop
  :# Again, no need to test handle 3, it's bound to be found in use.
  for /L %%h in (4,1,!tryLast!) do if not defined freeHandle call :TryRedir !firstFreeHandle! %%h freeHandle usedHandles2
  :# Move that free handle from the used list to the free list
  for %%h in ("!freeHandle!") do set "usedHandles=!usedHandles:%%~h=!"
  set "freeHandles=!freeHandle!!freeHandles!"
)
%ECHOVARS.D% freeHandles usedHandles

:# Search the first missed free handle, used for saving handle 2 above
:# If the first used handle is followed by the first free handle, then we know
:# it's the one that was used by the 2>NUL redirection. So it's actually free.
:# More generally, if all used handles before the first free one are known used
:# handles passed in %4, except for one, then that unknown one is actually free.
if defined freeHandles ( :# [Else there may actually be two unknown handles]
  set "firstFreeHandle=!freeHandles:~1,1!" &:# The first free one we've found so far
  set "knownUsedHandles=%~4" &:# This list may be empty, or partial
  set /a "nUnknownHandles=firstFreeHandle-3"
  %ECHOVARS.D% firstFreeHandle nUnknownHandles knownUsedHandles
  set "unknownHandles="
  set /a "tryLast=firstFreeHandle-1"
  for /l %%h in (3,1,!tryLast!) do set "unknownHandles=!unknownHandles! %%h"
  if defined knownUsedHandles for %%h in (!knownUsedHandles!) do (
    if %%h lss !firstFreeHandle! ( :# Then remove it from the unknown handle list
      set /a "nUnknownHandles-=1"
      set "unknownHandles=!unknownHandles: %%h=!"
    )
  )
  %ECHOVARS.D% nUnknownHandles unknownHandles
  if !nUnknownHandles!==1 (  :# OK, this single used handle is actually free.
    :# Move it from the used list to the free list
    for %%h in ("!unknownHandles!") do set "usedHandles=!usedHandles:%%~h=!"
    set "freeHandles=!unknownHandles!%freeHandles%"
    set "quantumLevel=0"
  ) else ( :# One unidentified handle in the used list is actually free.
    set "quantumLevel=1"
  )
) else ( :# No free handle found. Up to 2 used handles may actually be free.
  set "quantumLevel=2"
)
:# Cleanup and return
for %%v in (freeHandles usedHandles) do if defined %%v set "%%v=!%%v:~1!" &:# Remove the head space
endlocal & (
  set "%1=%freeHandles%"
  set "%2=%usedHandles%"
  set "%3=%quantumLevel%"
  %ECHOVARS.D% freeHandles usedHandles quantumLevel
) & (%ECHO.D% return %ERRORLEVEL%) & exit /b

:TryRedir %1=Handle to redirect; %2=Handle to duplicate; %3=Free var; %4=Used var
2>NUL ( :# Prevent error messages written to stderr from being visible.
  break %1>&%2 && (	:# The redirection succeeded.
    set "%4=!%4! %2"	&rem The handle %2 existed. Add it to the used list.
    (call,)		&rem Clear ERRORLEVEL, which might be non-0 despite success here.
  ) || (		:# The redirection failed, and an error message was written to stderr.
    set "%3=!%3! %2"	&rem The handle %2 did not exit. Add it to the free list.
  )
)
exit /b	  &:# Returns 0=Used, 1=Free

:#----------------------------------------------------------------------------#
:# Pipe creation routine - Returns the two handles selected, and updates the known handles list
:CreatePipe %1=PipeIn name; %2=PipeOut name; %3=Known handles list name (optional, list may be partial)
setlocal EnableExtensions EnableDelayedExpansion
%ECHO.D% call %0 %*
if not "%3"=="" (set "knownHandles=!%3!") else (set "knownHandles=")
call :EnumHandles freeHandles usedHandles quantumLevel "%knownHandles%"
:# Make sure there are at least 4 free handles. The list looks like: "0 2 4 6"
if %quantumLevel% gtr 1 endlocal & exit /b 1	 &:# No free handles
if "%freeHandles:~6,1%"=="" endlocal & exit /b 1 &:# Not enough free handles
:# Define an optional redirection, that plugs the hole in the usedHandles list, if any
set "FILL_HOLE="
if %quantumLevel% equ 1 set "FILL_HOLE=2>NUL"
:# Select the handles to use, in the first four free handles left
for /f "tokens=1,2,4" %%a in ("%freeHandles%") do (
  set "hIn=%%a"			&:# PipeIn handle
  set "hOut=%%b"		&:# PipeOut handle
  set "hTmp=%%c"		&rem Temporary handle, released after use
)
%ECHOVARS.D% hIn hOut hTmp
endlocal & (
  set "%1=%hIn%"		&:# PipeIn handle
  set "%2=%hOut%"		&:# PipeOut handle
  if not "%3"=="" (
    set "%3=!%3! %hIn% %hOut%"
    %ECHOVARS.D% %3
  )
) & %FILL_HOLE% (rundll32 1>&%hOut% %hOut%>&%hTmp% | rundll32 0>&%hIn% %hIn%>&%hTmp%)
%ECHO.D% return %ERRORLEVEL%
exit /b

:#----------------------------------------------------------------------------#
:# Main routine - Various options for testing :CreatePipe and :EnumHandles
:usage
echo %~nx0 - Test the :EnumHandles and :CreatePipe routines
echo Usage: %~nx0 [OPTIONS] [HANDLES]
echo Options:
echo   -?    Display this help
echo   -d    Debug mode
echo   -h    Hide subsequent handles (Don't record them as known)
echo   -l    List free handles, but don't attempt creating pipes
echo   -r    Record subsequent handles as known handles (Default)
exit /b 0

:main
set "ACTION=TestPipes"
set "VAR=knownHandles"
set "redirectHandles="
goto :get_arg
:next_arg
shift
:get_arg
if "%~1"=="" goto :start
if "%~1"=="-?" goto :usage
if "%~1"=="-a" set "ACTION=TryAll" & goto :next_arg
if "%~1"=="-d" set "DEBUG=1" & call :debug.init & goto :next_arg
if "%~1"=="-h" set "VAR=" & goto :next_arg
if "%~1"=="-l" set "ACTION=ListHandles" & goto :next_arg
if "%~1"=="-r" set "VAR=knownHandles" & goto :next_arg
if defined VAR set "%VAR%=!%VAR%! %~1"
set "redirectHandles=%redirectHandles% %1"
goto :next_arg

:start
set "USE_HANDLES="
if defined redirectHandles ( :# Generate the initial handle redirections
  set "USE_HANDLES=!redirectHandles:~1! " &:# Remove the head space, and add a trail space
  set "USE_HANDLES=!USE_HANDLES: =>NUL !"
)
%IFDEBUG% if defined USE_HANDLES (<NUL set /p "=set " & set USE_HANDLES) else (echo set USE_HANDLES=)
%ECHOVARS.D% knownHandles
goto :%ACTION%

:TryAll
%USE_HANDLES% (
  for /L %%h in (3,1,9) do (
    set "freeHandles="	&:# List of free file handles
    set "usedHandles="	&:# List of used file handles
    for /L %%i in (3,1,9) do (
      if not %%h==%%i call :TryRedir %%h %%i freeHandles usedHandles
    )
    echo Redirecting %%h finds free: !freeHandles! and used !usedHandles!
  )
)
exit /b

:ListHandles
%USE_HANDLES% (
  call :EnumHandles freeHandles usedHandles quantumLevel "!knownHandles!"
  echo Free handles: !freeHandles!
  echo Used handles: !usedHandles!
  if !quantumLevel! equ 1 (
    echo Warning: One unknown handle in the used list is actually free.
  ) else if !quantumLevel! gtr 1 (
    echo Warning: Up to two unknown handles in the used list might be free.
  )
)
exit /b

:TestPipes
%USE_HANDLES% (
  call :CreatePipe P1IN P1OUT knownHandles
  if errorlevel 1 echo Error: Failed to find 4 free handles for pipe 1 & exit /b 1
  echo P1IN=!P1IN! P1OUT=!P1OUT!
  
  call :CreatePipe P2IN P2OUT knownHandles
  if errorlevel 1 (
    echo Error: Failed to find 4 free handles for pipe 2
    set /a "P2IN=P2OUT=0"
  ) else (
    echo P2IN=!P2IN! P2OUT=!P2OUT!
  )
)

%ECHO.D% Starting I/Os to pipes

>&%P1OUT% echo From child 1 on pipe 1	&:# Write to pipe 1
if %P2OUT% neq 0 (			 :# Write to pipe 2
  >&%P2OUT% echo From child 2 on pipe 2
)

<&%P1IN% set /p "READ="			&:# Read value from pipe 1
echo Read on pipe 1: !READ!

if %P2IN% neq 0 (
  <&%P2IN% set /p "READ="		&:# Read value from pipe 2
  echo Read on pipe 2: !READ!
)

>&%P1OUT% echo From child 3 on pipe 1	&:# Write to pipe 1
if %P2OUT% neq 0 (			 :# Write to pipe 2
  >&%P2OUT% echo From child 4 on pipe 2
)

<&%P1IN% set /p "READ="			&:# Read value from pipe 1
echo Read on pipe 1: !READ!

if %P2IN% neq 0 (
  <&%P2IN% set /p "READ="		&:# Read value from pipe 2
  echo Read on pipe 2: !READ!
)
This updated version can create two pipes successfully if there is one other known used handle, whatever it is.
And it'll also succeed if there's an unknown used handle in handle 7 or 8 or 9. But It'll fail to create a second pipe if any unknown handle is in use from 3 to 6.
So if you're to use :CreatePipe (for things like batch multithreading maybe?), and use another handle for your own purposes, be sure to pass it to :CreatePipe, else make sure to use only handle 7 or 8 or 9.

Post Reply