Feature Request: wt.exe to return PID of new child process on Standard Output #19462

Closed
opened 2026-01-31 06:44:04 +00:00 by claunia · 7 comments
Owner

Originally created by @martin-rueegg on GitHub (Feb 25, 2023).

Description of the new feature/enhancement

In some scenarios it is important to be able to store the PID of a newly created shell (to kill it or wait for its termination asynchronously, to name two).

When launching a new wt.exe instance in order to run a command in another tab or split-pane, the console is actually instantiated from the already running instance. Hence, the calling instance terminates and the new console is not a child of the invoking console.

While it makes no sense to keep all the WT instances alive, it would be great if the PID of the newly created console could be returned on standard output, upon request.

Proposed technical implementation details (optional)

Add an additional option that is used when launching a new command, console, tab, or pane, e.g. --return-pid that would return the PID of the invoked child process, if wt.exe terminates.

Originally created by @martin-rueegg on GitHub (Feb 25, 2023). # Description of the new feature/enhancement <!-- A clear and concise description of what the problem is that the new feature would solve. Describe why and how a user would use this new functionality (if applicable). --> In some scenarios it is important to be able to store the PID of a newly created shell (to kill it or wait for its termination asynchronously, to name two). When launching a new `wt.exe` instance in order to run a command in another tab or split-pane, the console is actually instantiated from the already running instance. Hence, the calling instance terminates and the new console is not a child of the invoking console. While it makes no sense to keep all the WT instances alive, it would be great if the PID of the newly created console could be returned on standard output, upon request. # Proposed technical implementation details (optional) <!-- A clear and concise description of what you want to happen. --> Add an additional option that is used when launching a new command, console, tab, or pane, e.g. `--return-pid` that would return the PID of the invoked child process, if `wt.exe` terminates.
Author
Owner

@DHowett commented on GitHub (Feb 27, 2023):

This might be better-served by wt --wait as proposed in #8856; would you agree? After all, wt might spawn more than one process (in the case of wt split-pane x; split-pane y) and then we're getting dangerously close to specifying a protocol rather than simply returning a pid.

@DHowett commented on GitHub (Feb 27, 2023): This might be better-served by `wt --wait` as proposed in #8856; would you agree? After all, `wt` might spawn more than one process (in the case of `wt split-pane x; split-pane y`) and then we're getting dangerously close to specifying a _protocol_ rather than simply returning a pid.
Author
Owner

@martin-rueegg commented on GitHub (Feb 27, 2023):

Thanks, @DHowett, for looking into this.

I do understand where you're coming from regarding specifying a protocol. However, most likely --wait will not work, because wt.exe (new) might hand over the execution of the child process to an already existing wt.exe (existing), as described by @zadjii-msft in this comment. In which case the child.exe would not be an actual child of wt.exe (new), but wt.exe (existing) in terms of process tree.

