Is there any way to get a hook or event in Electron JS when a new application is launched - node.js

I'm new to Electron JS. Trying to build a cross-platform desktop application to watch user activities.
My requirement is when a user moves out of my application and opens some other application like a browser/ calculator, is there any way that can be monitored from my application?
Please advice. Thanks

You can use this electron api:
let spawn = require("child_process").spawn;
let bat = spawn("cmd.exe", [
"/c", // Argument for cmd.exe to carry out the specified script
"D:\test.bat", // Path to your file
"argument1" // Optional first argument
]);
bat.stdout.on("data", (data) => {
// Handle data...
});
bat.stderr.on("data", (err) => {
// Handle error...
});
bat.on("exit", (code) => {
// Handle exit
});
Make sure to put the correct path to your batch file (here is an example to find notepad):
tasklist | find /i "notepad.exe" && echo true || echo false
Make sure to handle only false, because true will log notepad.exe info, too.
P.S.: The batch script works only for Windows, I don't know how to create the bash version.

Related

NodeJS - How do I detect other copies of my program?

I have written a NodeJS command-line program with two modes:
mode foo: runs forever until the user presses Ctrl+C
mode bar: runs once
If the user is already running the program in mode foo, then running it again in mode bar will cause errors. Thus, when the user invokes mode bar, I want to search for all other existing copies of my command-line program that are running and kill them (as a mechanism to prevent the errors before they happen).
Getting a list of processes in NodeJS is easy, but that doesn't help me much. If I simply kill all other node processes, then I might be killing other programs that are not mine. So, I need to know which specific node processes are the ones running my app. Is it even possible to interrogate a process to determine that information?
Another option is to have my program write a temporary file to disk, or write a value to the Windows registry, or something along those lines. And then, before my program exists, I could clean up the temporary value. However, this feels like a precarious solution, because if my program crashes, then the flag will never be unset and will remain orphaned forever.
What is the correct solution to this problem? How can I kill my own application?
I was able to solve this problem using PowerShell:
import { execSync } from "child_process";
const CWD = process.cwd();
function validateOtherCopiesNotRunning(verbose: boolean) {
if (process.platform !== "win32") {
return;
}
// From: https://securityboulevard.com/2020/01/get-process-list-with-command-line-arguments/
const stdout = execPowershell(
"Get-WmiObject Win32_Process -Filter \"name = 'node.exe'\" | Select-Object -ExpandProperty CommandLine",
verbose,
);
const lines = stdout.split("\r\n");
const otherCopiesOfMyProgram= lines.filter(
(line) =>
line.includes("node.exe") &&
line.includes("myProgram") &&
// Exclude the current invocation that is doing a 1-time publish
!line.includes("myProgram publish"),
);
if (otherCopiesOfMyProgram.length > 0) {
throw new Error("You must close other copies of this program before publishing.");
}
}
function execPowershell(
command: string,
verbose = false,
cwd = CWD,
): string {
if (verbose) {
console.log(`Executing PowerShell command: ${command}`);
}
let stdout: string;
try {
const buffer = execSync(command, {
shell: "powershell.exe",
cwd,
});
stdout = buffer.toString().trim();
} catch (err) {
throw new Error(`Failed to run PowerShell command "${command}":`, err);
}
if (verbose) {
console.log(`Executed PowerShell command: ${command}`);
}
return stdout;
}

Can't seem to figure out how to get the output of an process fired with spawn. stdout.on('data') not outputting for me

