1

When launching a process through winapi calls (CreateProcessAsUserW for instance), you get a process and thread handle. With this you could get the PID of the process and with that you could do a Process.GetProcessById call. However, when the process has already terminated in the meantime this call will fail.

Is there a way to get a Process object by handle instead? I'm now using GetExitCodeProcess to at least get some info, but I would rather return a Process object with the relevant properties set.

Ertyui
  • 858
  • 9
  • 18
  • As long as you hold on to the process handle, `GetProcessById` will succeed. Or should, anyway (I guess, but then again, .NET code isn't exactly the epitome of code quality). – IInspectable Mar 22 '22 at 11:17
  • @IInspectable It evidently does not. If the process has disappeared from the task manager (but [opened handles remain](https://devblogs.microsoft.com/oldnewthing/20110107-00/?p=11803)), `GetProcessById` will fail. [`OpenProcess`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess) will succeed. – GSerg Mar 22 '22 at 11:52
  • So then, my assessment on the relative quality of .NET's library implementation is accurate. Thanks for the confirmation. – IInspectable Mar 22 '22 at 12:08
  • @IInspectable It [enumerates](https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System/services/monitoring/system/diagnosticts/ProcessManager.cs#L535) all processes and throws an [artificial exception](https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System/services/monitoring/system/diagnosticts/Process.cs#L1513) if the requested id is not among them (which is indeed [silly](https://devblogs.microsoft.com/oldnewthing/20071109-00/?p=24553)). So really it's `EnumProcesses` that is to blame? – GSerg Mar 22 '22 at 12:16
  • 3
    No, `EnumProcesses` is fine. It's the fact that the library authors decided to choose an API that's inappropriate to solve the particular problem to get a process handle by ID. I mean, there's `OpenProcess`, that **literally** takes a process ID, but .NET doesn't use it in its implementation of `GetProcessById`. – IInspectable Mar 22 '22 at 12:33
  • Yeah I agree, it's really just the `GetProcessById` implementation which is not good. Or the absence of a `GetProcessByNativeHandle` call, or something like that. I didn't spot any alternative constructors/factory functions which I could "exploit" to get what I want either. And I'd rather abstain using reflection to set the relevant private members of Process, to make it act as a proper Process object. – Ertyui Mar 22 '22 at 15:08
  • 2
    @Ertyui Well, you [can](https://stackoverflow.com/questions/708952/how-to-instantiate-an-object-with-a-private-constructor-in-c#comment65026579_708976) call the [private constructor](https://github.com/microsoft/referencesource/blob/5697c29004a34d80acdaf5742d7e699022c64ecd/System/services/monitoring/system/diagnosticts/Process.cs#L1517). – GSerg Mar 22 '22 at 21:26
  • @GSerg I tried that, but got a "Constructor on type 'System.Diagnostics.Process' not found." This is what I did: `Process new_process = Activator.CreateInstance(typeof(Process), ".", false, (int)pid, null) as Process;` I guess the constructor just doesn't exist in the assembly? :/ – Ertyui Mar 23 '22 at 12:26
  • @GSerg Ah wait, your linked-to example does work. I had overlooked the overload that allows both binding to the private constructor and passing arguments :) I'll go that route then. I know it's not the best/future proof. But it does make it much cleaner.. – Ertyui Mar 23 '22 at 12:32

1 Answers1

2

I "solved" the problem by doing the following:

// Construct Process object through private constructor, taking the PID. This also works when the process has already exited.
Process new_process = Activator.CreateInstance(typeof(Process), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { ".", false, (int)pid, null }, null, null) as Process;

// Wrap the handle and give ownership to wrapper
SafeProcessHandle safe_handle = new SafeProcessHandle((IntPtr)process_handle, true);

// Set interal process handle for Process object through private method. This prevents the .ExitCode accessor to throw an exception.
typeof(Process).GetMethod("SetProcessHandle", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(new_process, new object[] { safe_handle });

This works, but is kinda not great. Because it accesses two private functions through reflection. For more info about the internals of the Process object please have a look here.

Ertyui
  • 858
  • 9
  • 18