Skip to content

Instantly share code, notes, and snippets.

@anaisbetts
Last active May 2, 2023 11:40
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anaisbetts/59eca20df7f55cbb2f18ce7db7fe393f to your computer and use it in GitHub Desktop.
Save anaisbetts/59eca20df7f55cbb2f18ce7db7fe393f to your computer and use it in GitHub Desktop.
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;
}
}
@dougrathbone
Copy link

dougrathbone commented Jan 14, 2020

I couldn't find the following pinvoke references. Can you reference them somewhere?

NativeMethods.GetAllTopLevelWindowsInZOrder()
NativeMethods.WaitForProcessExit(pid, cts.Token)

@anaisbetts ?

@anaisbetts
Copy link
Author

        [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