Get notified from logon and logoff

Andre Hofmeister picture Andre Hofmeister · Apr 29, 2013 · Viewed 27.7k times · Source

I have to develop a program which runs on a local pc as a service an deliver couple of user status to a server. At the beginning I have to detect the user logon and logoff.

My idea was to use the ManagementEventWatcher class and to query the Win32_LogonSession to be notified if something changed.

My first test works well, here is the code part (This would executed as a thread from a service):

private readonly static WqlEventQuery qLgi = new WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 1), "TargetInstance ISA \"Win32_LogonSession\"");

public EventWatcherUser() {
}

public void DoWork() {
    ManagementEventWatcher eLgiWatcher = new ManagementEventWatcher(EventWatcherUser.qLgi);
    eLgiWatcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
    eLgiWatcher.Start();
}

private void HandleEvent(object sender, EventArrivedEventArgs e)
{
    ManagementBaseObject f = (ManagementBaseObject)e.NewEvent["TargetInstance"];
    using (StreamWriter fs = new StreamWriter("C:\\status.log", true))
    {
        fs.WriteLine(f.Properties["LogonId"].Value);
    }
}

But I have some understanding problems and I’m not sure if this is the common way to solve that task.

  1. If I query Win32_LogonSession I get several records which are associated to the same user. For example I get this IDs 7580798 and 7580829 and if I query

    ASSOCIATORS OF {Win32_LogonSession.LogonId=X} WHERE ResultClass=Win32_UserAccount

    I get the same record for different IDs. (Win32_UserAccount.Domain="PC-Name",Name="User1")

    Why are there several logon session with the same user? What is the common way to get the current signed in user? Or better how to get notified correctly by the login of a user?

  2. I thought I could use the same way with __InstanceDeletionEvent to determine if a user is log off. But I guess if the event is raised, I cant query Win32_UserAccount for the username after that. I’m right?

I’m at the right direction or are there better ways? It would be awesome if you could help me!

Edit Is the WTSRegisterSessionNotification class the correct way? I don't know if it's possible, because in a service I haven't a window handler.

Answer

Lorenzo Dematté picture Lorenzo Dematté · May 9, 2013

Since you are on a service, you can get session change events directly.

You can register yourself to receive the SERVICE_CONTROL_SESSIONCHANGE event. In particular, you will want to look for the WTS_SESSION_LOGON and WTS_SESSION_LOGOFF reasons.

For details and links to the relevant MSDN docs, check this answer I wrote just yesterday.

In C# it is even easier, as ServiceBase already wraps the service control routine and exposes the event as an overridable OnSessionChange method for you. See MSDN docs for ServiceBase, and do not forget to set the CanHandleSessionChangeEvent property to true to enable the execution of this method.

What you get back when the framework calls your OnSessionChange override is a SessionChangeDescription Structure with a reason (logoff, logon, ...) and a session ID you can use to obtain information, for example, on the user logging on/off (see the link to my prev answer for details)

EDIT: sample code

 public class SimpleService : ServiceBase {
    ...
    public SimpleService()
    {
        CanPauseAndContinue = true;
        CanHandleSessionChangeEvent = true;
        ServiceName = "SimpleService";
    }

    protected override void OnSessionChange(SessionChangeDescription changeDescription)
    {
        EventLog.WriteEntry("SimpleService.OnSessionChange", DateTime.Now.ToLongTimeString() +
            " - Session change notice received: " +
            changeDescription.Reason.ToString() + "  Session ID: " + 
            changeDescription.SessionId.ToString());


        switch (changeDescription.Reason)
        {
            case SessionChangeReason.SessionLogon:
                EventLog.WriteEntry("SimpleService.OnSessionChange: Logon");
                break;

            case SessionChangeReason.SessionLogoff:       
                EventLog.WriteEntry("SimpleService.OnSessionChange Logoff"); 
                break;
           ...
        }