0

I have made a simple windows application (without any Form though) and in it I used SessionEnding to do something before the OS shutsdown according to recipe19.1 in C# Cookbook (that I modified since Debug will not work anyway)

The application creates a text file when started (it does) and then when any event takes places should create another blank text file. The code is below and it does not work as expected. Nothing is created when an event takes place. Can someone tell me what is wrong with this?


As a sidenote, I tried other examples this time using an override of WndProc as stated in the documentation and it seems to be working but I am not sure how to apply this to an application without forms


The code

using Microsoft.Win32;
using System;
using System.Diagnostics;
using System.Threading;

namespace NoConsoleApp2
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {    
            RegisterForSystemEvents();
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Onstart.txt");

            bool keepRunning = true;
            Debug.WriteLine("Start");
            for (int i = 0; i < 100 && keepRunning; i++)
            {
                Thread.Sleep(1000);
            }    
            Debug.WriteLine("End");
        }

        public static void RegisterForSystemEvents()
        {
            //Always get the final notification when the event thread is shutting down 
            //so we can unregister.

            SystemEvents.EventsThreadShutdown += new EventHandler(OnEventsThreadShutdown);
            SystemEvents.PowerModeChanged += new PowerModeChangedEventHandler(OnPowerModeChanged);
            SystemEvents.SessionSwitch += new SessionSwitchEventHandler(OnSessionSwitch);
            SystemEvents.SessionEnding += new SessionEndingEventHandler(OnSessionEnding);
            SystemEvents.SessionEnded += new SessionEndedEventHandler(OnSessionEnded);
        }
        private static void UnregisterFromSystemEvents()
        {
            SystemEvents.EventsThreadShutdown -= new EventHandler(OnEventsThreadShutdown);
            SystemEvents.PowerModeChanged -= new PowerModeChangedEventHandler(OnPowerModeChanged);
            SystemEvents.SessionSwitch -= new SessionSwitchEventHandler(OnSessionSwitch);
            SystemEvents.SessionEnding -= new SessionEndingEventHandler(OnSessionEnding);
            SystemEvents.SessionEnded -= new SessionEndedEventHandler(OnSessionEnded);

        }
        /* Notifies you when the thread that is distributing the events from the SystemEvents class is
         * shutting down so that we can unregister events on the SystemEvents class
         */ 
        private static void OnEventsThreadShutdown(object sender, EventArgs e)
        {
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "END.txt");
            Debug.WriteLine("System event thread is shutting down, no more notifications");
            //Unregister all our events as the notification thread is goin away
            UnregisterFromSystemEvents();
        }
        /* Triggers when the user suspends or resumes the system from a suspended state
         */ 
        private static void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
        {
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "power.txt");
            switch (e.Mode)
            {
                case PowerModes.Resume:
                    Debug.WriteLine("PowerMode: OS is resuming from suspended state");
                    break;
                case PowerModes.StatusChange:
                    Debug.WriteLine("PowerMode: There was a change relating to the power supply (weak battery, unplug, etc...)");
                    break;
                case PowerModes.Suspend:
                    Debug.WriteLine("PowerMode: OS is about to be suspended");
                    break;

            }
        }
        /* Triggered by a change in the logged-on user
         */ 
        private static void OnSessionSwitch(object sender, SessionSwitchEventArgs e)
        {
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "sessionswitch.txt");

            switch (e.Reason)
            {
                case SessionSwitchReason.ConsoleConnect:
                    Debug.WriteLine("Session connected from the console");
                    break;
                case SessionSwitchReason.ConsoleDisconnect:
                    Debug.WriteLine("Session disconnected from the console");
                    break;
                case SessionSwitchReason.RemoteConnect:
                    Debug.WriteLine("Remote Session connected");
                    break;
                case SessionSwitchReason.RemoteDisconnect:
                    Debug.WriteLine("Remote Session disconnected");
                    break;
                case SessionSwitchReason.SessionLock:
                    Debug.WriteLine("Session has been locked");
                    break;
                case SessionSwitchReason.SessionLogoff:
                    Debug.WriteLine("User was logged off from a session");
                    break;
                case SessionSwitchReason.SessionLogon:
                    Debug.WriteLine("User has logged on to a session");
                    break;
                case SessionSwitchReason.SessionRemoteControl:
                    Debug.WriteLine("Session changed to or from remote status");
                    break;
                case SessionSwitchReason.SessionUnlock:
                    Debug.WriteLine("Session has been unlocked");
                    break;

            }
        }
        /* Triggered when the user is trying to log off or shut down the system
         */ 
       private static void OnSessionEnding(object sender, SessionEndingEventArgs e)
        {
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "sessionend.txt");

            //True to cancel the user request to end the session, false otherwise
            e.Cancel =  false;

            switch(e.Reason)
            {
                case SessionEndReasons.Logoff:
                    Debug.WriteLine("Session ending as the user is logging off");
                    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Logoff.txt");
                    break;
                case SessionEndReasons.SystemShutdown:
                    Debug.WriteLine("Session ending as the OS is shutting down");
                    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Shutdown.txt");
                    break;

            }

        }
        /*  Triggered when the user is actually logging off or shutting down the system
         */ 
        private static void OnSessionEnded(object sender, SessionEndedEventArgs e )
        { 
            switch(e.Reason)
            {
                case SessionEndReasons.Logoff:
                    Debug.WriteLine("Session ended as the user is logging off");
                    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Loggedoff.txt");
                    break;
                case SessionEndReasons.SystemShutdown:
                    Debug.WriteLine("Session ended as the OS is shutting down");
                    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Shutted.txt");
                    break;
            }

        }


    }
}

