fix(scripts): resolve taskkill from system32

This commit is contained in:
Vincent Koc
2026-06-21 09:03:40 +02:00
parent c22e300084
commit 6b0210a5fd
5 changed files with 117 additions and 81 deletions

View File

@@ -1,12 +1,11 @@
// Runs child commands with process-group signal forwarding and Windows shell normalization.
import { spawn, spawnSync } from "node:child_process";
import { constants as osConstants } from "node:os";
import path from "node:path";
import { buildCmdExeCommandLine } from "../windows-cmd-helpers.mjs";
import { resolveWindowsTaskkillPath } from "./windows-taskkill.mjs";
const FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
const FORCE_KILL_DELAY_MS = 5_000;
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
const managedChildren = new Set();
const signalHandlers = new Map();
@@ -72,46 +71,6 @@ export function terminateManagedChild(
child.kill(signal);
}
function getEnvValueCaseInsensitive(env, expectedKey) {
const direct = env[expectedKey];
if (direct !== undefined) {
return direct;
}
const expected = expectedKey.toUpperCase();
const actualKey = Object.keys(env).find((key) => key.toUpperCase() === expected);
return actualKey ? env[actualKey] : undefined;
}
function normalizeWindowsSystemRoot(raw) {
const trimmed = raw?.trim();
if (
!trimmed ||
trimmed.includes("\0") ||
trimmed.includes("\r") ||
trimmed.includes("\n") ||
trimmed.includes(";")
) {
return null;
}
const normalized = path.win32.normalize(trimmed);
if (!path.win32.isAbsolute(normalized) || normalized.startsWith("\\\\")) {
return null;
}
const parsed = path.win32.parse(normalized);
if (!/^[A-Za-z]:\\$/.test(parsed.root) || normalized.length <= parsed.root.length) {
return null;
}
return normalized.replace(/[\\/]+$/, "");
}
function resolveWindowsTaskkillPath(env = process.env) {
const systemRoot =
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "SystemRoot")) ??
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "WINDIR")) ??
DEFAULT_WINDOWS_SYSTEM_ROOT;
return path.win32.join(systemRoot, "System32", "taskkill.exe");
}
/**
* Run a child command while forwarding termination signals to the managed process group.
*

View File

@@ -0,0 +1,44 @@
// Resolves the Windows taskkill binary without trusting PATH.
import path from "node:path";
const DEFAULT_WINDOWS_SYSTEM_ROOT = "C:\\Windows";
function getEnvValueCaseInsensitive(env, expectedKey) {
const direct = env[expectedKey];
if (direct !== undefined) {
return direct;
}
const expected = expectedKey.toUpperCase();
const actualKey = Object.keys(env).find((key) => key.toUpperCase() === expected);
return actualKey ? env[actualKey] : undefined;
}
function normalizeWindowsSystemRoot(raw) {
const trimmed = raw?.trim();
if (
!trimmed ||
trimmed.includes("\0") ||
trimmed.includes("\r") ||
trimmed.includes("\n") ||
trimmed.includes(";")
) {
return null;
}
const normalized = path.win32.normalize(trimmed);
if (!path.win32.isAbsolute(normalized) || normalized.startsWith("\\\\")) {
return null;
}
const parsed = path.win32.parse(normalized);
if (!/^[A-Za-z]:\\$/.test(parsed.root) || normalized.length <= parsed.root.length) {
return null;
}
return normalized.replace(/[\\/]+$/, "");
}
export function resolveWindowsTaskkillPath(env = process.env) {
const systemRoot =
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "SystemRoot")) ??
normalizeWindowsSystemRoot(getEnvValueCaseInsensitive(env, "WINDIR")) ??
DEFAULT_WINDOWS_SYSTEM_ROOT;
return path.win32.join(systemRoot, "System32", "taskkill.exe");
}

View File

@@ -1,5 +1,6 @@
// Runs a command with inline KEY=value assignments while preserving signal behavior.
import { spawn, spawnSync } from "node:child_process";
import { resolveWindowsTaskkillPath } from "./lib/windows-taskkill.mjs";
const ENV_ASSIGNMENT_RE = /^[A-Za-z_][A-Za-z0-9_]*=/u;
const USAGE = "Usage: node scripts/run-with-env.mjs KEY=value [KEY=value ...] -- command [args...]";
@@ -104,16 +105,17 @@ export function signalRunWithEnvChild(
}
}
if (platform === "win32" && typeof child.pid === "number") {
const taskkillPath = resolveWindowsTaskkillPath();
const args = ["/PID", String(child.pid), "/T"];
if (signal === "SIGKILL") {
args.push("/F");
}
const result = runTaskkill("taskkill", args, { stdio: "ignore" });
const result = runTaskkill(taskkillPath, args, { stdio: "ignore" });
if (!result?.error && result?.status === 0) {
return;
}
if (signal !== "SIGKILL") {
const forceResult = runTaskkill("taskkill", [...args, "/F"], { stdio: "ignore" });
const forceResult = runTaskkill(taskkillPath, [...args, "/F"], { stdio: "ignore" });
if (!forceResult?.error && forceResult?.status === 0) {
return;
}

View File

@@ -1089,6 +1089,10 @@ const TOOLING_SOURCE_TEST_TARGETS = new Map([
["scripts/lib/local-heavy-check-runtime.mjs", ["test/scripts/local-heavy-check-runtime.test.ts"]],
["scripts/lib/kova-report-gate.mjs", ["test/scripts/kova-report-gate.test.ts"]],
["scripts/lib/managed-child-process.mjs", ["test/scripts/managed-child-process.test.ts"]],
[
"scripts/lib/windows-taskkill.mjs",
["test/scripts/managed-child-process.test.ts", "test/scripts/run-with-env.test.ts"],
],
[
"scripts/lib/local-build-metadata.mjs",
[