Search code examples
c#.netsens

Capturing Logon event on local computer as a non-admin. Is there an api?


As a non-admin user, I want to detect the event when another user logs in. I cannot use System Event Notification Service (SensLogon2) since it requires the user to be part of the Administrators group. Is there another API or are there certain permission/privileges that I can grant to the current user?

We need to detect another user logging on to the terminal via RDP so that we can change the application state the current user is in.


Solution

  • You can do next steps to get info about session changes:

    1. Call WTSRegisterSessionNotification with NOTIFY_FOR_ALL_SESSIONS on your form to receive WM_WTSSESSION_CHANGE message
    2. override void WndProc(ref Message m) of the Form and filtering by WM_WTSSESSION_CHANGE (0x2b1)
    3. Extract session id from LPARAM and session state change event from WPARAM
    4. Call WTSQuerySessionInformation with session id to get username

    Here is working example with pInvoke. I have Form1 (WinForm) in my project. It is:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Diagnostics;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace MessageLoop
    {
        public partial class Form1 : Form
        {
            /// <summary>
            ///  WM_WTSSESSION_CHANGE message number for filtering in WndProc
            /// </summary>
            private const int WM_WTSSESSION_CHANGE = 0x2b1;
    
            public Form1()
            {
                InitializeComponent();
                NativeWrapper.WTSRegisterSessionNotification(this, SessionNotificationType.NOTIFY_FOR_ALL_SESSIONS);
            }
            protected override void OnClosing(CancelEventArgs e)
            {
                NativeWrapper.WTSUnRegisterSessionNotification(this);
                base.OnClosing(e);
            }
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_WTSSESSION_CHANGE)
                {
                    int eventType = m.WParam.ToInt32();
                    int sessionId = m.LParam.ToInt32();
                    WtsSessionChange reason = (WtsSessionChange)eventType;
                    Trace.WriteLine(string.Format("SessionId: {0}, Username: {1}, EventType: {2}", 
                        sessionId, NativeWrapper.GetUsernameBySessionId(sessionId), reason));
                }
                base.WndProc(ref m);
            }
        }
    }
    

    Here is NativeWrapper.cs:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace MessageLoop
    {
        public enum WtsSessionChange
        {
            WTS_CONSOLE_CONNECT = 1,
            WTS_CONSOLE_DISCONNECT = 2,
            WTS_REMOTE_CONNECT = 3,
            WTS_REMOTE_DISCONNECT = 4,
            WTS_SESSION_LOGON = 5,
            WTS_SESSION_LOGOFF = 6,
            WTS_SESSION_LOCK = 7,
            WTS_SESSION_UNLOCK = 8,
            WTS_SESSION_REMOTE_CONTROL = 9,
            WTS_SESSION_CREATE = 0xA,
            WTS_SESSION_TERMINATE = 0xB
        }
        public enum SessionNotificationType
        {
            NOTIFY_FOR_THIS_SESSION = 0,
            NOTIFY_FOR_ALL_SESSIONS = 1
        }
        public static class NativeWrapper
        {
            public static void WTSRegisterSessionNotification(Control control, SessionNotificationType sessionNotificationType)
            {
                if (!Native.WTSRegisterSessionNotification(control.Handle, (int)sessionNotificationType))
                    throw new Win32Exception(Marshal.GetLastWin32Error()); 
            }
            public static void WTSUnRegisterSessionNotification(Control control)
            {
                if (!Native.WTSUnRegisterSessionNotification(control.Handle))
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
            public static string GetUsernameBySessionId(int sessionId)
            {           
                IntPtr buffer;
                int strLen;
                var username = "SYSTEM"; // assume SYSTEM as this will return "\0" below
                if (Native.WTSQuerySessionInformation(IntPtr.Zero, sessionId, Native.WTS_INFO_CLASS.WTSUserName, out buffer, out strLen) && strLen > 1)
                {
                    username = Marshal.PtrToStringAnsi(buffer); // don't need length as these are null terminated strings
                    Native.WTSFreeMemory(buffer);
                    if (Native.WTSQuerySessionInformation(IntPtr.Zero, sessionId, Native.WTS_INFO_CLASS.WTSDomainName, out buffer, out strLen) && strLen > 1)
                    {
                        username = Marshal.PtrToStringAnsi(buffer) + "\\" + username; // prepend domain name
                        Native.WTSFreeMemory(buffer);
                    }
                }
                return username;
            }
        }
    }
    

    And the last file is Native.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace MessageLoop
    {
        public static class Native
        {
            public enum WTS_INFO_CLASS
            {
                WTSInitialProgram,
                WTSApplicationName,
                WTSWorkingDirectory,
                WTSOEMId,
                WTSSessionId,
                WTSUserName,
                WTSWinStationName,
                WTSDomainName,
                WTSConnectState,
                WTSClientBuildNumber,
                WTSClientName,
                WTSClientDirectory,
                WTSClientProductId,
                WTSClientHardwareId,
                WTSClientAddress,
                WTSClientDisplay,
                WTSClientProtocolType,
                WTSIdleTime,
                WTSLogonTime,
                WTSIncomingBytes,
                WTSOutgoingBytes,
                WTSIncomingFrames,
                WTSOutgoingFrames,
                WTSClientInfo,
                WTSSessionInfo
            }
    
            [DllImport("wtsapi32.dll", SetLastError = true)]
            internal static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)] int dwFlags);
    
            [DllImport("wtsapi32.dll", SetLastError = true)]
            internal static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd);
    
            [DllImport("Wtsapi32.dll")]
            internal static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
    
            [DllImport("Wtsapi32.dll")]
            internal static extern void WTSFreeMemory(IntPtr pointer);
        }
    }