You’ve got it exactly right — this isn’t “close B,” it’s “make A not see B.”
(@teodor11’s steps are for a different problem — an installer that’s genuinely still running.)
Here’s the thing A won’t tell you: “close B first” is just what A prints when its scan finds B. A doesn’t need B gone — it needs its scan to come back empty. And a scan only ever looks at one surface: B’s process name, B’s window, or a “already running” lock (a named mutex). Break that one signal and both run. Two ways in 
━━━━━━━━━━━━━━━━━━━━━━━━━
The no-reversing shortcut (try this first)
A only scans its own login session and desktop. So put B somewhere A never looks — no hiding, no patching, no VM:
├─
Run B as a second Windows user → RunAsTool (one click, save the login) or AdvancedRun / RunasCs. B lives in another session → A’s scan can’t reach it, B stays fully running
└─
Run B on a separate desktop → Desktops (Sysinternals). FindWindow/EnumWindows only see the caller’s desktop, so A on the main desktop is blind to B’s windows
That alone fixes most “close it first” apps. If A digs deeper, break the exact signal 
━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1 — make A show you how it finds B
Point a tracer at A’s launch and it prints the exact call and the exact name/title/mutex it’s hunting — that one line tells you your fix:
├─
Frida → frida-trace -i "Process32Next*" -i "FindWindow*" -i "OpenMutex*" -p <A> — logs every check with its arguments
├─
WinAPIOverride — no-code: watch the call and force it to return “not found” in the same tool
└─
Dependencies (peek A’s imports first) · Process Monitor (catch a registry/mutex probe)
Match what you see to your move:
| A calls… |
it’s checking |
break it → |
Process32Next · EnumProcesses · NtQuerySystemInformation |
B’s exe name in the process list |
rename/repack B, hide it, or hook the list |
FindWindow · EnumWindows |
B’s window title/class |
rename or hide B’s window |
OpenMutex · CreateMutex |
B’s single-instance lock |
close or rename the mutex |
━━━━━━━━━━━━━━━━━━━━━━━━━
🧬 A scans the process list (by exe name) — 3 ways to vanish B
├─
Rename B’s identity on disk → CFF Explorer (rename the PE + rewrite company/version strings) · Resource Hacker (baked-in strings) · UPX (repack → new hash, beats a hash check). Enough when A only greps a name/hash
├─
Hide B from the list entirely → M00nRise/ProcessHider (ready EXE/PowerShell, hooks NtQuerySystemInformation) · JKornev/hidden (kernel mini-filter — drops B from every enumeration API)
└─
Make A’s own scan lie (surgical) → inject a hook into A that filters B out of the results it gets: Microsoft Detours (official) · MinHook (tiny, hooks NtQuerySystemInformation) · or Frida Interceptor.replace — no recompile
🪟 A scans open windows (title or class) — rename or hide B's window
├─
Rename B’s window at runtime → AutoHotkey one-liner WinSetTitle · window-title-spoofer (rename by PID). A’s FindWindow-by-title stops matching
├─
Hide the window off the enumerable surface → NirCmd win hide title "B" · WinLister (list every window + class, then hide)
└─
First read B’s exact title and class (A may match either) → WinLister / WinSpy++
🔒 A checks a single-instance mutex (the classic 'already running') — close it
├─
See the lock’s real name → Process Explorer (Ctrl+F a mutant name) · WinObj (read the literal string A’s OpenMutex compares against)
├─
Close it live, no restart of B → System Informer → B’s handles → right-click the Mutant → Close · or Handle handle -c from a script
└─
Or sidestep it → run B in a different session (PsExec -u) so its Local\ mutex lands in a separate namespace and never collides
🎯 A is stubborn / you want to read its logic — pin the exact check
├─
Static → Ghidra decompiles A to show the detection routine (which string/mutex/API) before you even run it · PE-bear narrows the imports
├─
Live → x64dbg: attach to A, bp OpenMutexW/FindWindowW, watch the exact value it tests, then NOP the branch that decides “B is running”
└─
If A fights the debugger → ScyllaHide (user-mode) / TitanHide (kernel) hide your debugger so you can still step the scan
🧱 Bulletproof, still no VM — box B so A physically can't reach it
├─
Process Governor — wrap B in a Windows Job object with UI-handle limits, so B can’t touch window objects outside its box (a real enumeration boundary, on bare metal)
└─
ShellRunas / RunasCs — a dedicated second local account for B is the cleanest permanent split; A’s same-user scan never sees another user’s processes or windows
━━━━━━━━━━━━━━━━━━━━━━━━━
The whole trick: A’s block is a scan result, and a scan reads one surface. See which surface, change what B looks like there — or move B to a session A never scans. B keeps running; A opens like B was never there.
“Close it first” is a question, not a lock — answer “nothing’s running” and the door opens.