C# – Catch shutdown event of computer

In my first years at TUM – Technique University Munich, there was a very slow computer system in EIKOM tower for students. As I remembered, the system consists of Intel 80486 computers running Linux KDE. Because the computers are too slow therefore if I opened many programms (as you know 2 are already “many”), it would hang the complete system. No mouse move, no keyboard input, one couldn’t do anything anymore, the computer was dead. So as usual one can press the powerful restart button on the case to force computer working again, but there is a nasty warning paper of network administrators in the room saying that the students shouldn’t restart the computer which can cause problems with connecting to network later. What should you do in my case? Maybe go to other computer and let the dead one for network administrator to repair. You’re good students, but I am not. I just looked around to see if someone was nearby, the hand on restart button and … pressed it. ^_^. I was lucky that the computers always worked after restarting but please do not imitate me to restart if you are not allowed to do so. Although I was happy with restart button but I asked myself, how can I prevent someone like me from shutdown/restarting computers programatically? Last week my friend reminded me this problem when he asked me how to catch the shutdown event. He has a program which executes many background actions and does not want to let the user shutdown system before his program finishes its work. So I write for him a short block code to catch shutdown event and swallow this event if needed.

Because the block code is very short, I would like to explain more about the theory first. The soul of this code lies at the message WM_QUERYENDSESSION. It is sent when the user chooses to end the session or when an application calls one of the system shutdown functions. That means if the user wants to log off, restart or shutdown computers, this message will be fired to all applications after each other. If any application returns zero, the session is not ended. The system stops sending WM_QUERYENDSESSION messages as soon as one application returns zero.

After receving results of this message from the application, the system sends the WM_ENDSESSION message with the wParam parameter set to the results before to inform the application whether the session is ending. As adviced in MSDN, your application should return TRUE or FALSE immediately upon receiving WM_QUERYENDSESSION, and defer any cleanup operations until it receives the WM_ENDSESSION message. If you have not finished with clean ups until WM_ENDSESSION comes, then be quick and send approriate messages to quite like DestroyWindow, PostQuitMessage, etc… Because after handling WM_ENDSESSION wit wParam on true, you have only few seconds to exit safely your program, system will brutally end your process with TerminateProcess API.

To catch these messages, you just need to override the WndProc function of windows form and add your clean up code before return

protected override void WndProc(ref Message m)
{
	if (catchEvent && (m.Msg == WinAPI.WM_QUERYENDSESSION || m.Msg == WinAPI.WM_ENDSESSION))
		//Clean up your app here
		return;

	base.WndProc(ref m);
}

That’s all what you need to do to exit your application safely. From Windows Vista you have a new option to set the waiting message to inform what your application is still busy.

if (WinAPI.ShutdownBlockReasonCreate(this.Handle, txtMessage.Text))
{
	catchEvent = true;
	MessageBox.Show("Catch Shutdown Event Successfully");
}
else
{
	MessageBox.Show("Catch Shutdown Event failed");
}

If user ends his session, Windows will show a warning as image below with your warning message. In my case it’s “Oachkatzschwoap”

Windows warning message shutdown

If you would like to remove this message you can call the API function ShutdownBlockReasonDestroy as below

if (WinAPI.ShutdownBlockReasonDestroy(this.Handle))
{
	catchEvent = false;
	MessageBox.Show("UnCatch Shutdown Event Successfully");
}
else
{
	MessageBox.Show("UnCatch Shutdown Event failed");
}

It’s great, isn’t it? We can now catch the shutdown event to clean up our applications before exit or prevent the users to close them. However it should be greater if we can catch this event before all applications. Let’s consider a case that our programm has many processes. What will happen if we catch the shutdown event only in main app and when the user tries to shut down, our child processes received WM_QUERYENDSESSION message before the main one. They will be killed without our knowing and can cause data lost later when we quit our main app. To avoid this case we can call the API SetProcessShutdownParameters to set our main app to be the first one who handles the shutdown event

private void Form1_Load(object sender, EventArgs e)
{
	WinAPI.SetProcessShutdownParameters(0x3FF, 0x00000001);
}

This API requires 2 arguments one is dwLevel, the other is dwFlags. The dwLevel has one of values below

000-0FF : System reserved last shutdown range.
100-1FF : Application reserved last shutdown range.
200-2FF : Application reserved “in between” shutdown range.
300-3FF : Application reserved first shutdown range.
400-4FF : System reserved first shutdown range.

As you can see that I register in code above my application as first in application shutdown range. You can download the sample sourcce code “Shutdown Event Handler” and try it yourself. The computer system at EIKOM is now completely replaced with the new one after law of tuition paying. The new computers are good and don’t hang anymore and I already leaved the university. I have no chance more to restart EIKOM computers. ^^.