Use safeSpawn instead of execSync

This commit is contained in:
Sander Declerck 2026-01-19 15:31:41 +01:00
parent 9b61a325fa
commit 8b189443b7
No known key found for this signature in database

View file

@ -1,8 +1,8 @@
import { arch, tmpdir } from "os"; import { arch, tmpdir } from "os";
import { unlinkSync } from "fs"; import { unlinkSync } from "fs";
import { join } from "path"; import { join } from "path";
import { execSync } from "child_process";
import { ui } from "../environment/userInteraction.js"; import { ui } from "../environment/userInteraction.js";
import { safeSpawn } from "../utils/safeSpawn.js";
import { import {
getAgentDownloadUrl, getAgentDownloadUrl,
getAgentVersion, getAgentVersion,
@ -10,7 +10,7 @@ import {
} from "./downloadAgent.js"; } from "./downloadAgent.js";
export async function installOnWindows() { export async function installOnWindows() {
if (!isRunningAsAdmin()) { if (!(await isRunningAsAdmin())) {
ui.writeError("Administrator privileges required."); ui.writeError("Administrator privileges required.");
ui.writeInformation( ui.writeInformation(
"Please run this command in an elevated terminal (Run as Administrator).", "Please run this command in an elevated terminal (Run as Administrator).",
@ -32,18 +32,18 @@ export async function installOnWindows() {
await downloadFile(downloadUrl, msiPath); await downloadFile(downloadUrl, msiPath);
ui.emptyLine(); ui.emptyLine();
stopServiceIfRunning(); await stopServiceIfRunning();
uninstallIfInstalled(); await uninstallIfInstalled();
// Wait a moment for uninstall to complete // Wait a moment for uninstall to complete
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
ui.writeInformation("⚙️ Installing SafeChain Agent..."); ui.writeInformation("⚙️ Installing SafeChain Agent...");
runMsiInstaller(msiPath); await runMsiInstaller(msiPath);
ui.emptyLine(); ui.emptyLine();
ui.writeInformation("🚀 Starting SafeChain Agent service..."); ui.writeInformation("🚀 Starting SafeChain Agent service...");
startService(); await startService();
ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`); ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`);
cleanup(msiPath); cleanup(msiPath);
@ -53,13 +53,9 @@ export async function installOnWindows() {
ui.emptyLine(); ui.emptyLine();
} }
function isRunningAsAdmin() { async function isRunningAsAdmin() {
try { const result = await safeSpawn("net", ["session"], { stdio: "ignore" });
execSync("net session", { stdio: "ignore" }); return result.status === 0;
return true;
} catch {
return false;
}
} }
function getWindowsArchitecture() { function getWindowsArchitecture() {
@ -69,29 +65,31 @@ function getWindowsArchitecture() {
throw new Error(`Unsupported architecture: ${nodeArch}`); throw new Error(`Unsupported architecture: ${nodeArch}`);
} }
function uninstallIfInstalled() { async function uninstallIfInstalled() {
try { // Use PowerShell to find the product code, then use msiexec to uninstall
// Use PowerShell to find the product code, then use msiexec to uninstall // This is the modern alternative to wmic which is deprecated
// This is the modern alternative to wmic which is deprecated const powershellScript = `$app = Get-WmiObject -Class Win32_Product -Filter "Name='SafeChain Agent'"; if ($app) { Write-Output $app.IdentifyingNumber }`;
const findProductCodeCmd = `powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='SafeChain Agent'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`; ui.writeVerbose(`Finding product code with PowerShell`);
ui.writeVerbose(`Finding product code: ${findProductCodeCmd}`);
const productCode = execSync(findProductCodeCmd, { const result = await safeSpawn("powershell", ["-Command", powershellScript], {
encoding: "utf8", stdio: "pipe",
}).trim(); });
if (productCode) { if (result.status !== 0) {
ui.writeInformation("🗑️ Removing previous installation..."); ui.writeVerbose("No existing installation found (fresh install).");
ui.writeVerbose(`Found product code: ${productCode}`); return;
ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`); }
execSync(`msiexec /x ${productCode} /qn /norestart`, {
stdio: "inherit", const productCode = result.stdout.trim();
});
} else { if (productCode) {
ui.writeVerbose("No existing installation found (fresh install)."); ui.writeInformation("🗑️ Removing previous installation...");
} ui.writeVerbose(`Found product code: ${productCode}`);
} catch { ui.writeVerbose(`Running: msiexec /x ${productCode} /qn /norestart`);
// Not installed or uninstall failed, which is fine for a fresh install await safeSpawn("msiexec", ["/x", productCode, "/qn", "/norestart"], {
stdio: "inherit",
});
} else {
ui.writeVerbose("No existing installation found (fresh install)."); ui.writeVerbose("No existing installation found (fresh install).");
} }
} }
@ -99,40 +97,43 @@ function uninstallIfInstalled() {
/** /**
* @param {string} msiPath * @param {string} msiPath
*/ */
function runMsiInstaller(msiPath) { async function runMsiInstaller(msiPath) {
// /i = install // /i = install
// /qn = quiet mode (no UI) // /qn = quiet mode (no UI)
ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`); ui.writeVerbose(`Running: msiexec /i "${msiPath}" /qn`);
execSync(`msiexec /i "${msiPath}" /qn`, { stdio: "inherit" }); // noopengrep this is ok, we control the msiPath await safeSpawn("msiexec", ["/i", msiPath, "/qn"], { stdio: "inherit" });
} }
function stopServiceIfRunning() { async function stopServiceIfRunning() {
try { ui.writeInformation("⏹️ Stopping running service...");
ui.writeInformation("⏹️ Stopping running service..."); ui.writeVerbose('Running: net stop "SafeChainAgent"');
ui.writeVerbose('Running: net stop "SafeChainAgent"'); const result = await safeSpawn("net", ["stop", "SafeChainAgent"], {
execSync('net stop "SafeChainAgent"', { stdio: "inherit" }); stdio: "inherit",
} catch { });
// Service is not running or doesn't exist, which is fine
if (result.status !== 0) {
ui.writeVerbose("Service not running (will start after installation)."); ui.writeVerbose("Service not running (will start after installation).");
} }
} }
function startService() { async function startService() {
try { // Check if service is already running
// Check if service is already running ui.writeVerbose('Checking service status: sc query "SafeChainAgent"');
ui.writeVerbose('Checking service status: sc query "SafeChainAgent"'); const queryResult = await safeSpawn("sc", ["query", "SafeChainAgent"], {
const status = execSync('sc query "SafeChainAgent"', { encoding: "utf8" }); stdio: "pipe",
});
if (status.includes("RUNNING")) { if (queryResult.status === 0 && queryResult.stdout.includes("RUNNING")) {
ui.writeVerbose("SafeChain Agent service is already running."); ui.writeVerbose("SafeChain Agent service is already running.");
return; return;
} }
} catch {
if (queryResult.status !== 0) {
ui.writeVerbose("Service not found or query failed, attempting to start."); ui.writeVerbose("Service not found or query failed, attempting to start.");
} }
ui.writeVerbose('Running: net start "SafeChainAgent"'); ui.writeVerbose('Running: net start "SafeChainAgent"');
execSync('net start "SafeChainAgent"', { stdio: "inherit" }); await safeSpawn("net", ["start", "SafeChainAgent"], { stdio: "inherit" });
} }
/** /**