EDIT:

I have been advised that I should use Application.Run and with this the events are responding. However I assume Run makes a loop so I am wondering where to put my own logic (in this example case the for loop)

static void Main()
{    
    RegisterForSystemEvents();
    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Onstart.txt");

    bool keepRunning = true;
    Debug.WriteLine("Start");

    Application.Run();  //this activates the events

    System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "GoFOrloop.txt");

    for (int i = 0; i < 100 && keepRunning; i++)
    {
        Thread.Sleep(1000);
    }    
    Debug.WriteLine("End");
}

In this case, the file GoForLoop.txt is never created (meaning that the loop itself is never executed)

EDIT2: I have tried the solution in EDIT and it works (the events are triggered) but the main logic(in this case the loop) does not work so I tried a second suggestion to use Application Contexts. Sadly this time eventhough the loop works, the events are not triggered anymore :(

class Context : ApplicationContext
    {

        public Context()
        {
            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "GoFOrLoop.txt");
            for (int i = 0; i < 60; i++)
            {
                //    Console.WriteLine("Number: " + i);
                Thread.Sleep(1000);
            }



            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "end.txt");
            Debug.WriteLine("End");


            //  Environment.Exit(1);
            ExitThread();
        }



    }

and in the Main

 [STAThread]
        static void Main()
        {
            /*    Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());
                */

            RegisterForSystemEvents();

            System.IO.File.Create(AppDomain.CurrentDomain.BaseDirectory + "Onstart.txt");

//            bool keepRunning = true;

            Debug.WriteLine("Start");

             //           Application.Run(); //this works
            Application.Run(new Context()); //this DOES NOT work
}
KansaiRobot
  • 7,564
  • 11
  • 71
  • 150
  • Seems to me the documentation is painfully clear on the point: _"Console applications do not raise the SessionEnding event"_. And _"This event is only raised if the message pump is running. In a Windows service, unless a hidden form is used or the message pump has been started manually, this event will not be raised. For a code example that shows how to handle system events by using a hidden form in a Windows service, see the [SystemEvents](https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.systemevents?view=netframework-4.7.1) class."_ – Peter Duniho Feb 14 '18 at 08:38
  • If you are referring to `Debug.WriteLine("End");`it gets written when the application normally ends (after the loop) but since I am trying to end it with a shutdown, this is on purpose never executed – KansaiRobot Feb 14 '18 at 08:38
  • @PeterDuniho this is not a Console Application.not a windows service – KansaiRobot Feb 14 '18 at 08:39
  • Without a message loop, it has the exact same limitations as any console program. _Not_ attaching a console to your process isn't what keeps the event from working; not having a message loop is. Console programs and services are just specific examples (exceptions, actually) of the more general case described in the documentation: **_"This event is only raised if the message pump is running"_** – Peter Duniho Feb 14 '18 at 08:41
  • @PeterDuniho I have review the duplicate and as you can see the question is different in that real work is done here and not in the other , plus there is no real answer to the problem there. I ask you to take off the duplicate mark so other people can provide an answer – KansaiRobot Feb 15 '18 at 01:51
  • _"there is no real answer to the problem there"_ -- sure there is, it's right there at the top: _"**Important:** Console applications do not raise the SessionEnding event."_ There's even information and references to show you how you can create a suitable message loop so that the events can be raised. And again: **the fact that this is not a console program is irrelevant -- the material aspect is that like a console program, your program does not include a message loop.** – Peter Duniho Feb 15 '18 at 02:07
  • @PeterDuniho The message loop was included in the Edits (Application.Run) – KansaiRobot Feb 15 '18 at 02:12
  • 1
    Your original question is addressed by the duplicate. You've edited your post to **change** the question. If you want to ask a new question, then post a new question. – Peter Duniho Feb 15 '18 at 02:56

1 Answers1

2

Windows apps for process events uses message loop. In your code you don't run message loop.

Add to Main method:

Application.Run();

This starts meessage loop for your app.

BWA
  • 5,672
  • 7
  • 34
  • 45
  • Thanks. This seems very helpful. However since I want to have some processing too (in the case of the example the loop) where should I put this?? – KansaiRobot Feb 14 '18 at 08:43
  • I tried putting the Application.Run and it worked! But I am assuming the Application.Run starts a loop so I have to figure it out where to put my own code. Any help will be greatly apprciated! – KansaiRobot Feb 14 '18 at 08:48
  • You should place this in front of the place where you want to have an active messaging loop. In this example, in front of the loop. – BWA Feb 14 '18 at 08:50
  • I tried (as you can see in the Edit of this question) but it seems Run is an endless loop so I don't know where to put my loop (the logic). – KansaiRobot Feb 14 '18 at 09:00
  • You can use [this](https://msdn.microsoft.com/en-us/library/ms157901(v=vs.110).aspx) version Run function. – BWA Feb 14 '18 at 09:04
  • 1
    Or you can before `Application.Run()` start new thread and in new thread make job. – BWA Feb 14 '18 at 09:17
  • I tried the application context and it does not work. (see the Edit2) – KansaiRobot Feb 15 '18 at 01:56