I am trying to setup some automation on a game server for the game Rust.
The game server itself is ran by running its executable file RustDedicated.exe with some arguments.
According to some googling and reading here on Stack Overflow I have made this script:
import config from "config";
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
const GAMESERVERPATH: string = config.get("Environment.RustDedicatedPath");
const EXECUTABLE: string = config.get("Environment.ExecutableFile");
const GAMESERVERARGS: Array<string> = [
"-batchmode",
"+server.port", `${config.get("Server.port")}`,
"+server.level", `"${config.get("Server.level")}"`,
"+server.seed", `${config.get("Server.seed")}`,
"+server.worldsize", `${config.get("Server.worldsize")}`,
"+server.maxplayers", `${config.get("Server.maxplayers")}`,
"+server.hostname", `"${config.get("Server.hostname")}"`,
"+server.description", `"${config.get("Server.description")}"`,
"+server.headerimage", `"${config.get("Server.headerimage")}"`,
"+rcon.port", `${config.get("Rcon.port")}`,
"+rcon.password", `"${config.get("Rcon.password")}"`,
"+rcon.web", `${config.get("Rcon.web")}`
];
const gameServerProc : ChildProcessWithoutNullStreams = spawn(
GAMESERVERPATH+EXECUTABLE,
GAMESERVERARGS,
{
cwd: GAMESERVERPATH,
shell: true,
}
);
gameServerProc.stdout.on("data", (data) => {
console.log(`stdout:${data.toString()}`);
});
gameServerProc.stderr.on("data", (data) => {
console.log(`stderr:${data.toString()}`);
});
gameServerProc.on("error", (err) => {
console.log(`error:${err.message}`);
});
What is happening is that i can see the output of the executable in the terminal window, and the server is firing without errors, but it seems that stdout is not firing the on('data') event.
I never see stdout:.
See the screenshot below where i have Code open, the output is on the bottom right.
Why is my script failing to get the on('data') firing when the executable outputs?
This issue had nothing to do with the code, but rather the game engine Unity. An extra argument -logFile - had to be present for the executable to output to stdout.

Allow powershell console to show in NodeJS

