cd /news/developer-tools/wmic-is-gone-and-your-node-process-t… · home topics developer-tools article
[ARTICLE · art-108105] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

wmic is gone and your Node process tree is silently wrong on Windows 11

A developer discovered that the Windows 11 removal of WMIC silently breaks Node.js process tree utilities like pidtree, causing process trees to appear empty. The issue stems from pidtree 0.6.0 shelling out to wmic, which fails with ENOENT on modern Windows, and the failure is silently swallowed by Promise.allSettled, making it look like a process has no children. The developer recommends upgrading to pidtree 1.0.0 or using a single Get-CimInstance snapshot for efficient process tree queries.

read6 min views1 publishedAug 23, 2026

The last post I wrote here ended on a line I had to go and check: killing a shell does not kill what the shell started. This is what I found underneath it, and one of the things is a dependency a lot of people have without knowing.

Numbers below come from Windows 11 Home, build 26200, in August 2026. Run the commands yourself, they take a minute. I work on NestMux, which is where I hit this, but nothing here is specific to it.

A terminal pane running a coding agent reported 77 MB of memory. That is roughly the PowerShell host and nothing else. The agent inside it, and the dev server the agent had started, were not being counted.

So the process tree was empty. Every pane looked like an idle shell.

Get-Command wmic
Get-Command : The term 'wmic' is not recognized as the name of a cmdlet...

Microsoft deprecated WMIC in Windows 10 21H1 and started removing it from the image in Windows 11 22H2. On a current install it is simply not there. This is documented and it was announced, and it is still going to break things for years, because wmic

is buried inside libraries rather than in the code people wrote.

pidtree

is the standard way to get a process tree from Node. It backs pkill

-style behavior in a lot of tooling and it has millions of downloads a week. On Windows, version 0.6.0 shells out to wmic

.

npm i pidtree@0.6.0
node -e "require('pidtree')(process.pid,{root:true}).then(console.log).catch(e=>console.log('FAIL:',e.message))"
FAIL: spawn wmic ENOENT

Version 1.0.0 fixed it. It still tries wmic

first, and falls back to PowerShell Get-CimInstance

when that fails:

npm i pidtree@1.0.0
node -e "const m=require('pidtree');(m.default||m)(process.pid,{root:true}).then(console.log)"
OK [ 30520, 36000, 29244 ]

So check what you actually resolve, not what your package.json

says. A caret range of ^0.6.0

will never reach 1.0.0. That was our case: the range said ^0.6.0

, the lockfile said 0.6.0

, and every call on Windows was throwing ENOENT

into a Promise.allSettled

that treated the rejection as "no children" and moved on. It was not an error anyone saw. It was a tree of size one.

That is the part worth repeating. This failure does not look like a failure. It looks like a process with no children.

If you cannot upgrade, the replacement is Get-CimInstance Win32_Process

. The instinct is to query per PID with a filter. Do not:

Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId

1..5 | ForEach-Object { Get-CimInstance Win32_Process -Filter "ParentProcessId=$PID" }

One full snapshot of 396 processes costs less than five filtered queries. The cost is in starting the CIM call, not in the rows. Take one snapshot per polling cycle, build a parent -> children[]

map from it, and walk the map in memory for every PID you care about. If you are polling several panes every few seconds, this is the difference between a background cost you can ignore and one you cannot.

Now the part from the previous post, with the actual output.

Start a grandchild. Root cmd

spawns a cmd

, which spawns timeout

:

$g = Start-Process cmd -PassThru -WindowStyle Hidden -ArgumentList '/c','cmd /c timeout /t 90 /nobreak'
@(Get-CimInstance Win32_Process -Filter "Name='timeout.exe'").Count

Kill the root and only the root:

Stop-Process -Id $g.Id -Force
@(Get-CimInstance Win32_Process -Filter "Name='timeout.exe'").Count

Still alive. Windows has no process groups in the POSIX sense and no automatic reparenting cleanup. Stop-Process

, and process.kill(pid)

from Node, both terminate exactly one process. The descendants keep running, holding their file handles and their ports, with a parent PID that now points at a dead process.

taskkill /F /T

walks it properly:

taskkill /F /T /PID $g.Id
SUCCESS: the process with PID 29828 (child process of PID 34112) has been terminated.
SUCCESS: the process with PID 39928 (child process of PID 37072) has been terminated.
SUCCESS: the process with PID 34112 (child process of PID 37072) has been terminated.
SUCCESS: the process with PID 37072 (child process of PID 39620) has been terminated.

Deepest first, then up. That ordering is the whole point: it resolves the tree while the links are still intact.

Which gives you the trap. If you kill the root first and then go looking for the descendants, the links you needed are gone. The orphans are still there, they are still holding whatever they were holding, and nothing connects them to the thing you were trying to clean up. Resolve the tree first, or hand the root to taskkill /F /T

and let it do both.

One hazard while you are in here: Windows reuses PIDs, and aggressively. A PID you cached thirty seconds ago and are about to force-kill may now be something else. If you keep a PID map across polling cycles, validate it against a fresh snapshot before you kill anything.

The uncomfortable case. A coding agent starts a dev server in the background. On Windows that server frequently ends up with no usable ancestry back to the pane: the intermediate process has exited, the parent PID is stale or recycled, and the binary path is just node.exe

. The command line does not carry the worktree it belongs to either.

What is left is the process's current directory. If the server's cwd is inside a given worktree, it belongs to that worktree.

There is no Node API for reading another process's cwd on Windows. On Linux it is /proc/<pid>/cwd

, on macOS it is lsof

, and on Windows you go through the kernel:

const koffi = require('koffi')
const ntdll = koffi.load('ntdll.dll')
const NtQueryInformationProcess = ntdll.func(
  'long __stdcall NtQueryInformationProcess(void*, int, _Out_ uint8_t*, uint32_t, _Out_ uint32_t*)'
)
// OpenProcess with PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
// then NtQueryInformationProcess to get PROCESS_BASIC_INFORMATION,
// then ReadProcessMemory to walk:
//   PEB + 0x20 -> ProcessParameters -> CurrentDirectory (UNICODE_STRING)

The offsets are x64 Windows 10 and 11. They are not a public contract, which is the honest caveat here: this is a documented-enough structure that tooling has relied on it for twenty years, and it is still something Microsoft can move.

Two things you will hit immediately. Elevated processes and processes from another user reject OpenProcess

with access denied, and you get nothing back for them, so whatever feature you built on this silently does not apply to Docker Desktop and to services. Log those once per PID rather than per poll, otherwise a five second polling loop buries the console.

pidtree

below 1.0.0. If you do, your Windows process trees are size one and nothing told you.process.kill

and Stop-Process

are not tree kills. taskkill /F /T

is.If you maintain something that manages child processes on Windows and you have a cleaner answer than the PEB walk, I would like to hear it. That is the part of this I am least happy with.

── more in #developer-tools 4 stories · sorted by recency
── more on @microsoft 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/wmic-is-gone-and-you…] indexed:0 read:6min 2026-08-23 ·