NODEJS [electron io.js] send input to spawned process child.stdin.write(); - node.js

I have a set of scripts in PowerShell. I built a GUI with a .HTA, but I am migrating over to a NODE.js like approach.
I have a working script, but I had to make a modification to work around the lost functionality to get input from the command line. I used to have the CMD prompt window appear where I would see all output and, if there was a prompt (Read-Host), the user could interact with it. To work around that, I am now using [Microsoft.VisualBasic.Interaction]::InputBox() to get input from the user when needed.
I have had issues trying to use child.stdin.write(); because my PowerShell process just hangs. It will not respond to my input.
I have tried every answer I have found on the net, with no success. The script that is called in production is a long running process, depending on the task selected it will take from 20 min to 3 hours.
So, the issue is, I cannot get the child.spawned process to send the input, or maybe PowerShell does not accept input from stdin.
Thanks for your help in advance.
CODE:
HTML:
<button type="button" id="btn_ExecuteReport">Run Report</button>
<button type="button" id="btn_StopReport" >Stop Report</button>
<button type="button" id="btn_SendInput" >Send this...</button>
<h2>Results:</h2>
<p id="result_id">...</p>
<br/>
JS (using Electron v 0.31.0, io.js v 3.1.0, jQuery v 1.7.2):
$(document).ready(function () {
$("#btn_ExecuteReport").click(function (event) {
event.stopPropagation();
global.$ = $;
var remote = require('remote');
// to get some paths depending on OS
var app = remote.require('app');
//clipboard
var clipboard = require('clipboard');
var shell = require('shell');
// for choosing a folder
var dialog = remote.require('dialog');
var spawn = require('child_process').spawn;
sCmd = "powershell.exe ";
sCmdArg = '&' + "'" + __dirname + "/app.ps1' " ;
// omit parameters for the purpose of this example
// + "-ODir '" + sOutputDirectory
// + "' -WDir '" + sWorkingDirectory + "'"
// + " -Report " + sReport
// + " -pSendEmail " + sSendEmail
// + " -pSendEmailHTMLBody " + sSendEmailHTMLBody
// + " -pCreateReport " + sCreateReport
// + " -pCheckReport " + sCheckReport
// + " -pEmailAction " + sEmailAction
// ;
$("#result_id").html("<p><font color='magenta'>Command: </font>"
+ sCmdConcat + "</p>");
// try again with spawn and event handlers
var psSpawn = spawn(sCmd, [sCmdArg]);
// add a 'data' event listener for the spawn instance
psSpawn.stdout.on('data', function(data) {
console.log('stdout: ' + data);
});
// add an 'end' event listener to close the writeable stream
psSpawn.stdout.on('end', function(data) {
console.log('Done with psSpawn...' );
});
psSpawn.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
// when the spawn child process exits, check if there were any errors and close the writeable stream
psSpawn.on('exit', function(code) {
if (code != 0) {
console.log('[EXIT] Failed: ' + code);
}
//Collect result from PowerShell (via clipboard)
sResultData = clipboard.readText("Text");
});
psSpawn.on('close', function(code) {
if (code != 0) {
console.log('[ISSUE] code: ' + code);
}
console.log('[CLOSE]');
});
//sending the input with button #btn_sendInput
$("#btn_SendInput").click(function (event) {
psSpawn.stdin.setEncoding('utf8');
psSpawn.stdin.write('ok');
psSpawn.stdin.write('\n');
psSpawn.stdin.write(os.EOL);
//uncomment following line to end stdin on first button press
//psSpawn.stdin.end();
});
// killing process
$("#btn_StopReport").click(function (event) {
event.stopPropagation();
psSpawn.kill('SIGTERM');
console.log('killed ' + psSpawn.pid);
});
});
});
PowerShell:
#work around to lack of Read-Host functionality
Write-Host "waiting"
[System.Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') | Out-Null
$computer = [Microsoft.VisualBasic.Interaction]::InputBox("Enter a computer name", "Computer", "$env:computername")
#
Write-Host "[Done] waiting"
#workaround END works
Write-Host "Computer: [$computer]"
# START omit ----------------
#if we remove the following lines, all works but we lose the functionality to ask for input from the command line
Write-Host "Propmt for OK"
$ok = Read-Host
#
Write-Host "Prompt. Value of 'ok': [$ok]"
# END omit ----------------
#Copy new message to clipboard
"we want to see this maessage on the browser. $computer" | clip
#Exit script returning the appropriate value
[Environment]::Exit(0)
exit
My console output is:
stdout: waiting
stdout:
stdout: [Done] waiting
Computer: [COMPUTER_NAME]
Propmt for OK
The script hands no matter how many times I press Send this.... The console output is (after pressing the #buton_StopReport):
killed 2548
Done with psSpawn...
[EXIT] Failed: null
[ISSUE] code: null
[CLOSE]

Related

Can I “listen” for a specific output with child_process?

So far I have gotten my script to execute a windows .bat file with child_process, my issue is that it opens it in the background with no way to “connect” to it to see what happens and debug, is there a way to “listen” for a certain output to happen? For example, if the .bat outputs a “Done!” in the shell at one point, is there a way to make my node.js script detect that certain keyword and run further commands if it does?
Thanks!
Some clarification: The .bat outputs "Done!" and stays running, it doesn't stop, all I want to do is detect that "Done!" so that I can send a message to the user that the server has successfully started
My current code:
exec('D:\\servers\\game_server_1\\start.bat', {shell: true, cwd: 'D:\\servers\\game_server_1'});
Well, if you're trying to do a one and done type of NodeJS script, you can just spawn a process that launches with the given command and exits when all commands completed. This creates a one and done streaming interface that you can monitor. The stdout returns a data buffer that returns the command you ran, unless it's something like START to launch a program-- it returns null. You could just issue a KILL command after the START -- your_program.exe:
const spawn = require('child_process').spawn;
const child = spawn('cmd.exe', ['/c', 'commands.bat']);
let DONE = 0;
const done = () => {
console.log("log it");
DONE++;
};
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
//it's important to add some type of counter to
//prevent any logic from running twice, since
//this will run twice for any given command
if ( data.toString().includes("DONE") && DONE === 0 ) {
done();
}
});
child.stderr.on('data', function (data) {
console.log('stderr: ' + data);
});
child.on('exit', function (code) {
console.log('child process exited with code ' + code);
});
Keep in mind, when you run a command to launch a program and the program launches, the data buffer will be null in stdout event listener. The error event will only fire if there was an issue with launching the program.
YOUR .BAT:
ECHO starting batch script
//example launching of program
START "" https://localhost:3000
//issue a command after your program launch
ECHO DONE
EXIT
You could also issue an ECHO DONE command right after the command where you launched the program and listen for that, and try and parse out that command from stdout.
You could use a Regular expression.
const { spawn } = require('child_process');
const child = spawn(...);
child.stdout.on('data', function (data) {
console.log('stdout: ' + data);
// Now use a regular expression to detect a done event
// For example
data.toString().match(/Done!/);
});
// Error handling etc. here