This was actually the reason why I was asking to return the PIDs. Because in my case it is about the process tree. (Specifically I am using gsudo and would want to inherit the credential cache to the newly created child.exe.

So the solution proposed here, would allow the user to have further control on the processes, like retrieving information with tasklist /fi "PID eq 1234" /V or killing it with taskkill /pid 1234

In fact, returning the PID(s) might also be a solution for the other use case, as it is quite simple to then wait for those processes to end:

 :waitforpid
 tasklist /fi "pid eq %1" 2>nul | find "%1" >nul
 if %ERRORLEVEL%==0 (
   timeout /t 2 /nobreak >nul
   goto :waitforpid
 )
 goto :eof

(Credits: PA. @ https://stackoverflow.com/a/22559493/3102305)

Of course it could be an option that wt.exe --wait does that job for the user and returns only, when all the "child" PIDs are gone. But both concepts could rely on the same internal implementation of getting the child PIDs from wt.exe (existing).

Regarding the protocol: would it not make sense to return a semi-colon separated list of PIDs, where the number of semicolons match the number of semicolons in the command line arguments to wt.exe (new) and each PID corresponds to the respective subcommand?

@martin-rueegg commented on GitHub (Feb 27, 2023): Thanks, @DHowett, for looking into this. I do understand where you're coming from regarding specifying a _protocol_. However, most likely `--wait` will not work, because `wt.exe (new)` might hand over the execution of the child process to an already existing `wt.exe (existing)`, as described by @zadjii-msft in [this comment](https://github.com/microsoft/terminal/issues/8856#issuecomment-766953843). In which case the `child.exe` would not be an actual child of `wt.exe (new)`, but `wt.exe (existing)` in terms of process tree. This was actually the reason why I was asking to return the PIDs. Because in my case it is about the process tree. (Specifically I am using [gsudo](https://github.com/gerardog/gsudo) and would want to inherit the credential cache to the newly created `child.exe`. So the solution proposed here, would allow the user to have further control on the processes, like retrieving information with `tasklist /fi "PID eq 1234" /V` or killing it with `taskkill /pid 1234` In fact, returning the PID(s) might also be a solution for the other use case, as it is quite simple to then wait for those processes to end: ```batch :waitforpid tasklist /fi "pid eq %1" 2>nul | find "%1" >nul if %ERRORLEVEL%==0 ( timeout /t 2 /nobreak >nul goto :waitforpid ) goto :eof ``` (Credits: [PA.](https://stackoverflow.com/users/30447/pa) @ https://stackoverflow.com/a/22559493/3102305) Of course it could be an option that `wt.exe --wait` does that job for the user and returns only, when all the "child" PIDs are gone. But both concepts could rely on the same internal implementation of getting the child PIDs from `wt.exe (existing)`. Regarding the _protocol_: would it not make sense to return a semi-colon separated list of PIDs, where the number of semicolons match the number of semicolons in the command line arguments to `wt.exe (new)` and each PID corresponds to the respective subcommand?
Author
Owner

@zadjii-msft commented on GitHub (Mar 1, 2023):

We've actually been having some discussions offline about what wt --wait means after #14843 and I think we've got a lot of new ideas that would make this work after all. I think the thing we're considering is that wt --wait would wait for all the children spawned by that commandline to exit, without necessarily waiting on the root terminal process (which will likely outlive those processes).

I'd much rather do that than try and return something on standard out.

But also, might it not just be easier to spawn cmd.exe, and just let the "default terminal" functionality (in Windows 11) parent the client to the Terminal of the user's choice? So, you're not waiting on the Terminal process, but the client app you actually care about/?

@zadjii-msft commented on GitHub (Mar 1, 2023): We've actually been having some discussions offline about what `wt --wait` means after #14843 and I think we've got a lot of new ideas that _would_ make this work after all. I think the thing we're considering is that `wt --wait` _would_ wait for all the children spawned by that commandline to exit, without necessarily waiting on the root terminal process (which will likely outlive those processes). I'd much rather do that than try and return something on standard out. But also, might it not just be easier to spawn `cmd.exe`, and just let the "default terminal" functionality (in Windows 11) parent the client to the Terminal of the user's choice? So, you're not waiting on the Terminal process, but the client app you actually care about/?
Author
Owner

@martin-rueegg commented on GitHub (Mar 2, 2023):

Thank you, @zadjii-msft for your reply and your considerations.

We've actually been having some discussions offline about what wt --wait means after #14843 and I think we've got a lot of new ideas that would make this work after all.

Sounds promising!

I think the thing we're considering is that wt --wait would wait for all the children spawned by that commandline to exit, without necessarily waiting on the root terminal process (which will likely outlive those processes).

Makes total sense. But it's a different use case.

I'd much rather do that than try and return something on standard out.

What is the issue about returning something on the standard out? I assume, normally there would never be output on STDOUT, but only on STDERR, e.g. in case a subcomand was not found. Hence, the STDOUT could easily be used without breaking backwards compatibility, as there would only be output if the user explicitly asks for the PIDs to be returned using --return-pid.

But also, might it not just be easier to spawn cmd.exe, and just let the "default terminal" functionality (in Windows 11) parent the client to the Terminal of the user's choice? So, you're not waiting on the Terminal process, but the client app you actually care about/?

Well, the WT has the possibility of having multiple panes in one tab, which is amazing. In one example I'm using that functionality to show in one tab

  • the main process in a big pane, and
  • the log files in a smaller pane.

Use-case

Let's assume, I have a script.bat that starts a httpd.exe in foreground-mode, and one tail.exe -f /path/to/error.log. Let's further assume that tail.exe should be terminated, once httpd.exe exits (example 1). That script contains the following line to start these children:

wt.exe new-tab httpd.exe; split-pane -H --size .2 tail.exe -f /path/to/error.log

Why --wait wouldn't do

The --wait parameter wouldn't make any sense here, because I would not know which process needs to be killed. Even if I would run the split-pane from within the new-tab-session's commandline, I still would not know the PID of the tail.exe.

Of course, this is a simplified example. But let's assume the path to the log file contains the PID of httpd.exe (example 2). Or that the second command is not just a tail.exe, but maybe something that needs to kill the httpd.exe if a certain entry appears in the log (example 3), or a certain file is appearing in the file system (e.g. /path/to/request.httpd.stop). How would these processes know the PID of httpd.exe (unless the httpd.exe itself creates a PID-file)?

However, with the --return-pid I could do something like:

Example 1: Kill commandline2 once commandline1 has ended

REM  #### Example 1: Kill commandline2 once commandline1 has ended
REM
setlocal enableextensions enabledelayedexpansion

set /a count = 0

REM run the command and save the returned PIDs
for /f "tokens=; usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe; split-pane -H --size .2 tail.exe -f /path/to/error.log`) do (
	set /a count += 1
	if !count! == 1 set /a httpd_pid = %%f
	if !count! == 2 set /a tail_pid = %%f
)

REM wait for as long httpd.exe is running
:waitforhttpd
tasklist /fi "pid eq %httpd_pid%" 2>nul | find "%httpd_pid%" >nul
if %ERRORLEVEL%==0 (
	timeout /t 2 /nobreak >nul
	goto :waitforhttpd
)

REM kill tail.exe
taskkill /PID %tail_pid%

endlocal

Example 2: Show log file with PID of commandline1

As such, it would also allow my other example, with the PID of httpd.exe in the path of the log file to be traced:

REM  #### Example 2: Show log file with PID of commandline1
REM
REM start httpd.exe and save PID
set /a httpd_pid = 0
for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe 2>NUL`) do (
	if not "%%f" == "" set /a httpd_pid = %%f
)

REM now run tail.exe using the PID from httpd.exe in the log file path
if not "%httpd_pid%" == "0" wt.exe split-pane -H --size .2 tail.exe -f /path/to/error.%httpd_pid%.log

No protocol

On order to avoid the protocol in the STDOUT, --return-pid could also be limited to wt.exe calls that do only have one single command. Hence, wt new-tab --return-pid commandline1; new-tab commandline2 would cause a syntax error and fail. Such a command would have to be split into wt new-tab --return-pid commandline1 & wt --return-pid new-tab commandline2. Then there would only ever one integer or an empty string on STDOUT, if --return-pid is set.

My previous example 1 would then have to be slightly changed:

REM  #### Example 1b: Kill commandline2 once commandline1 has ended
REM
...
REM run the command and save the returned PIDs
for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe 2>NUL`) do (
	if not "%%f" == "" set /a httpd_pid = %%f
)
for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid split-pane -H --size .2 tail.exe -f /path/to/error.log 2>NUL`) do (
	if not "%%f" == "" set /a tail_pid = %%f
)
...

Conclusion

  • I specifically would like to run the processes in WT, thanks to its possibility to split tabs into panes and to "load" those panes with my commands
  • --wait would not help in my use-cases
  • --return-pid, however, would allow for great flexibility
  • at a later point, maybe that PID could even be used to identify a specific pane, e.g. to give it the focus with wt.exe focus-tab --child-pid %pid%
@martin-rueegg commented on GitHub (Mar 2, 2023): Thank you, @zadjii-msft for your reply and your considerations. > We've actually been having some discussions offline about what `wt --wait` means after #14843 and I think we've got a lot of new ideas that _would_ make this work after all. Sounds promising! > I think the thing we're considering is that `wt --wait` _would_ wait for all the children spawned by that commandline to exit, without necessarily waiting on the root terminal process (which will likely outlive those processes). Makes total sense. But it's a different use case. > I'd much rather do that than try and return something on standard out. What is the issue about returning something on the standard out? I assume, normally there would never be output on STDOUT, but only on STDERR, e.g. in case a subcomand was not found. Hence, the STDOUT could easily be used without breaking backwards compatibility, as there would only be output if the user explicitly asks for the PIDs to be returned using `--return-pid`. > But also, might it not just be easier to spawn `cmd.exe`, and just let the "default terminal" functionality (in Windows 11) parent the client to the Terminal of the user's choice? So, you're not waiting on the Terminal process, but the client app you actually care about/? Well, the WT has the possibility of having multiple panes in one tab, which is amazing. In one example I'm using that functionality to show in one tab - the main process in a big pane, and - the log files in a smaller pane. ### Use-case Let's assume, I have a `script.bat` that starts a `httpd.exe` in foreground-mode, and one `tail.exe -f /path/to/error.log`. Let's further assume that `tail.exe` should be terminated, once `httpd.exe` exits (**example 1**). That script contains the following line to start these children: ``` cmd wt.exe new-tab httpd.exe; split-pane -H --size .2 tail.exe -f /path/to/error.log ``` #### Why `--wait` wouldn't do The `--wait` parameter wouldn't make any sense here, because I would not know which process needs to be killed. Even if I would run the `split-pane` from within the `new-tab`-session's commandline, I still would not know the PID of the `tail.exe`. Of course, this is a simplified example. But let's assume the path to the log file contains the PID of `httpd.exe` (**example 2**). Or that the second command is not just a `tail.exe`, but maybe something that needs to kill the `httpd.exe` if a certain entry appears in the log (**example 3**), or a certain file is appearing in the file system (e.g. `/path/to/request.httpd.stop`). How would these processes know the PID of `httpd.exe` (unless the `httpd.exe` itself creates a PID-file)? However, with the `--return-pid` I could do something like: #### Example 1: Kill commandline2 once commandline1 has ended ``` batch REM #### Example 1: Kill commandline2 once commandline1 has ended REM setlocal enableextensions enabledelayedexpansion set /a count = 0 REM run the command and save the returned PIDs for /f "tokens=; usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe; split-pane -H --size .2 tail.exe -f /path/to/error.log`) do ( set /a count += 1 if !count! == 1 set /a httpd_pid = %%f if !count! == 2 set /a tail_pid = %%f ) REM wait for as long httpd.exe is running :waitforhttpd tasklist /fi "pid eq %httpd_pid%" 2>nul | find "%httpd_pid%" >nul if %ERRORLEVEL%==0 ( timeout /t 2 /nobreak >nul goto :waitforhttpd ) REM kill tail.exe taskkill /PID %tail_pid% endlocal ``` #### Example 2: Show log file with PID of commandline1 As such, it would also allow my other example, with the PID of `httpd.exe` in the path of the log file to be traced: ``` batch REM #### Example 2: Show log file with PID of commandline1 REM REM start httpd.exe and save PID set /a httpd_pid = 0 for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe 2>NUL`) do ( if not "%%f" == "" set /a httpd_pid = %%f ) REM now run tail.exe using the PID from httpd.exe in the log file path if not "%httpd_pid%" == "0" wt.exe split-pane -H --size .2 tail.exe -f /path/to/error.%httpd_pid%.log ``` ### No protocol On order to avoid the _protocol_ in the STDOUT, `--return-pid` could also be limited to `wt.exe` calls that do only have one single command. Hence, `wt new-tab --return-pid commandline1; new-tab commandline2` would cause a syntax error and fail. Such a command would have to be split into `wt new-tab --return-pid commandline1 & wt --return-pid new-tab commandline2`. Then there would only ever one integer or an empty string on STDOUT, if `--return-pid` is set. My previous **example 1** would then have to be slightly changed: ``` batch REM #### Example 1b: Kill commandline2 once commandline1 has ended REM ... REM run the command and save the returned PIDs for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid new-tab httpd.exe 2>NUL`) do ( if not "%%f" == "" set /a httpd_pid = %%f ) for /f "tokens=* usebackq" %%f in (`wt.exe --return-pid split-pane -H --size .2 tail.exe -f /path/to/error.log 2>NUL`) do ( if not "%%f" == "" set /a tail_pid = %%f ) ... ``` ### Conclusion - I specifically would like to run the processes in WT, thanks to its possibility to split tabs into panes and to "load" those panes with my commands - `--wait` would not help in my use-cases - `--return-pid`, however, would allow for great flexibility - at a later point, maybe that PID could even be used to identify a specific pane, e.g. to give it the focus with `wt.exe focus-tab --child-pid %pid%`
Author
Owner

