AikidoSec-safe-chain/packages/safe-chain/src/installation/installOnWindows.js
Sander Declerck 92ec4e47f9
PR comments
2026-02-11 14:23:57 +01:00

153 lines
4.6 KiB
JavaScript

import { tmpdir } from "os";
import { unlinkSync } from "fs";
import { join } from "path";
import { execSync } from "child_process";
import { ui } from "../environment/userInteraction.js";
import { printVerboseAndSafeSpawn, safeSpawn } from "../utils/safeSpawn.js";
import { downloadAgentToFile, getAgentVersion } from "./downloadAgent.js";
const WINDOWS_SERVICE_NAME = "SafeChainAgent";
const WINDOWS_APP_NAME = "SafeChain Agent";
export async function installOnWindows() {
if (!(await isRunningAsAdmin())) {
ui.writeError("Administrator privileges required.");
ui.writeInformation(
"Please run this command in an elevated terminal (Run as Administrator).",
);
return;
}
const msiPath = join(tmpdir(), `SafeChainUltimate-${Date.now()}.msi`);
ui.emptyLine();
ui.writeInformation(`📥 Downloading SafeChain Ultimate ${getAgentVersion()}`);
ui.writeVerbose(`Destination: ${msiPath}`);
const result = await downloadAgentToFile(msiPath);
if (!result) {
ui.writeError("No download available for this platform/architecture.");
return;
}
try {
ui.emptyLine();
await stopServiceIfRunning();
await uninstallIfInstalled();
ui.writeInformation("⚙️ Installing SafeChain Ultimate...");
await runMsiInstaller(msiPath);
ui.emptyLine();
ui.writeInformation(
"✅ SafeChain Ultimate installed and started successfully!",
);
ui.emptyLine();
} finally {
ui.writeVerbose(`Cleaning up temporary file: ${msiPath}`);
cleanup(msiPath);
}
}
async function isRunningAsAdmin() {
// Uses Windows Security API to check if current process has admin privileges.
// Returns "True" or "False" as a string.
const result = await safeSpawn(
"powershell",
[
"-Command",
"([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)",
],
{ stdio: "pipe" },
);
return result.status === 0 && result.stdout.trim() === "True";
}
async function uninstallIfInstalled() {
// Query Win32_Product via WMI to find the installed SafeChain Agent.
// If found, outputs the product GUID (e.g., "{12345678-1234-...}") needed for msiexec uninstall.
ui.writeVerbose(`Finding product code with PowerShell`);
let productCode;
try {
productCode = execSync(
`powershell -Command "$app = Get-WmiObject -Class Win32_Product -Filter \\"Name='${WINDOWS_APP_NAME}'\\"; if ($app) { Write-Output $app.IdentifyingNumber }"`,
{ encoding: "utf8" },
).trim();
} catch {
ui.writeVerbose("No existing installation found (fresh install).");
return;
}
if (!productCode) {
ui.writeVerbose("No existing installation found (fresh install).");
return;
}
ui.writeInformation("🗑️ Removing previous installation...");
ui.writeVerbose(`Found product code: ${productCode}`);
// Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec)
// Options:
// - /x: Uninstalls the package.
// - /qn: Specifies there's no UI during the installation process.
// - /norestart: Stops the device from restarting after the installation completes.
const uninstallResult = await printVerboseAndSafeSpawn(
"msiexec",
["/x", productCode, "/qn", "/norestart"],
{ stdio: "inherit" },
);
if (uninstallResult.status !== 0) {
throw new Error(`Uninstall failed (exit code: ${uninstallResult.status})`);
}
}
/**
* @param {string} msiPath
*/
async function runMsiInstaller(msiPath) {
// Use msiexec to run the msi installer quitely (https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec)
// Options:
// - /i: Specifies normal installation
// - /qn: Specifies there's no UI during the installation process.
const result = await printVerboseAndSafeSpawn(
"msiexec",
["/i", msiPath, "/qn"],
{
stdio: "inherit",
},
);
if (result.status !== 0) {
throw new Error(`MSI installer failed (exit code: ${result.status})`);
}
}
async function stopServiceIfRunning() {
ui.writeInformation("⏹️ Stopping running service...");
const result = await printVerboseAndSafeSpawn(
"net",
["stop", WINDOWS_SERVICE_NAME],
{
stdio: "pipe",
},
);
if (result.status !== 0) {
ui.writeVerbose("Service not running (will start after installation).");
}
}
/**
* @param {string} msiPath
*/
function cleanup(msiPath) {
try {
unlinkSync(msiPath);
} catch {
ui.writeVerbose("Failed to clean up temporary installer file.");
}
}