child_process.on('close') is sometimes painfully slow

I have a electron application that opens a external program (in my case Office), and has to wait for the program to be closed.
the code I wrote works great but sometimes the child_process.on('close') event is fired 10 or 20 seconds after the program has closed. The code is:
const cp = require("child_process");
child = cp.spawn(path/to/Office.exe + ' "' + path/to/myFile.pptx + '"', {shell: true});
child.on('close', function (code) {
//do something
});
Most of the time it reacts after 1 or 2 seconds which is fine, but sometimes it takes up to 20 seconds until I receive the close event. The program closes fast (according to the task manager), but node seems to wait for something.
I also tried child.on('exit'), calling the program with cp.exec()and using the options.stdio: ignore for spawn, as I thought maybe node is waiting for some stream from the child. But that made no difference.
Does anybody know a safe way to speed that process up?
I have tried your code and the close event triggers with a 0.5-2s delay, bearable i would say.
However, the 20s delay did not occur, but if this problem still persists on your end, you can try the approach below, which consists in checking the spawn pid.
const pidExists = (pid) => {
let pidOk = true;
try {
process.kill(pid, 0);
} catch (e) {
pidOk = false;
}
return pidOk;
};
const cp = require("child_process");
// I added the detach option because we won't need that process anymore since we're following the PID.
let child = cp.spawn(path/to/Office.exe + ' "' + path/to/myFile.pptx + '"', {shell: true, detach: true});
let officePID = child.pid; // this is the spawn pid
setInterval(()=>{
if( pidExists(officePID)){
console.log('file is still open', new Date().getTime());
}else{
console.log('file was closed', new Date().getTime());
process.exit(0);
}
}, 500);
This is a better approach since you said that the task manager shows you that the program was closed.

