Search code examples
node.jspowershellexecchild-processread-host

How is child_process.exec('powershell "$userInput = Read-Host;"') handled in node.js?


I am experimenting with the code below, for testing how does child_process.exec() in node.js handle standard input from user.

import { exec } from 'child_process';
exec('powershell "$userInput = Read-Host;"', (err, stdout, stderr) => {
  if(err) throw err;
  console.log(stdout);
  console.log(stderr);
});

When I ran this through node main.js, the terminal is running forever with no output.

It seems that the exec() function creates a new shell waiting for user to type in it. However, I was unable to find the place to do so.

So I wonder if there are some ways to show that place or to handle this problem without the need to use Read-Host to receive user input.


Solution

  • Assuming that you're asking how to provide stdin input programmatically to a launched child process:

    • Use spawn rather than exec (the latter involves the platform-appropriate default shell, which isn't needed here).

    • Via the returned child process object, use .stdin.write() to write to its stdin stream; be sure to call .stdin.end() once all input has been provided, otherwise the child process won't exit.

    • In the code passed to powershell.exe, the Windows PowerShell CLI, use the automatic $input variable to access stdin input.

    import { spawn } from 'child_process'
    
    // Note the use of $input to refer to stdin input.
    let ps = spawn(
      'powershell.exe', 
      [
        '-NoProfile', '-Command', 
        '$userInput = $input; "You entered: $userInput"'
      ]
    )
    
    // Connect the child process' output streams to the calling process'
    ps.stdout.pipe(process.stdout)
    ps.stderr.pipe(process.stderr)
    
    // Write the desired input to the child process' stdin.
    ps.stdin.write('user input')
    
    // Close stdin; without this, the child process won't terminate.
    ps.stdin.end()
    

    As you've discovered yourself, if you want to connect the calling process' stdin stream to that of the PowerShell child process, i.e. to relay stdin input received by the current process to the child process, replace the ps.stdin.write('user input') and ps.stdin.end() calls with:

    // Relay the current process' stdin input to the PowerShell child process.
    process.stdin.pipe(ps.stdin)
    

    Note:

    • If you don't provide stdin input to your script on invocation, it will be prompted for interactively (due to PowerShell's automatic $input variable trying to read from stdin):

      • On Unix-like platforms, you can submit interactively typed input with Ctrl+D.

      • On Windows, you should be able to use Ctrl+Z, followed by Enter, but as of the current LTS / stable version of Node.js (v20.15.0 / v22.3.0) this appears not to work: the interactive prompt continues. (Only Ctrl+C ends the interactive prompt, which, however, aborts processing).