Last active
May 2, 2023 11:40
-
-
Save anaisbetts/59eca20df7f55cbb2f18ce7db7fe393f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public async Task KillProcessNicely(uint pid, int delayInMs = 3*1000) | |
{ | |
// Ugh, this is gross - https://stackoverflow.com/questions/16653517/how-best-to-post-wm-quit-to-a-running-process | |
var tids = NativeMethods.GetAllTopLevelWindowsInZOrder() | |
.Select(hwnd => { | |
uint wndpid = 0; | |
var tid = NativeMethods.GetWindowThreadProcessId(hwnd, out wndpid); | |
if (wndpid != pid) { | |
return (uint)0; | |
} | |
return tid; | |
}) | |
.ToArray(); | |
if (tids.Length == 0) { | |
// NB: They somehow don't have a message queue? Straight | |
// to Murdertown it is. | |
try { | |
var ps = Process.GetProcessById((int)pid); | |
ps.Kill(); | |
} catch (Exception ex) { | |
this.Log().Warn(ex, $"Failed to force-kill PID 0x{pid:x}"); | |
} | |
} | |
// Post WM_QUIT to any thread queue we can find | |
foreach (var tid in tids) { | |
if (tid < 1) continue; | |
this.Log().Info($"Attempting to post WM_QUIT to PID 0x{pid:x}"); | |
NativeMethods.PostThreadMessage(tid, 0x12 /* WM_QUIT */, IntPtr.Zero, IntPtr.Zero); | |
} | |
Process proc; | |
try { | |
proc = Process.GetProcessById((int)pid); | |
} catch (Exception ex) { | |
// Process might have already bought it? | |
this.Log().Warn(ex, $"Failed to OpenProcess on PID 0x{pid:x}"); | |
return; | |
} | |
try { | |
var cts = new CancellationTokenSource(); | |
cts.CancelAfter(delayInMs); | |
await NativeMethods.WaitForProcessExit(pid, cts.Token); | |
} catch (OperationCanceledException) { | |
proc.Kill(); | |
} catch (Exception ex) { | |
// More race conditions | |
this.Log().Warn(ex, $"Failed to force-kill PID 0x{pid:x}"); | |
return; | |
} | |
} |
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetTopWindow(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true, EntryPoint = "GetWindow")]
public static extern IntPtr GetNextWindow(IntPtr hwnd, GNWDirection wCmd);
public static IEnumerable<IntPtr> GetAllTopLevelWindowsInZOrder()
{
var ret = GetTopWindow(IntPtr.Zero);
if (ret == IntPtr.Zero) throw new Win32Exception();
do {
yield return ret;
ret = GetNextWindow(ret, GNWDirection.GW_HWNDNEXT);
} while (ret != IntPtr.Zero);
}
public static Task WaitForProcessExit(uint pid, CancellationToken cs)
{
try {
var p = Process.GetProcessById((int)pid);
return Task.Factory.StartNew(() => {
while (!p.WaitForExit(1000)) {
cs.ThrowIfCancellationRequested();
}
}, TaskCreationOptions.LongRunning);
} catch (Exception e) {
return Task.FromException(e);
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I couldn't find the following pinvoke references. Can you reference them somewhere?
NativeMethods.GetAllTopLevelWindowsInZOrder()
NativeMethods.WaitForProcessExit(pid, cts.Token)
@anaisbetts ?