Show loading gif while child process task is performed and hide when complete

Hello I am looking for assistance with displaying a loading gif while my route performs a task. I already have a promise function that displays a message when the test is running and than displays a message when it is done. I'm thinking I can incorporate the loading gif along with it. I am just not sure how to go about it with this set up.
The HTML
<div class="container">
<img src="/images/loading.gif" height="32" width="32" id="loading-indicator" style="display: none ; margin: 0 auto;" />
</div>
The sever side, using node.js with express.
router.get("/pos/fcsf_default", (req, res) => {
let test = "Was a super long string, edited to read easier."
let data = JSON.stringify(test);
sendToSlack("QA Hub - Automation - FCSF: " + req.user.username + " is running a rental with default values(This is a test)", "QA Hub - Automation", "auto_message_test" );
console.log("QA Hub - Automation - FCSF: " + req.user.username + " is running a rental with default values", "QA Hub - Automation", "auto_message_test");
var child = exec(`cd ${__dirname} & cd ../../ & cd automation_projects/fcsf/ & rake reserve`);
fs.writeFileSync(path.resolve(__dirname, "../", "temp/form-data.json"), (data));
myPromise(child).then(function (result) {
console.log('promise complete: ' + result);
sendToSlack("QA Hub - Automation - FCSF: " + "Test complete.");
console.log("QA Hub - Automation - FCSF: " + "Test complete.")
}, function (err) {
console.log('promise rejected: ' + err);
sendToSlack("Test failed");
});
req.flash("info", data + " test is being sent to process and run.");
res.redirect("/QAApplicationHub/Automation/dispatch_receive");
})
Ive read using ajax, is that the best approach here? I'm thinking what has to be done is retrieve the image from the server side and than send it to the front end so that I can show it on the UI and trigger it from this route....Any assistance is appreciated!

Why does the Node.js scripts console close instantly in Windows 8?

I've tried nearly every example for scripts I can find. Every sample opens the terminal for a split second. Even this closes as soon as input is entered. Is this normal?
var rl = require('readline');
var prompts = rl.createInterface(process.stdin, process.stdout);
prompts.question("How many servings of fruits and vegetables do you eat each day? ", function (servings) {
var message = '';
if (servings < 5) {
message = "Since you're only eating " + servings +
" right now, you might want to start eating " + (5 - servings) + " more.";
} else {
message = "Excellent, your diet is on the right track!";
}
console.log(message);
process.exit();
});
There are 2 options that control this in Tools/Options/Node.js Tools/General:
Wait for input when process exists abnormally
Wait for input when process exists normally
Taken from https://nodejstools.codeplex.com/discussions/565665

Scala Process exits but doesn't clean up threads