@zadjii-msft commented on GitHub (Sep 6, 2023):

Sorry for leaving you on read for such a long time here. This is a solid writeup, with good scenarios. I still dunno how I feel about returning the PIDs raw on stdout. It's specifically good for this use case, sure. But it is also totally unprecedented territory here. We've got nothing else that uses wt's stdout at this time, and no grand plans for it yet. That actually makes me more reluctant to do something like this, specifically because it means if we ever do have some grand plan for stdout, we'd have to square it with this feature.

You bring up a point about --wait specifically not working for scenarios where you're doing a bunch of subcommands. I'm not sure I totally agree here. Although it might be a bit less ergonomic, you could always do something like

wt -w 0 --wait new-tab httpd.exe
wt -w 0 --wait split-pane -H --size .2 tail.exe -f /path/to/error.log

Basically, break the multiple subcommands into separate wt calls. The -w 0 is a flag to wt to tell it "run this command in the current/most recent window". (You could always use something like wt -w httpd or something, to have a specific "httpd" window).

I think when it comes to Example 2, now we're really pushing the boundaries of raw shell scripting here. This super feels like the kind of place where a more powerful language (python, go, heck even pwsh) might be called for.

As far as this request particularly, I think I'm inclined to just reject it in this form. There's probably better ways of going about doing what you're looking for. But returning the pids on stdout is probably not the right way.

