Reputation: 81429
I am working on a .NET console app which needs to clean up resources on exit. The problem I'm running into is that I don't get any notification if the cmd parent is closed via the console window [X], via Task Manager/Process Explorer or programmatically with WM_CLOSE. I can live with not being able to handle Kill Process from Task Mgr. or ProcExp. WM_CLOSE of the parent console is the most likely way that this application will be closed before it's finished processing.
Here are the events I've tried to register for so far:
AppDomain.CurrentDomain.ProcessExit += CurrentDomainProcessExit;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
Console.CancelKeyPress += ConsoleCancelKeyPress;
Application.ApplicationExit += ApplicationApplicationExit;
Process parentProcess = ProcessInfo.GetParentProcess(
Process.GetCurrentProcess());
parentProcess.Disposed += ParentDisposed;
parentProcess.Exited += ParentExited;
Process grandParentProcess = ProcessInfo.GetParentProcess(parentProcess);
grandParentProcess.Disposed += GrandParentDisposed;
grandParentProcess.Exited += GrandParentExited;
These events fire properly when I send a CTRL+C from the console or the application finishes uninterrupted. But none of them fire when the parent app (cmd console) is closed. (The parent/grandparent processes aren't CLR so I'm not sure I would ever receive those Disposed/Exited events. Those are just shots in the dark.) I've looked at some pInvoke stuff but I would rather not go down that road if .NET is an option.
Is there a way to detect and handle a shutdown in these situations? I am open to any .NET, pInvoke/Win32/C/C++ solution. (Basically any way it can be done on the Windows platform.)
Thanks.
P.S. I'm still working with .NET 2.0 so I can't use anything introduced in .NET 3.0+
Upvotes: 3
Views: 4276
Reputation: 86708
The Exited
event should fire whenever you set EnableRaisingEvents = true
on the Process
, regardless of whether the process is .Net, native, or anything else. If you're actually seeing the Process
exit without the Exited
event firing, can you post a small reproduction scenario?
Also, the Disposed
event is useless for your scenario. It only fires when you call Dispose()
on the Process
object instance in your process, and has nothing whatsoever to do with anything that happens within the OS process that the object refers to.
Upvotes: 2
Reputation: 74197
Part of your problem may be that the ProcessInfo
class in your example is part of the System.Web
namespace. It (to quote MSDN) "returns information about ASP.Net worker processes that are running under the ASP.Net Process Model." This would seem unlikely to return anything terribly useful to your command-line app.
You need to use WMI via the System.Management namespace. The following sample should do the trick and get you the System.Diagnostics.Process object for the current process's immediate parent. This example uses the Process.WaitForExit()
method, but wiring up an event handler should work as well.
You should note, however, especially since your talking about a console app, that the immediate parent process is not necessarily the process that actually spawned the current process. If process A spawns process console app B directly with ProcessStartInfo
specifying UseShellExecute=false
, than A will be the immediate parent of B. However, if process A spawns process B (a console app) with ProcessStartInfo
specifying UseShellExecute=true
, then process A will not be the immediate parent of process B: there will be an intermediate process (cmd.exe
) between A and B. And if you're involving *.cmd batch files or PowerShell code...it might be more complicated.
So you might need to run further up of the process tree to find the parent of interest.
Also, since you didn't spawn the parent process, you won't have access to the condition (exit) code of the parent process after it completes. Trying to access the parent process's ExitCode property throws an invalid operation exception.
using System;
using System.Collections;
using System.Diagnostics;
using System.Management;
namespace WaitOnParentProcessSample
{
class Program
{
static int Main( string[] argv )
{
using ( Process parentProcess = GetParentProcess() )
{
Console.WriteLine( "Waiting for parent process (pid:{0}) to exit..." , parentProcess.Id );
parentProcess.WaitForExit();
Console.WriteLine( "Parent Process Has exited. Condition code cannot be determined" );
}
return 0;
}
private static Process GetParentProcess()
{
Process parentProcess = null;
using ( Process currentProcess = Process.GetCurrentProcess() )
{
string filter = string.Format( "ProcessId={0}" , currentProcess.Id );
SelectQuery query = new SelectQuery( "Win32_Process" , filter );
using ( ManagementObjectSearcher searcher = new ManagementObjectSearcher( query ) )
using ( ManagementObjectCollection results = searcher.Get() )
{
if ( results.Count>0 )
{
if ( results.Count>1 )
throw new InvalidOperationException();
IEnumerator resultEnumerator = results.GetEnumerator();
bool fMoved = resultEnumerator.MoveNext();
using ( ManagementObject wmiProcess = (ManagementObject)resultEnumerator.Current )
{
PropertyData parentProcessId = wmiProcess.Properties["ParentProcessId"];
uint pid = (uint)parentProcessId.Value;
parentProcess=Process.GetProcessById( (int)pid );
}
}
}
}
return parentProcess;
}
}
}
Upvotes: 1
Reputation: 27486
Your best bet is likely to use P/Invoke. The Windows API function SetConsoleCtrlHandler()
may do what you expect.
Sample code below stolen from here (similar code available at MSDN, here).
class Program
{
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
// An enumerated type for the control messages
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
// Put your own handler here
return true;
}
static void Main(string[] args)
{
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
}
}
Upvotes: 2