# -*- coding: utf-8 -*- """proc-parent —— 打印进程父链(纯 stdlib ctypes,⛔ 不依赖 psutil)。 用途(配合 SKILL.md §2j):判断某个进程**是不是某个 WorkBuddy 会话的后台任务**。 python proc-parent.py [pid2 ...] 读数: · 链上出现 `… ← sandbox-cli.exe ← WorkBuddy.exe` ⇒ 是会话/宿主托管的**后台任务** · 链上出现 `services.exe` / `svchost.exe` / `explorer.exe` ⇒ 是**独立进程**(计划任务/启动文件夹/手工起) ⚠️ 本脚本只读(CreateToolhelp32Snapshot)—— ⛔ 不结束、不修改任何进程。 """ import ctypes import ctypes.wintypes as wt import sys TH32CS_SNAPPROCESS = 0x00000002 MAX_PATH = 260 class PROCESSENTRY32(ctypes.Structure): _fields_ = [ ("dwSize", wt.DWORD), ("cntUsage", wt.DWORD), ("th32ProcessID", wt.DWORD), ("th32DefaultHeapID", ctypes.POINTER(ctypes.c_ulong)), ("th32ModuleID", wt.DWORD), ("cntThreads", wt.DWORD), ("th32ParentProcessID", wt.DWORD), ("pcPriClassBase", ctypes.c_long), ("dwFlags", wt.DWORD), ("szExeFile", ctypes.c_char * MAX_PATH), ] def snapshot(): k = ctypes.windll.kernel32 h = k.CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) if h == -1: return {} pe = PROCESSENTRY32() pe.dwSize = ctypes.sizeof(PROCESSENTRY32) out = {} ok = k.Process32First(h, ctypes.byref(pe)) while ok: out[pe.th32ProcessID] = (pe.th32ParentProcessID, pe.szExeFile.decode("mbcs", "replace")) ok = k.Process32Next(h, ctypes.byref(pe)) k.CloseHandle(h) return out def main(): pids = [a for a in sys.argv[1:] if a.isdigit()] if not pids: print("用法: python proc-parent.py [pid2 ...]") return 1 procs = snapshot() for arg in pids: pid = int(arg) print("=== pid %d ===" % pid) seen, cur = set(), pid while cur and cur in procs and cur not in seen: seen.add(cur) ppid, name = procs[cur] print(" %-8d %-22s ppid=%d" % (cur, name, ppid)) cur = ppid if cur and cur not in procs: print(" %-8d (已退出) <- 链在此断" % cur) print() return 0 if __name__ == "__main__": sys.exit(main())