Allow powershell console to show in NodeJS - node.js

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.

Related

nodejs run a bash script using spawn

I know this question has been asked many times but I have tried most of the methods and they just don't work for me.
So here is my problem, I have a simple bash script like this
#!/bin/bash
echo "Username: $1";
echo %DATABASE_URL%;
I want to run this script in a separate process. so if the parent process gets killed during my script being excused it still continues running.
Here is my nodejs code
const child = spawn('bash', [`script.sh`, 'test'], {
detached: true,
cwd: process.cwd(),
detached: true,
stdio: "inherit",
DATABASE_URL: 'test'
}, function (err, stdout, stderr) {
// Node.js does not invoke this
console.log(stdout);
stdout.on("data", data => {
console.log('Output of script execution');
});
stderr.on("data", data => {
console.log('an error with file system');
});
});
child.unref();
child.on('exit', (code) => {
console.log("Child exited");
});
So I know that my script returns some output and should run callback but it does not run it. It directly jumps to the on.('exit') callback which confuses me.
Also it worth mentioning that I am testing the code on windows and bash script.sh 'test' works if I run it on cmd.
Posts I have tried:
How to run shell script file using nodejs?
Execute script from Node in a separate process
Bash Script : what does #!/bin/bash mean?
and many of the existing weblogs that explains the same.

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;
}

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

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.

Node.js - exec shell commands with .bashrc initialized

I'm writing a small utility tool for development to sync files over ssh. Normally I use ssh-agent set up in .bashrc file to connect to my dev server easily. I'd like to use exec in the script, but calling ssh-agent, every time I make a request sounds a bit inoptimal.
Is there a way I could execute the agent code once, and then have it working for all subsequent ssh requests I make? E.g. to spawn a shell process like a terminal emulator, and then use that process to execute a command, rather than invoking a new shell with each command.
The reason I want to do this, is I don't want to store the password in a config file.
You can create one ssh process, and then execute other commands using same process. Here is an example how to use it for bash. I'm creating a new bash shell and execugte the command ls -la and exit you can execute other commands.
const cp = require("child_process")
class MyShell {
constructor(command) {
this._spawned = cp.spawn(command, {
stdio: ["pipe", "pipe", "inherit"],
})
}
execute(command, callback) {
this._spawned.stdin.write(command + "\n")
this._spawned.stdout.on("data", (chunk) => {
if (callback) {
callback(chunk.toString())
}
})
}
}
var myShell = new MyShell("bash")
myShell.execute("ls -la", (result) => {
console.log(result)
})
myShell.execute("exit")

Is there a way to launch a terminal window (or cmd on Windows) and pass/run a command?

Question
Is it possible to do the following?
open a new cmd.exe or terminal (on MacOS / Linux) window
pass / run a command, e.g. cd <path>
Problem
I can open cmd by running this command:
"$electron.shell.openItem('cmd.exe')"
But shell.openItem doesn't allow to pass the arguments / commands.
I tried using child_process but I couldn't make it work at all, it doesn't open a new terminal window:
const { spawn, exec } = require('child_process');
spawn('C:/Windows/System32/cmd.exe');
I also tried running the following command, but still nothing:
spawn( 'cmd.exe', [ '/c', 'echo ASDASD' ], { stdio: [0, 1, 2] } )
The only possible solution that I see is to create a command.bat:
start cmd.exe /K "cd /D C:\test"
And then use openItem:
"$electron.shell.openItem('command.bat')"
But that would only work on Windows
Solution
I finally found a way to do it on Windows:
var child_process = require('child_process');
child_process.exec("start cmd.exe /K cd /D C:/test");
Notes
You have to add the word start to open a new command window
Instead of cd /D C:/test you can specify any other command, e.g. python
/D is to make sure it will change the current drive automatically, depending on the path specified
/K removes the initial message
Don't use execSync it will lock the app until the terminal (command
prompt) window is closed
As for MacOS, looks like it's possible to do with osascript
osascript -e 'tell application "Terminal" to activate' -e 'tell application "System Events" to tell process "Terminal" to keystroke "t" using command down'
Here is a working example showing how to open a Terminal window at a specific path (~/Desktop for instance) on macOS, from a renderer script:
const { app } = require ('electron').remote;
const atPath = app.getPath ('desktop');
const { spawn } = require ('child_process');
let openTerminalAtPath = spawn ('open', [ '-a', 'Terminal', atPath ]);
openTerminalAtPath.on ('error', (err) => { console.log (err); });
It should be easy to adapt it to any selected atPath...
As for running other commands, I haven't found a way yet...
And here is the equivalent working code for Linux Mint Cinnamon or Ubuntu:
const { app } = require ('electron').remote;
const terminal = 'gnome-terminal';
const atPath = app.getPath ('desktop');
const { spawn } = require ('child_process');
let openTerminalAtPath = spawn (terminal, { cwd: atPath });
openTerminalAtPath.on ('error', (err) => { console.log (err); });
Please note that the name of the terminal application may be different, depending on the Linux flavor (for instance 'mate-terminal' on Linux Mint MATE), and also that the full path to the application can be explicitly defined, to be on the safe side:
const terminal = '/usr/bin/gnome-terminal';
HTH...

Resources