import { execFileSync } from "node:child_process" ;
import fs from "node:fs" ;
import path from "node:path" ;
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js" ;
const DEFAULT_SYSTEM_ROOT = "C:\\Windows" ;
const DEFAULT_PROGRAM_FILES = "C:\\Program Files" ;
const DEFAULT_PROGRAM_FILES_X86 = "C:\\Program Files (x86)" ;
const WINDOWS_NT_CURRENT_VERSION_KEY = "HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion" ;
const WINDOWS_CURRENT_VERSION_KEY = "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion" ;
const REG_QUERY_TIMEOUT_MS = 5 _000 ;
type QueryRegistryValue = (key: string, valueName: string) => string | null ;
type IsReadableFile = (filePath: string) => boolean ;
type WindowsInstallRootsTestOverrides = {
queryRegistryValue?: QueryRegistryValue;
isReadableFile?: IsReadableFile;
};
export type WindowsInstallRoots = {
systemRoot: string;
programFiles: string;
programFilesX86: string;
programW6432: string | null ;
};
let queryRegistryValueFn: QueryRegistryValue = defaultQueryRegistryValue;
let isReadableFileFn: IsReadableFile = defaultIsReadableFile;
let cachedProcessInstallRoots: WindowsInstallRoots | null = null ;
function defaultIsReadableFile(filePath: string): boolean {
try {
fs.accessSync(filePath, fs.constants.R_OK);
return true ;
} catch {
return false ;
}
}
function trimTrailingSeparators(value: string): string {
const parsed = path.win32.parse(value);
let trimmed = value;
while (trimmed.length > parsed.root.length && /[\\/]/.test(trimmed.at(-1 ) ?? "" )) {
trimmed = trimmed.slice(0 , -1 );
}
return trimmed;
}
/**
* Windows install roots should be local absolute directories , not drive - relative
* paths , UNC shares , or PATH - like lists that could widen trust unexpectedly .
*/
export function normalizeWindowsInstallRoot(raw: string | undefined): string | null {
if (typeof raw !== "string" ) {
return null ;
}
const trimmed = raw.trim();
if (
!trimmed ||
trimmed.includes("\0" ) ||
trimmed.includes("\r" ) ||
trimmed.includes("\n" ) ||
trimmed.includes(";" )
) {
return null ;
}
const normalized = trimTrailingSeparators(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)) {
return null ;
}
if (normalized.length <= parsed.root.length) {
return null ;
}
return normalized;
}
function getEnvValueCaseInsensitive(
env: Record<string, string | undefined>,
expectedKey: string,
): string | undefined {
const direct = env[expectedKey];
if (direct !== undefined) {
return direct;
}
const upper = expectedKey.toUpperCase();
const actualKey = Object.keys(env).find((key) => key.toUpperCase() === upper);
return actualKey ? env[actualKey] : undefined;
}
function getWindowsRegExeCandidates(env: Record<string, string | undefined>): readonly string[] {
const seen = new Set<string>();
const candidates: string[] = [];
for (const root of [
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "SystemRoot" )),
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "WINDIR" )),
DEFAULT_SYSTEM_ROOT,
]) {
if (!root) {
continue ;
}
const key = normalizeLowercaseStringOrEmpty(root);
if (seen.has(key)) {
continue ;
}
seen.add(key);
candidates.push(path.win32.join(root, "System32" , "reg.exe" ));
}
return candidates;
}
function locateWindowsRegExe(env: Record<string, string | undefined> = process.env): string | null {
for (const candidate of getWindowsRegExeCandidates(env)) {
if (isReadableFileFn(candidate)) {
return candidate;
}
}
return null ;
}
function escapeRegex(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&" );
}
function parseRegQueryValue(stdout: string, valueName: string): string | null {
const pattern = new RegExp(`^\\s*${escapeRegex(valueName)}\\s+REG_[A-Z0-9 _]+\\s+(.+)$`, "im" );
const match = stdout.match(pattern);
return match?.[1 ]?.trim() || null ;
}
function runRegQuery(
regExe: string,
key: string,
valueName: string,
use64BitView: boolean ,
): string {
const args = ["query" , key, "/v" , valueName];
if (use64BitView) {
args.push("/reg:64" );
}
return execFileSync(regExe, args, {
encoding: "utf8" ,
stdio: ["ignore" , "pipe" , "ignore" ],
timeout: REG_QUERY_TIMEOUT_MS,
windowsHide: true ,
});
}
function defaultQueryRegistryValue(key: string, valueName: string): string | null {
const regExe = locateWindowsRegExe(process.env);
if (!regExe) {
return null ;
}
for (const use64BitView of [true , false ]) {
try {
const stdout = runRegQuery(regExe, key, valueName, use64BitView);
const parsed = parseRegQueryValue(stdout, valueName);
if (parsed) {
return parsed;
}
} catch {
// Keep trying alternate registry views or fallbacks below.
}
}
return null ;
}
function getRegistryInstallRoots(): Partial<WindowsInstallRoots> {
return {
systemRoot:
normalizeWindowsInstallRoot(
queryRegistryValueFn(WINDOWS_NT_CURRENT_VERSION_KEY, "SystemRoot" ) ?? undefined,
) ?? undefined,
programFiles:
normalizeWindowsInstallRoot(
queryRegistryValueFn(WINDOWS_CURRENT_VERSION_KEY, "ProgramFilesDir" ) ?? undefined,
) ?? undefined,
programFilesX86:
normalizeWindowsInstallRoot(
queryRegistryValueFn(WINDOWS_CURRENT_VERSION_KEY, "ProgramFilesDir (x86)" ) ?? undefined,
) ?? undefined,
programW6432:
normalizeWindowsInstallRoot(
queryRegistryValueFn(WINDOWS_CURRENT_VERSION_KEY, "ProgramW6432Dir" ) ?? undefined,
) ?? undefined,
};
}
function buildWindowsInstallRoots(
env: Record<string, string | undefined>,
useRegistryRoots: boolean ,
): WindowsInstallRoots {
const registryRoots = useRegistryRoots ? getRegistryInstallRoots() : {};
const envProgramW6432 = normalizeWindowsInstallRoot(
getEnvValueCaseInsensitive(env, "ProgramW6432" ),
);
const programW6432 = registryRoots.programW6432 ?? envProgramW6432 ?? null ;
return {
systemRoot:
registryRoots.systemRoot ??
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "SystemRoot" )) ??
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "WINDIR" )) ??
DEFAULT_SYSTEM_ROOT,
programFiles:
registryRoots.programFiles ??
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "ProgramFiles" )) ??
programW6432 ??
DEFAULT_PROGRAM_FILES,
programFilesX86:
registryRoots.programFilesX86 ??
normalizeWindowsInstallRoot(getEnvValueCaseInsensitive(env, "ProgramFiles(x86)" )) ??
DEFAULT_PROGRAM_FILES_X86,
programW6432,
};
}
export function getWindowsInstallRoots(
env: Record<string, string | undefined> = process.env,
): WindowsInstallRoots {
if (env === process.env) {
cachedProcessInstallRoots ??= buildWindowsInstallRoots(env, true );
return cachedProcessInstallRoots;
}
return buildWindowsInstallRoots(env, false );
}
export function getWindowsProgramFilesRoots(
env: Record<string, string | undefined> = process.env,
): readonly string[] {
const roots = getWindowsInstallRoots(env);
const seen = new Set<string>();
const result: string[] = [];
for (const value of [roots.programW6432, roots.programFiles, roots.programFilesX86]) {
if (!value) {
continue ;
}
const key = normalizeLowercaseStringOrEmpty(value);
if (seen.has(key)) {
continue ;
}
seen.add(key);
result.push(value);
}
return result;
}
export function _resetWindowsInstallRootsForTests(
overrides: WindowsInstallRootsTestOverrides = {},
): void {
queryRegistryValueFn = overrides.queryRegistryValue ?? defaultQueryRegistryValue;
isReadableFileFn = overrides.isReadableFile ?? defaultIsReadableFile;
cachedProcessInstallRoots = null ;
}
export const _private = {
getWindowsRegExeCandidates,
locateWindowsRegExe,
};
Messung V0.5 in Prozent C=99 H=98 G=98
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-10)
¤
*© Formatika GbR, Deutschland