I've got a NodeJS (Electron) client which is running the following code:
child = spawn("powershell.exe",['-ExecutionPolicy', 'ByPass', '-File', require("path").resolve(__dirname, '../../../../updater.ps1')]);
child.on("exit",function(){
require('electron').remote.getCurrentWindow().close();
});
The file that this opens is a powershell file which downloads and unpacks an update. If I run this file manually, I get the powershell console which shows me a progress bar for the download and the unpacking. However, running it from code like above does not show the console.
How can I make my code show the powershell console during it's runtime? I'm having a hard time formulating search terms to find an answer to this.
Things I've tried:
Adding '-NoExit' to my 2nd parameter array
Adding { windowsHide: false } parameter
Adding '-WindowStyle', 'Maximized' to 2nd parameter array
I've also tried switching to exec.
exec('powershell -ExecutionPolicy Bypass -File ' + updater_path, function callback(error, stdout, stderr){
console.log(error);
});
Which runs the file but still doesn't show the console.
An answer will preferably allow me to run powershell files un-attached to the NodeJS client, and will also show the powershell console while running.
Here is my current code:
updater = spawn("powershell.exe",['-ExecutionPolicy', 'ByPass', '-File', remote.app.getAppPath() + '\\app\\files\\scripts\\' + data.type + '_updater.ps1'], { detached: true, stdio: 'ignore' });
updater.unref();
Which actually does nothing, it doesn't even seem like it runs the script at all.
I've tried the same thing using a batch file, it's never opened.
updater = spawn("cmd",[remote.app.getAppPath() + '\\app\\files\\scripts\\launch_updater.bat'], { detached: true, stdio: ['ignore', 'ignore', 'ignore'] });
updater.unref();
The shell:true and detached:true where enough to open PowerShell.
I've made this simple demo for a detached PowerShell window:
root:
index.js ( aka your main js file )
powerShell.js ( the detached powershell window)
showTime.ps1 ( aka your file )
In powerShell.js will find that i separated the arguments that keep the PS Window open under the defaultPowerShellArguments variable.
Advice: I see alot of powershell at the spawn command parameter, i cannot stress this enough, add the .exe extension too when running under Windows, correct ispowershell.exe. If you happen to use just powershell and nodepad will all of a sudden pop-up echoing your script, you will have a hard time trying to figure it out.
index.js
const powerShell = require('./powerShell');
let seconds = 5;
let remaining = seconds;
let nd = new Date().getTime() + (seconds * 1000);
setInterval(() => {
remaining -= 1
let time = new Date();
console.log('Your code running at:', time.toTimeString());
if (remaining === 3) {
console.log('Opening powershell...')
powerShell([
"-File",
"./showTime.ps1"
]);
}
if (time.getTime() > nd) {
console.log('Main script will exit.');
process.exit(0);
}
}, 1000);
powerShell.js
module.exports = (spawnArguments) => {
if (typeof spawnArguments === "undefined" || !(spawnArguments instanceof Array)) {
spawnArguments = [];
}
const {spawn} = require('child_process');
let defaultPowerShellArguments = ["-ExecutionPolicy", "Bypass", "-NoExit",];
let powershell = spawn('powershell.exe', [...defaultPowerShellArguments, ...spawnArguments], {
shell: true,
detached: true,
// You can tell PowerShell to open straight to the file directory
// Then you can use the relative path of your file.
// cwd: 'ABSOLUTE_PATH_TO_A_VALID_FOLDER'
// cwd: 'C:/'
});
}
showTime.ps1
Get-Date -Format G
I suspect you'll need to pass the shell option to tell node which shell to execute the command on. This defaults to process.env.ComSpec on Windows, which if for whatever reason isn't set will then default to cmd.exe.
Reference: https://nodejs.org/api/child_process.html#child_process_default_windows_shell:
Although Microsoft specifies %COMSPEC% must contain the path to 'cmd.exe' in the root environment, child processes are not always subject to the same requirement. Thus, in child_process functions where a shell can be spawned, 'cmd.exe' is used as a fallback if process.env.ComSpec is unavailable.
PowerShell shell support has been made available via this commit.
Since the exec command is working, I'd suggest starting there and trying to get the window to show. Try passing shell: 'PowerShell' in the options object. windowsHide defaults to false for child_process.exec, but it might not hurt to pass it as well:
exec('powershell -ExecutionPolicy Bypass -File ' + updater_path, { shell: 'PowerShell', windowsHide: false }, function callback(error, stdout, stderr){
console.log(error);
});
You may also be able to get it working with spawn with similar options: { shell: 'PowerShell', windowsHide: false }
I eventually worked around this by using exec to call a batch file, and this batch file runs the powershell file.
//call buffer .bat file, close main window after 3 seconds to make sure it runs before closing.
exec('start ' + remote.app.getAppPath() + '\\app\\files\\scripts\\launch_updater.bat ' + data.type);
setTimeout(function() {
require('electron').remote.getCurrentWindow().close();
}, 3000);
launch_updater.bat:
#ECHO OFF
set arg1=%~1
start powershell.exe -executionpolicy bypass -File "%~dp0/%arg1%_updater.ps1"
for /f "skip=3 tokens=2 delims= " %%a in ('tasklist /fi "imagename eq cmd.exe"') do (
if "%%a" neq "%current_pid%" (
TASKKILL /PID %%a /f >nul 2>nul
)
)
exit /b
The loop in the batch file is essentially just so it will close itself without leaving a command window open. I pass an argument based on what kind of update it is.

Using grunt to run a node server and do cleanup after