I am trying to run PhantomJS from within a scala application using akka actors:
val process = Process("phantomjs --ignore-ssl-errors=yes " + myrenderscript.js + args ...)
val result = process.run(processLogger, true).exitValue() match {
case ExitCode.SUCCESS => Left(Success)
case ExitCode.TIMEOUT => Right(TimeoutError)
case ExitCode.OPEN_FAILED => Right(NetworkError)
case _ => Right(UnknownError)
}
the myrenderscript.js looks like this:
var version = "1.1";
var TIMEOUT = 30000,
EXIT_SUCCESS = 0,
EXIT_TIMEOUT = 2,
EXIT_OPEN_FAILED = 3;
if (phantom.args.length < 2) {
console.log("Usage: phantomjs render.js parentUrl output [width height]");
phantom.exit(1);
}
var url = phantom.args[0];
var output = phantom.args[1];
var width = parseInt(phantom.args[2] || 1024);
var height = parseInt(phantom.args[3] || 1024);
var clipwidth = parseInt(phantom.args[4] || 1024);
var clipheight = parseInt(phantom.args[5] || 1024);
var zoom = parseFloat(phantom.args[6] || 1.0);
var phantom_version = phantom.version.major + "." + phantom.version.minor + "." + phantom.version.patch;
var userAgentString = "PhantomJS/" + phantom_version + " screenshot-webservice/" + version;
renderUrlToFile(url, output, width, height, clipwidth, clipheight, zoom, userAgentString, function (url, file) {
console.log("Rendered '" + url + "' at size (" + width + "," + height + ") into '" + output + "'");
phantom.exit(EXIT_SUCCESS);
phantom = null;
});
setTimeout(function () {
console.error("Timeout reached (" + TIMEOUT + "ms): " + url);
phantom.exit(EXIT_TIMEOUT);
}, TIMEOUT);
function renderUrlToFile(url, file, width, height, clipwidth, clipheight, zoom, userAgentString, callback) {
console.log("renderUrlToFile start: " + url)
var page = new WebPage();
page.viewportSize = { width: width, height: height };
page.clipRect = { top: 0, left: 0, width: clipwidth, height: clipheight};
page.settings.userAgent = userAgentString;
page.zoomFactor = zoom;
page.open(url, function (status) {
console.log("renderUrlToFile open page: " + url)
if (status !== "success") {
console.log("Unable to render '" + url + "' (" + status + ")");
page.release();
page.close();
page = null;
phantom.exit(EXIT_OPEN_FAILED);
} else {
console.log("renderUrlToFile open page success and pre-render: " + url)
page.render(file);
console.log("renderUrlToFile open page post-render: " + url)
page.release();
page.close();
page = null;
callback(url, file);
}
});
}
prior to creating the process and after it finishes running, about 4 new threads are being created.
Each time the method that creates the process is called, new threads are created and started. After the process is done, the threads go back to a state of monitoring. Eventually my application takes upwards to 500+ threads (I'm capturing a large website and the internal links)
How do I get scala to clean up the threads that are created when running phantomjs?
Edit:
I've changed the scala code to do the following:
val process = Process("phantomjs --ignore-ssl-errors=yes " + myrenderscript.js + args ...).run(processLogger, connectInput)
val result = process.exitValue() match {
case ExitCode.SUCCESS => Left(Success)
case ExitCode.TIMEOUT => Right(TimeoutError)
case ExitCode.OPEN_FAILED => Right(NetworkError)
case _ => Right(UnknownError)
}
process.destroy()
Yet still the threads live on....
I figured out why it is not cleaning up the threads but I don't fully understand it. So if someone posts the true answer on here, I'll vote that answer up.
The problem was I was setting the connectInput value to true. When I set it to false, the threads get destroyed as expected. I'm not sure as to why.
When set to true, a thread dump reveals that one of the threads was blocking the others:
Thread-3#2830 daemon, prio=5, in group 'main', status: 'RUNNING'
blocks Thread-63#4131
blocks Thread-60#4127
blocks Thread-57#4125
blocks Thread-54#4121
blocks Thread-51#4103
blocks Thread-48#4092
blocks Thread-45#4072
blocks Thread-42#4061
blocks Thread-39#4054
blocks Thread-36#4048
blocks Thread-33#4038
blocks Thread-30#4036
blocks Thread-27#4008
blocks Thread-24#3996
blocks Thread-21#3975
blocks Thread-18#3952
blocks Thread-15#3939
blocks Thread-12#3905
blocks Thread-9#3885
blocks Thread-6#3850
at java.io.FileInputStream.readBytes(FileInputStream.java:-1)
at java.io.FileInputStream.read(FileInputStream.java:220)
at java.io.BufferedInputStream.read1(BufferedInputStream.java:256)
at java.io.BufferedInputStream.read(BufferedInputStream.java:317)
at java.io.FilterInputStream.read(FilterInputStream.java:116)
at java.io.FilterInputStream.read(FilterInputStream.java:90)
at scala.sys.process.BasicIO$.loop$1(BasicIO.scala:225)
at scala.sys.process.BasicIO$.transferFullyImpl(BasicIO.scala:233)
at scala.sys.process.BasicIO$.transferFully(BasicIO.scala:214)
at scala.sys.process.BasicIO$.connectToIn(BasicIO.scala:183)
at scala.sys.process.BasicIO$$anonfun$input$1.apply(BasicIO.scala:190)
at scala.sys.process.BasicIO$$anonfun$input$1.apply(BasicIO.scala:189)
at scala.sys.process.ProcessBuilderImpl$Simple$$anonfun$2.apply$mcV$sp(ProcessBuilderImpl.scala:72)
at scala.sys.process.ProcessImpl$Spawn$$anon$1.run(ProcessImpl.scala:22)
I initially thought it was the process logger, but that wasn't the case.
Can someone explain this to me?

Resources