2

is there a way to call a function in a spawned child process, a powershell script, from the parent node js script?

node script:

const cp = require("child_process");
const psData = cp.spawn
("powershell -executionpolicy bypass  ./powershell-script.ps1", [], {
    shell: "powershell.exe",
});

powershell script:

function psDoSomething{
    # do something
}

2 Answers 2

2

There's no builtin way for Node to call specific functions in a subprocess. You could pass arguments to your PS script if that would help, or you could spawn/exec another PS script, but Node doesn't know or care about Powershell (or Python, or Shell, or any other language in a spawned/execed script). If you need that functionality, you could try one of the Powershell-related packages on NPM.

Edit: OP suggested in a comment using stdin.write on the child process, and listening to STDIN in the subprocess, which is an interesting idea. I don't use Powershell, but here's how that could work using Bash:

const bashSession = require('child_process').spawn('./foo.sh')
bashSession.stdin.setEncoding('utf-8')
bashSession.stdout.pipe(process.stdout)
bashSession.stdin.write('someFunc\n')
bashSession.stdin.end()
#!/bin/bash

doThing() {
    echo We made it here
}

while read line; do
    if [[ $line == someFunc ]]; then
        doThing
    else
        echo "$line"
    fi
done < "${1:-/dev/stdin}"

Having your subprocess listen to everything on stdin could create problems, but the same concept could be used with a designated file, or with a TCP socket.

Sign up to request clarification or add additional context in comments.

2 Comments

I reckon arguments can only be passed when the child process is spawned so this won't work for my purposes. I've read a bit about childprocess.stdin.write("...") and was wondering if I could add a function to the ps script that will listen to the stdin and trigger the doSomething function when a key word is written from the node script, is this possible?
@bitbar Yes, that would work, and that's a good idea that I totally didn't even think of. Editing my post.
2

If all you're looking to do is a one-time call to a function defined inside your PowerShell script, the following should do:

let child = require('child_process').spawn(
  'powershell', 
  [
    '-noprofile', '-executionpolicy', 'bypass', '-c', 
    '. ./powershell-script.ps1; psDoSomething'
  ],
  // In this simple example, pass the child process' output
  // streams directly through to this process' output streams.
  { stdio: 'inherit' }
)
  • The above uses powershell.exe, the Windows PowerShell CLI, with its -c / -Command parameter to process a given snippet of PowerShell code.

  • Since the function to invoke is defined inside the ./powershell-script.ps1 script, that script must be dot-sourced in order to load the function definition into the caller's scope.

  • Thereafter, the function can be invoked.


If you're looking to spawn a PowerShell child process to which you can iteratively feed commands for execution later, on demand, via stdin - in other words: to create a programmatically controlled REPL - you'll need a solution whose fundamentals are outlined in Zac Anger's answer, based on spawning the PowerShell child process with -c - (-Command -), namely as powershell.exe -noprofile -executionpolicy bypass -c -

  • Caveat: Sending a command spanning multiple lines must be terminated with two newlines; see GitHub issue #3223 for details.
let child = require('child_process').spawn(
    // Start PowerShell in REPL mode, with -c - (-Command -)
    'powershell',
    [
        '-noprofile', '-executionpolicy', 'bypass', '-c',
        '-' // Start PowerShell as a REPL
    ]
)

// Connect the child process' output streams to the calling process',
// so they are *passed through*.
// Note: Using { stdio: 'inherit' } in the .spawn() call instead 
// would prevent using child.stdin.write() below.
child.stdout.pipe(process.stdout)
child.stderr.pipe(process.stderr)

// Dot-source the file that defines the function.
// Note the double \n to ensure that PowerShell recognizes the end of the command.
child.stdin.write('. ./powershell-script.ps1\n\n')
// Now you can invoke it. 
child.stdin.write('psDoSomething\n\n')
// ... pass additional commands, as needed.
// End the session.
child.stdin.write('"Goödbyə!"\n\n')
child.stdin.end()

Character-encoding note:

  • child.stdin.write() sends UTF-8-encoded strings by default (you can change the encoding with child.stdin.setEncoding(), as shown in Zac's anwer).

  • For PowerShell on Windows to interpret UTF-8-encodes string correctly and to also make it output UTF-8 itself, the current console's code page must be 65001 beforehand - run chcp to verify. If it isn't:

    • If your Node.js program runs from a cmd.exe session, run the following first:

       chcp 65001 
      
    • If it runs from a PowerShell session, run the following first:

       $OutputEncoding = [Console]::InputEncoding = [Console]::OutputEncoding = [Text.UTF8encoding]::new()
      
  • On Unix-like platforms, PowerShell fortunately defaults to UTF-8.

6 Comments

how do I invoke the function? child.stdin.write("psDoSomething"); does not work, although the ps script get's the text.
@bitbar, the ; psDoSomething part in my code snippet invokes the function. If you do want to use the stdin-based approach, you must submit . ./powershell-script.ps1 as the first command.
if I understand correctly '. ./powershell-script.ps1; psDoSomething' invokes psDoSomething once, i.e. when the child is spawned. What if the child script has a while true loop, how would I repeatedly call the psDoSomething within the same child process? e.g.: function psDoSomething { Write-Output "func invoked from node js script" } while ($true) {#some other routines}
With the stdin approach, there's no need for a loop: Launch the process with powershell.exe -noprofile -executionpolicy bypass -c -, then feed all commands to execute via stdin, starting with . ./powershell-script.ps1, after which you can send psDoSomething
thanks for your help and insights! I've decided to stick with psData.stdin.write("some text or special char\n"); in the node script and in the ps: $textFromNode = Read-Host if ($textFromNode -eq "some text or special char") { psDoSomething }. This will allow me to invoke psDoSomething on demand. (this comment was edited)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.