Skip to content

Commit 5b6cd57

Browse files
dorlugasigalCopilot
andcommitted
fix(windows): use tasklist fallback when wmic unavailable for shell detection
On newer Windows builds wmic is removed, causing getWindowsAncestors to silently fail and default to COMSPEC (cmd.exe) even when running from pwsh. Add tasklist as a fallback strategy, and use PSModulePath env var heuristic when only intermediary processes (node.exe) are found in the tree. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 720043a commit 5b6cd57

2 files changed

Lines changed: 64 additions & 29 deletions

File tree

src/cli/index.js

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -75,48 +75,66 @@ function getWindowsAncestors(startPid, maxDepth = 4) {
7575
const safePid = parseInt(startPid, 10);
7676
if (!Number.isFinite(safePid) || safePid <= 0) return names;
7777

78+
// Strategy 1: wmic (available on older Windows builds)
7879
try {
7980
const result = execFileSync(
8081
'wmic',
8182
['process', 'get', 'Name,ParentProcessId,ProcessId', '/format:csv'],
8283
{ stdio: ['pipe', 'pipe', 'ignore'], encoding: 'utf8', timeout: 5000, windowsHide: true },
8384
);
8485

85-
// Parse CSV output — first non-empty line is the header
8686
const lines = result.split(/\r?\n/).filter((l) => l.trim());
87-
if (lines.length === 0) return names;
88-
89-
const header = lines[0].split(',').map((h) => h.trim());
90-
const nameIdx = header.indexOf('Name');
91-
const pidIdx = header.indexOf('ProcessId');
92-
const ppidIdx = header.indexOf('ParentProcessId');
93-
if (nameIdx === -1 || pidIdx === -1 || ppidIdx === -1) return names;
94-
95-
const processes = new Map();
96-
for (let i = 1; i < lines.length; i++) {
97-
const cols = lines[i].split(',');
98-
if (cols.length <= Math.max(nameIdx, pidIdx, ppidIdx)) continue;
99-
const pid = parseInt(cols[pidIdx], 10);
100-
if (Number.isFinite(pid)) {
101-
processes.set(pid, {
102-
name: cols[nameIdx].trim().toLowerCase(),
103-
ppid: parseInt(cols[ppidIdx], 10),
104-
});
87+
if (lines.length > 0) {
88+
const header = lines[0].split(',').map((h) => h.trim());
89+
const nameIdx = header.indexOf('Name');
90+
const pidIdx = header.indexOf('ProcessId');
91+
const ppidIdx = header.indexOf('ParentProcessId');
92+
if (nameIdx !== -1 && pidIdx !== -1 && ppidIdx !== -1) {
93+
const processes = new Map();
94+
for (let i = 1; i < lines.length; i++) {
95+
const cols = lines[i].split(',');
96+
if (cols.length <= Math.max(nameIdx, pidIdx, ppidIdx)) continue;
97+
const pid = parseInt(cols[pidIdx], 10);
98+
if (Number.isFinite(pid)) {
99+
processes.set(pid, {
100+
name: cols[nameIdx].trim().toLowerCase(),
101+
ppid: parseInt(cols[ppidIdx], 10),
102+
});
103+
}
104+
}
105+
106+
let currentPid = safePid;
107+
for (let i = 0; i < maxDepth; i++) {
108+
const proc = processes.get(currentPid);
109+
if (!proc) break;
110+
log.debug(`Process tree: ${proc.name}`);
111+
names.push(proc.name);
112+
if (!Number.isFinite(proc.ppid) || proc.ppid === 0 || proc.ppid === currentPid) break;
113+
currentPid = proc.ppid;
114+
}
115+
if (names.length > 0) return names;
105116
}
106117
}
118+
} catch (err) {
119+
log.debug(`wmic not available: ${err.message}`);
120+
}
107121

108-
// Walk up the tree in memory — no more subprocess calls
109-
let currentPid = safePid;
110-
for (let i = 0; i < maxDepth; i++) {
111-
const proc = processes.get(currentPid);
112-
if (!proc) break;
113-
log.debug(`Process tree: ${proc.name}`);
114-
names.push(proc.name);
115-
if (!Number.isFinite(proc.ppid) || proc.ppid === 0 || proc.ppid === currentPid) break;
116-
currentPid = proc.ppid;
122+
// Strategy 2: tasklist for direct parent name (always available on Windows)
123+
try {
124+
const result = execFileSync('tasklist', ['/FI', `PID eq ${safePid}`, '/FO', 'CSV', '/NH'], {
125+
stdio: ['pipe', 'pipe', 'ignore'],
126+
encoding: 'utf8',
127+
timeout: 5000,
128+
windowsHide: true,
129+
});
130+
const match = result.match(/"([^"]+)"/);
131+
if (match) {
132+
const name = match[1].toLowerCase();
133+
log.debug(`Process tree (tasklist): ${name}`);
134+
names.push(name);
117135
}
118136
} catch (err) {
119-
log.debug(`Could not query process tree: ${err.message}`);
137+
log.debug(`tasklist failed: ${err.message}`);
120138
}
121139

122140
return names;
@@ -188,6 +206,19 @@ function getDefaultShell() {
188206
log.debug(`Using detected shell: cmd.exe`);
189207
return 'cmd.exe';
190208
}
209+
210+
// Heuristic: PSModulePath env var is set by PowerShell and inherited by children.
211+
// When the tree walk only found intermediaries (node.exe), this detects the real shell.
212+
const psModulePath = (process.env.PSModulePath || '').toLowerCase();
213+
if (psModulePath.includes('\\powershell\\')) {
214+
log.debug('Detected pwsh.exe via PSModulePath');
215+
return 'pwsh.exe';
216+
}
217+
if (psModulePath.includes('\\windowspowershell\\')) {
218+
log.debug('Detected powershell.exe via PSModulePath');
219+
return 'powershell.exe';
220+
}
221+
191222
const fallback = process.env.COMSPEC || 'cmd.exe';
192223
log.debug(`Falling back to: ${fallback}`);
193224
return fallback;

test/cli/cli.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,10 @@ describe('CLI', () => {
530530
const origPlatform = os.platform;
531531
const origExecFileSync = child_process.execFileSync;
532532
const origComspec = process.env.COMSPEC;
533+
const origPSModulePath = process.env.PSModulePath;
533534
os.platform = () => 'win32';
534535
process.env.COMSPEC = 'C:\\Windows\\System32\\cmd.exe';
536+
delete process.env.PSModulePath;
535537
child_process.execFileSync = (cmd) => {
536538
if (cmd === 'wmic') {
537539
return ['Node,Name,ParentProcessId,ProcessId', `PC,explorer.exe,0,${process.ppid}`].join(
@@ -551,6 +553,8 @@ describe('CLI', () => {
551553
child_process.execFileSync = origExecFileSync;
552554
if (origComspec !== undefined) process.env.COMSPEC = origComspec;
553555
else delete process.env.COMSPEC;
556+
if (origPSModulePath !== undefined) process.env.PSModulePath = origPSModulePath;
557+
else delete process.env.PSModulePath;
554558
}
555559
});
556560
});

0 commit comments

Comments
 (0)