@zadjii-msft commented on GitHub (Sep 6, 2023): Sorry for leaving you on read for such a long time here. This is a solid writeup, with good scenarios. I still dunno how I feel about returning the PIDs raw on stdout. It's specifically good for this use case, sure. But it is also totally unprecedented territory here. We've got nothing else that uses `wt`'s stdout at this time, and no grand plans for it yet. That actually makes me _more_ reluctant to do something like this, specifically because it means if we ever do have some grand plan for stdout, we'd have to square it with this feature. You bring up a point about `--wait` specifically not working for scenarios where you're doing a bunch of subcommands. I'm not sure I _totally_ agree here. Although it might be a bit less ergonomic, you could always do something like ``` wt -w 0 --wait new-tab httpd.exe wt -w 0 --wait split-pane -H --size .2 tail.exe -f /path/to/error.log ``` Basically, break the multiple subcommands into separate `wt` calls. The [`-w 0` is a flag to `wt`](https://learn.microsoft.com/en-us/windows/terminal/command-line-arguments?tabs=windows#options-and-commands) to tell it "run this command in the current/most recent window". (You could always use something like `wt -w httpd` or something, to have a specific "httpd" window). I think when it comes to Example 2, now we're really pushing the boundaries of raw shell scripting here. This super feels like the kind of place where a more powerful language (python, go, heck even pwsh) might be called for. As far as this request particularly, I think I'm inclined to just reject it in this form. There's probably better ways of going about doing what you're looking for. But returning the pids on stdout is probably _not_ the right way.
Author
Owner

@martin-rueegg commented on GitHub (Sep 7, 2023):

@zadjii-msft Thank you for your reply. Much appreciated.

Also thank you for the reminder regarding the -w 0 flag.

Yet I'm sorry you're making the decision based on some potentially never used feature. Particularly, since there would be always the option to return a syntax error, as mentioned in my No Protocol section. If there would every be such a grand (or even not so grand) feature, that would collide with this, then there could be a documented incompatibility of the two features.

Anyway: Would it be an option to save the PID to an Environment Variable? In this case the "protocol" could be to either

  • number the variable names (e.g. WT_CHILD_PID_0, WT_CHILD_PID_1, WT_CHILD_PID_2 ..., or
  • have as many semicolons in the output variable, as the original command line, e.g.
    • WT_CHILD_PID=1234 for wt new-tab --return-pid commandline1
    • WT_CHILD_PID=1234;1235 for wt new-tab --return-pid commandline1; new-tab commandline2

If you could consider such a scenario, should I create a new request or shall we adapt this one to be more specific about the technical details?

@martin-rueegg commented on GitHub (Sep 7, 2023): @zadjii-msft Thank you for your reply. Much appreciated. Also thank you for the reminder regarding the `-w 0` flag. Yet I'm sorry you're making the decision based on some potentially never used feature. Particularly, since there would be always the option to return a syntax error, as mentioned in my _No Protocol_ section. If there would every be such a grand (or even not so grand) feature, that would collide with this, then there could be a documented incompatibility of the two features. Anyway: Would it be an option to save the PID to an Environment Variable? In this case the "protocol" could be to either - number the variable names (e.g. `WT_CHILD_PID_0`, `WT_CHILD_PID_1`, `WT_CHILD_PID_2` ..., or - have as many semicolons in the output variable, as the original command line, e.g. - `WT_CHILD_PID=1234` for `wt new-tab --return-pid commandline1` - `WT_CHILD_PID=1234;1235` for `wt new-tab --return-pid commandline1; new-tab commandline2` If you could consider such a scenario, should I create a new request or shall we adapt this one to be more specific about the technical details?
Author
Owner

@zadjii-msft commented on GitHub (Sep 7, 2023):

I'm pretty sure environment variables wouldn't work either. wt could set it for itself, sure, but the environment of the calling shell (cmd, pwsh) would remain unchanged. At that point, reading the env vars of the spawned wt.exe would require something quite a bit lower level than CMD scripting 😝

@zadjii-msft commented on GitHub (Sep 7, 2023): I'm pretty sure environment variables wouldn't work either. `wt` could set it for itself, sure, but the environment of the calling shell (`cmd`, `pwsh`) would remain unchanged. At that point, reading the env vars of the spawned `wt.exe` would require something quite a bit lower level than CMD scripting 😝
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#19462