So basically this is what I want to do. Have a grunt script that compiles my coffee files to JS. Then run the node server and then, either after the server closes or while it's still running, delete the JS files that were the result of the compilation and only keep the .coffee ones.
I'm having a couple of issues getting it to work. Most importantly, the way I'm currently doing it is this:
grunt.loadNpmTasks("grunt-contrib-coffee");
grunt.registerTask("node", "Starting node server", function () {
var done = this.async();
console.log("test");
var sp = grunt.util.spawn({
cmd: "node",
args: ["index"]
}, function (err, res, code) {
console.log(err, res, code);
done();
});
});
grunt.registerTask("default", ["coffee", "node"]);
The problem here is that the node serer isn't run in the same process as grunt. This matters because I can't just CTRL-C once to terminate JUST the node server.
Ideally, I'd like to have it run in the same process and have the grunt script pause while it's waiting for me to CTRL-C the server. Then, after it's finished, I want grunt to remove the said files.
How can I achieve this?
Edit: Note that the snippet doesn't have the actual removal implemented since I can't get this to work.
If you keep the variable sp in a more global scope, you can define a task node:kill that simply checks whether sp === null (or similar), and if not, does sp.kill(). Then you can simply run the node:kill task after your testing task. You could additionally invoke a separate task that just deletes the generated JS files.
For something similar I used grunt-shell-spawn in conjunction with a shutdown listener.
In your grunt initConfig:
shell: {
runSuperCoolJavaServer:{
command:'java -jar mysupercoolserver.jar',
options: {
async:true //spawn it instead!
}
}
},
Then outside of initConfig, you can set up a listener for when the user ctrl+c's out of your grunt task:
grunt.registerTask("superCoolServerShutdownListener",function(step){
var name = this.name;
if (step === 'exit') process.exit();
else {
process.on("SIGINT",function(){
grunt.log.writeln("").writeln("Shutting down super cool server...");
grunt.task.run(["shell:runSuperCoolJavaServer:kill"]); //the key!
grunt.task.current.async()();
});
}
});
Finally, register the tasks
grunt.registerTask('serverWithKill', [
'runSuperCoolJavaServer',
'superCoolServerShutdownListener']
);

NodeJS not spawning child process except in tests

I have the following NodeJS code:
var spawn = require('child_process').spawn;
var Unzipper = {
unzip: function(src, dest, callback) {
var self = this;
if (!fs.existsSync(dest)) {
fs.mkdir(dest);
}
var unzip = spawn('unzip', [ src, '-d', dest ]);
unzip.stdout.on('data', function (data) {
self.stdout(data);
});
unzip.stderr.on('data', function (data) {
self.stderr(data);
callback({message: "There was an error executing an unzip process"});
});
unzip.on('close', function() {
callback();
});
}
};
I have a NodeUnit test that executes successfully. Using phpStorm to debug the test the var unzip is assigned correctly
However if I run the same code as part of a web service, the spawn call doesn't return properly and the server crashes on trying to attach an on handler to the nonexistent stdout property of the unzip var.
I've tried running the program outside of phpStorm, however it crashes on the command line as well for the same reason. I'm suspecting it's a permissions issue that the tests don't have to deal with. A web server spawning processes could cause chaos in a production environment, therefore some extra permissions might be needed, but I haven't been able to find (or I've missed) documentation to support my hypothesis.
I'm running v0.10.3 on OSX Snow Leopard (via MacPorts).
Why can't I spawn the child process correctly?
UPDATES
For #jonathan-wiepert
I'm using Prototypical inheritance so when I create an "instance" of Unzipper I set stdout and stderr ie:
var unzipper = Unzipper.spawn({
stdout: function(data) { util.puts(data); },
stderr: function(data) { util.puts(data); }
});
This is similar to the concept of "constructor injection". As for your other points, thanks for the tips.
The error I'm getting is:
project/src/Unzipper.js:15
unzip.stdout.on('data', function (data) {
^
TypeError: Cannot call method 'on' of undefined
As per my debugging screenshots, the object that is returned from the spawn call is different under different circumstances. My test passes (it checks that a ZIP can be unzipped correctly) so the problem occurs when running this code as a web service.
The problem was that the spawn method created on the Object prototype (see this article on Protypical inheritance) was causing the child_process.spawn function to be replaced, so the wrong function was being called.
I saved child_process.spawn into a property on the Unzipper "class" before it gets clobbered and use that property instead.

Resources