如何使.NET Windows服务检测登录,注销和切换用户事件?

我需要在Windows XP SP3上跟踪用户中的当前用户(使用控制台的用户)。

我尝试了以下方法:

  • Microsoft.Win32.SystemEvents.SessionSwitch:适用于单个登录/注销事件,但无法检测切换用户。

    如果发生以下情况:

    1. userA登录
    2. userA切换用户
    3. userB登录
    4. userB注销
    5. userA恢复会话

    SystemEvents.SessionSwitch未检测到事件3和4

  • 监视“安全”事件日志:事件不一致并且无序到达。 例如,如果重播上面的列表,我会在恢复会话后收到事件ID 528(登录),然后是userA的两个538(Logoff)。 检查event.TimeGenerated没有帮助。 如果在SecPol.msc上禁用审核,则此方法也不起作用。

  • P /调用WTSRegisterSessionNotification:工作正常。 我必须创建一个隐藏的表单,覆盖其WndProc以处理WM_WTSSESSION_CHANGE消息,然后调用WTSQuerySessionInformation以获取与该事件关联的用户名。 这种方法看起来太复杂了,有没有更简单的方法?

编辑:

  • 每n毫秒调用WTSGetActiveConsoleSessionId也可以,但我正在寻找一个基于事件的方法。

如果您正在提供服务,则您的类派生自ServiceBase 。 考虑调查OnSessionChange方法。 重写此方法以检测不同类型的会话更改。 它提供了会话更改的原因和新的会话标识符。 请确保在构造函数中将CanHandleSessionChangeEvent设置为true ,否则将不会调用您的覆盖。

好的WTSRegisterSessionNotification – 解决方案只适用于您手中的控件或表单,但是在Windows服务环境中运行的控制台应用程序或类是什么,并且没有ServiceBase对象,因为它是基于插件的体系结构。

以下是一些结构定义:

 using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Linq; using System.Windows.Forms; using System.Reflection; namespace Rdp.Interfaces { /************************************************************************************************/ /* struct members */ /************************************************************************************************/ ///  /// Terminal Session Info data holder struct. ///  ///  /// Structures, interfaces, p-invoke members for Terminal Service Session ///  [Serializable] public struct TerminalSessionInfo { ///  ///  Remote Desktop Services API Structure member ///  ///  public WTS_SESSION_INFO SessionInfo; ///  ///  Remote Desktop Services API Structure member ///  ///  public WTS_CLIENT_PROTOCOL_TYPE ProtocolType; ///  ///  Remote Desktop Services API Structure member ///  ///  public WTS_CLIENT_INFO ClientInfo; ///  ///  Remote Desktop Services API Structure member ///  ///  public WTSINFO WtsInfo; ///  ///  The client user name. ///  public string UserName; ///  ///  The domain name of the client computer. ///  public string Domain; ///  /// The client network address. ///  public string ClientIPAddress; ///  /// The machine name of the client computer. ///  public string ClientMachineName; ///  /// Initializes a new instance of the  structure. ///  /// Only used to force an initialization of members. public TerminalSessionInfo(int SessionId) { this.SessionInfo = new WTS_SESSION_INFO(); this.SessionInfo.iSessionID = SessionId; this.SessionInfo.sWinsWorkstationName = String.Empty; this.UserName = String.Empty; this.Domain = String.Empty; this.ClientIPAddress = String.Empty; this.ProtocolType = WTS_CLIENT_PROTOCOL_TYPE.UNKNOWN; this.ClientMachineName = String.Empty; this.ClientInfo = new WTS_CLIENT_INFO(); this.ClientInfo.ClientMachineName = String.Empty; this.ClientInfo.Domain = String.Empty; this.ClientInfo.UserName = String.Empty; this.ClientInfo.WorkDirectory = String.Empty; this.ClientInfo.InitialProgram = String.Empty; this.ClientInfo.ClientDirectory = String.Empty; this.ClientInfo.DeviceId = String.Empty; this.WtsInfo = new WTSINFO(); this.WtsInfo.Domain = String.Empty; this.WtsInfo.UserName = String.Empty; this.WtsInfo.WinStationName = String.Empty; } ///  /// Returns the fully qualified type name of this instance. ///  ///  /// A  containing a fully qualified type name. ///  /// 2 public override string ToString() { string retval = "SessionID: " + this.SessionInfo.iSessionID.ToString(); retval += String.IsNullOrEmpty(this.Domain) ? "" : Environment.NewLine + "Domain: " + this.Domain; retval += String.IsNullOrEmpty(this.UserName) ? "" : Environment.NewLine + "UserName: " + this.UserName; retval += String.IsNullOrEmpty(this.ClientMachineName) ? "" : Environment.NewLine + "ClientMachineName: " + this.ClientMachineName; retval += String.IsNullOrEmpty(this.ClientIPAddress) ? "" : Environment.NewLine + "ClientIPAddress: " + this.ClientIPAddress; retval += String.IsNullOrEmpty(this.SessionInfo.sWinsWorkstationName) ? "" : Environment.NewLine + "WinsWorkstationName: " + this.SessionInfo.sWinsWorkstationName; retval += this.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.UNKNOWN ? "" : Environment.NewLine + "ProtocolType: " + this.ProtocolType.ToString(); retval += String.IsNullOrEmpty(this.SessionInfo.oState.ToString()) ? "" : Environment.NewLine + "State: " + this.SessionInfo.oState.ToString(); retval += String.IsNullOrEmpty(this.ClientInfo.ClientMachineName) ? "" : Environment.NewLine + "ClientInfoMachineName: " + this.ClientInfo.ClientMachineName; retval += String.IsNullOrEmpty(this.ClientInfo.Domain) ? "" : Environment.NewLine + "ClientInfoDomain: " + this.ClientInfo.Domain; retval += String.IsNullOrEmpty(this.ClientInfo.UserName) ? "" : Environment.NewLine + "ClientInfoUserName: " + this.ClientInfo.UserName; retval += String.IsNullOrEmpty(this.ClientInfo.WorkDirectory) ? "" : Environment.NewLine + "ClientInfoWorkDirectory: " + this.ClientInfo.WorkDirectory; retval += String.IsNullOrEmpty(this.ClientInfo.ClientDirectory) ? "" : Environment.NewLine + "ClientInfoDirectory: " + this.ClientInfo.ClientDirectory; retval += String.IsNullOrEmpty(this.ClientInfo.DeviceId) ? "" : Environment.NewLine + "ClientInfoDeviceId: " + this.ClientInfo.DeviceId; retval += this.ClientInfo.ClientBuildNumber == 0 ? "" : Environment.NewLine + "ClientInfoBuildNumber: " + this.ClientInfo.ClientBuildNumber.ToString(); retval += this.ClientInfo.ClientHardwareId == 0 ? "" : Environment.NewLine + "ClientInfoHardwareId: " + this.ClientInfo.ClientHardwareId.ToString(); retval += this.ClientInfo.ClientProductId == 0 ? "" : Environment.NewLine + "ClientInfoProductId: " + this.ClientInfo.ClientProductId.ToString(); retval += String.IsNullOrEmpty(this.WtsInfo.Domain) ? "" : Environment.NewLine + "WtsInfoDomain: " + this.WtsInfo.Domain; retval += String.IsNullOrEmpty(this.WtsInfo.UserName) ? "" : Environment.NewLine + "WtsInfoUserName: " + this.WtsInfo.UserName; retval += String.IsNullOrEmpty(this.WtsInfo.WinStationName) ? "" : Environment.NewLine + "WtsInfoWinStationName: " + this.WtsInfo.WinStationName; retval += this.WtsInfo.ConnectTime == 0 ? "" : Environment.NewLine + "WtsInfoConnectTime: " + ToCSharpTime(this.WtsInfo.ConnectTime, true).ToString(); retval += this.WtsInfo.CurrentTime == 0 ? "" : Environment.NewLine + "WtsInfoCurrentTime: " + ToCSharpTime(this.WtsInfo.CurrentTime, true).ToString(); retval += this.WtsInfo.DisconnectTime == 0 ? "" : Environment.NewLine + "WtsInfoDisconnectTime: " + ToCSharpTime(this.WtsInfo.DisconnectTime, true).ToString(); retval += this.WtsInfo.LogonTime == 0 ? "" : Environment.NewLine + "WtsInfoLogonTime: " + ToCSharpTime(this.WtsInfo.LogonTime, true).ToString(); retval += this.WtsInfo.LastInputTime == 0 ? "" : Environment.NewLine + "WtsInfoLogonTime: " + ToCSharpTime(this.WtsInfo.LastInputTime, true).ToString(); retval += this.WtsInfo.IncomingBytes == 0 ? "" : Environment.NewLine + "WtsInfoIncomingBytes: " + this.WtsInfo.IncomingBytes.ToString(); retval += this.WtsInfo.OutgoingBytes == 0 ? "" : Environment.NewLine + "WtsInfoOutgoingBytes: " + this.WtsInfo.OutgoingBytes.ToString(); return retval; } ///  /// Help method to find C++ corresponding long value of C# DateTime (starting at 01 /// / 01 / 1970 00:00:00). ///  /// .NET object /// If set to , then date will be /// assummed as local time and converted to UTC - time; otherwise, UTC will be /// assumed. ///  /// C++ corresponding long value ///  public static long ToUnixtime(DateTime date, bool localTime) { DateTime unixStartTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); //DateTime unixStartTime = DateTime.MinValue; if(localTime) date = date.ToUniversalTime(); TimeSpan timeSpan = date - unixStartTime; return Convert.ToInt64(timeSpan.TotalMilliseconds); } ///  ///  Help method to find C# DateTime from C++ corresponding long value. ///  /// Unix value of date time starting at 01 / 01 / 1970 /// 00:00:00 /// If set to , then ; otherwise, /// . public static DateTime ToCSharpTime(long unixTime, bool localTime) { DateTime unixStartTime = new DateTime(1970, 1, 1, 0, 0, 0, 0); //DateTime unixStartTime = DateTime.MinValue; if(localTime) return unixStartTime.AddTicks(unixTime).ToLocalTime(); return unixStartTime.AddTicks(unixTime); } #region IComparable Members ///  /// Overriding Operator == ///  /// Object to compare /// Object to compare /// Return true if the segment start / end values match. public static bool operator ==(TerminalSessionInfo a, TerminalSessionInfo b) { return Equals(a, b); } ///  /// Overriding Operator != ///  /// Object to compare /// Object to compare /// Return true if the segment start / end values match. public static bool operator !=(TerminalSessionInfo a, TerminalSessionInfo b) { return !Equals(a, b); } ///  /// Overriding Equals ///  /// Object to compare with own instance. /// Return true if the segment start / end values match. public override bool Equals(object obj) { // If parameter is null return false. if(obj == null) { return false; } // If parameter cannot be cast to Point return false. TerminalSessionInfo p = (TerminalSessionInfo)obj; if((System.Object)p == null) { return false; } // Return true if the segment start / end values match: return Equals(this, p); } ///  /// Memberwise comparison ///  ///  ///  public static bool Equals(TerminalSessionInfo a, TerminalSessionInfo b) { bool retval = false; if(((System.Object)a == null) && (System.Object)b == null) { return false; } if(((System.Object)a == null) ^ (System.Object)b == null) { return false; } // check property members string[] properties = new string[] { "UserName", "Domain", "ClientIPAddress", "ClientMachineName", "SessionInfo.iSessionID", "SessionInfo.sWinsWorkstationName", "SessionInfo.oState", "ProtocolType", "ClientInfo.ClientMachineName", "ClientInfo.Domain", "ClientInfo.UserName", "ClientInfo.WorkDirectory", "ClientInfo.InitialProgram", "ClientInfo.EncryptionLevel", "ClientInfo.ClientAddressFamily", "ClientInfo.ClientAddress", "ClientInfo.HRes", "ClientInfo.VRes", "ClientInfo.ColorDepth", "ClientInfo.ClientDirectory", "ClientInfo.ClientBuildNumber", "ClientInfo.ClientHardwareId", "ClientInfo.ClientProductId", "ClientInfo.DeviceId", "WtsInfo.State", "WtsInfo.SessionId", "WtsInfo.WinStationName", "WtsInfo.Domain", "WtsInfo.UserName", "WtsInfo.ConnectTime", "WtsInfo.DisconnectTime", "WtsInfo.LogonTime" }; retval = true; object vala, valb; foreach(string prop in properties) { try { vala = GetFieldItem(a, prop); valb = GetFieldItem(b, prop); if(((System.Object)vala == null) && (System.Object)valb == null) continue; if(((System.Object)vala == null) ^ (System.Object)valb == null) return false; if(!Object.Equals(vala, valb)) return false; } catch(Exception ex) { retval = false; } } return retval; } /* Ein Property TopDown suchen. Wir graben uns durch die Hierarchiestufen einer Klasse nach unten, // Bis wir das erste mal auf das Property "name" stossen. Dieses wird dann zurückgemolden // Bei überladenen Properties wird dann erst das überladene gefunden. // Über den normalen Type.GetProperty(name) würde sonst ein ambigous error erzeugt.*/ ///  /// Gets property with path  from . /// Using System.Type.GetProperty(name) throws an exception if a property is overloaded. This method /// does not throw an ambigous exception instead it returns the overloaded property value. ///  /// Object with properties. /// Path to property (eg: TimeOfDay.Hours or Ticks) ///  static public System.Reflection.PropertyInfo GetPropertyTopDown(System.Object obj, System.String name) { System.Type trs = obj.GetType(); for(trs = obj.GetType(); trs != null; trs = trs.BaseType) { // Nur Properties, die in dieser Hierarchiestufe der Klasse deklariert wurden System.Reflection.PropertyInfo[] pis = trs.GetProperties( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); foreach(System.Reflection.PropertyInfo pi in pis) { System.Diagnostics.Debug.Assert(trs == pi.DeclaringType); if(pi.Name == name) { //System.Diagnostics.Debug.WriteLine(pi.DeclaringType + " -> " + pi.Name); return pi; } } } return null; } /* Ein Property TopDown suchen. Wir graben uns durch die Hierarchiestufen einer Klasse nach unten, // Bis wir das erste mal auf das Property "name" stossen. Dieses wird dann zurückgemolden // Bei überladenen Properties wird dann erst das überladene gefunden. // Über den normalen Type.GetProperty(name) würde sonst ein ambigous error erzeugt.*/ ///  /// Gets property with path  from . Using /// System.Type.GetField(name) throws an exception if a property is overloaded. This /// method does not throw an ambigous exception instead it returns the overloaded /// property value. ///  /// Object with properties. /// Path to property (eg: TimeOfDay.Hours or Ticks) static public System.Reflection.FieldInfo GetFieldTopDown(System.Object obj, System.String name) { System.Type trs = obj.GetType(); for(trs = obj.GetType(); trs != null; trs = trs.BaseType) { // Nur Properties, die in dieser Hierarchiestufe der Klasse deklariert wurden System.Reflection.FieldInfo[] pis = trs.GetFields( System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); foreach(System.Reflection.FieldInfo fi in pis) { System.Diagnostics.Debug.Assert(trs == fi.DeclaringType); if(fi.Name == name) { //System.Diagnostics.Debug.WriteLine(pi.DeclaringType + " -> " + pi.Name); return fi; } } } return null; } ///  /// Gets value of property with path . ///  /// Object with properties. /// Property path. ///  ///  ///  static public System.Object GetFieldItem(System.Object obj, System.String name) { System.Reflection.FieldInfo fi = null; System.String[] s = name.Split(new char[] { '.' }, 2); while(s.Length > 1) { //pi = Balzers.Misc.ReflectionHelper.GetPropertyTopDown(obj, name); //System.Diagnostics.Debug.Assert(pi != null, "GetItem(obj, " + name + ")"); fi = GetFieldTopDown(obj, s[0]); System.Diagnostics.Debug.Assert(fi != null, "GetFieldItem(obj, " + name + ")"); obj = fi.GetValue(obj); //obj = obj.GetType().GetProperty(s[0]).GetValue(obj, null); s = s[1].Split(new char[] { '.' }, 2); } //pi = Balzers.Misc.ReflectionHelper.GetPropertyTopDown(obj, name); //System.Diagnostics.Debug.Assert(pi != null, "GetItem(obj, " + name + ")"); fi = GetFieldTopDown(obj, s[0]); System.Diagnostics.Debug.Assert(fi != null, "GetFieldItem(obj, " + s[0] + ")"); System.Object value = fi.GetValue(obj); return value; //return obj.GetType().GetProperty(s[0]).GetValue(obj, null); } #endregion } } 

这是下一部分:

 using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Linq; using System.Windows.Forms; using System.Reflection; namespace Rdp.Interfaces { #region struct members //Structure for Terminal Service Client IP Address ///  [StructLayout(LayoutKind.Sequential)] public struct WTS_CLIENT_ADDRESS { ///  ///  Address family. This member can /// be AF_INET, AF_INET6, AF_IPX, AF_NETBIOS, /// or AF_UNSPEC. ///  public int iAddressFamily; ///  /// Client network address. The format of the field of Address depends on the /// address type as specified by the AddressFamily member. /// For an address family AF_INET: Address contains the IPV4 /// address of the client as a null-terminated string. /// For an family AF_INET6: Address contains the IPV6 address of /// the client as raw byte values. (For example, the address "FFFF::1" /// would be represented as the following series of byte values: "0xFF 0xFF /// 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 /// 0x01") ///  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public byte[] bAddress; } ///  /// Maximum string lengths constants used within RDP API structures MSDN ///  /// MSDN /// Example public struct WTSAPI32_CONSTANTS { ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int USERNAME_LENGTH = 20; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int CLIENTNAME_LENGTH = 20; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int CLIENTADDRESS_LENGTH = 30; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int MAX_ELAPSED_TIME_LENGTH = 15; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int MAX_DATE_TIME_LENGTH = 15; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int WINSTATIONNAME_LENGTH = 32; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int DOMAIN_LENGTH = 17; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int WTS_DRIVE_LENGTH = 3; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int WTS_LISTENER_NAME_LENGTH = 32; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int WTS_COMMENT_LENGTH = 60; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int PRODUCTINFO_COMPANYNAME_LENGTH = 256; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int PRODUCTINFO_PRODUCTID_LENGTH = 4; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int VALIDATIONINFORMATION_LICENSE_LENGTH = 16384; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int VALIDATIONINFORMATION_HARDWAREID_LENGTH = 20; ///  /// Maximum string lengths constants used within RDP API structures ///  /// Example /// WTS_CLIENT_INFO public const int MAX_PATH = 260; } //Structure for Terminal Service Client Infostructure ///  /// Contains information about a Remote Desktop Connection (RDC) client. ///  /// MSDN [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] [Serializable] public struct WTS_CLIENT_INFO { ///  ///  The NetBIOS name of the client computer. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.CLIENTNAME_LENGTH + 1)] public string ClientMachineName; ///  ///  The domain name of the client computer. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.DOMAIN_LENGTH + 1)] public string Domain; ///  ///  The client user name. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.USERNAME_LENGTH + 1)] public string UserName; ///  ///  The folder for the initial program. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)] public string WorkDirectory; ///  ///  The program to start on connection. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)] public string InitialProgram; ///  ///  The security level of encryption. ///  [MarshalAs(UnmanagedType.U1)] public byte EncryptionLevel; ///  ///  The address family. This member can /// be AF_INET, AF_INET6, AF_IPX, AF_NETBIOS, /// or AF_UNSPEC. ///  [MarshalAs(UnmanagedType.U4)] public UInt32 ClientAddressFamily; ///  ///  The client network address. ///  [MarshalAs(UnmanagedType.ByValArray, SizeConst = WTSAPI32_CONSTANTS.CLIENTADDRESS_LENGTH + 1)] public UInt16[] ClientAddress; ///  ///  Horizontal dimension, in pixels, of the client's display. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 HRes; ///  ///  Vertical dimension, in pixels, of the client's display. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 VRes; ///  ///  Color depth of the client's display. For possible values, see /// the ColorDepth member of the WTS_CLIENT_DISPLAY structure. ///  ///  [MarshalAs(UnmanagedType.U2)] public UInt16 ColorDepth; ///  ///  The location of the client ActiveX control DLL. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)] public string ClientDirectory; ///  ///  The client build number. ///  [MarshalAs(UnmanagedType.U4)] public UInt32 ClientBuildNumber; ///  ///  Reserved. ///  [MarshalAs(UnmanagedType.U4)] public UInt32 ClientHardwareId; ///  ///  Reserved. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 ClientProductId; ///  ///  The number of output buffers on the server per session. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 OutBufCountHost; ///  ///  The number of output buffers on the client. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 OutBufCountClient; ///  ///  The length of the output buffers, in bytes. ///  [MarshalAs(UnmanagedType.U2)] public UInt16 OutBufLength; ///  ///  The device ID of the network adapter. ///  [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)] public string DeviceId; } ///  /// Contains information about a Remote Desktop Services session. ///  [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] [Serializable] public struct WTSINFO { ///  /// A value of the WTS_CONNECTSTATE_CLASS enumeration type that indicates the /// session's current connection state. ///  public WTS_CONNECTSTATE_CLASS State; public UInt32 SessionId; public UInt32 IncomingBytes; public UInt32 OutgoingBytes; public UInt32 IncomingFrames; public UInt32 OutgoingFrames; public UInt32 IncomingCompressedBytes; public UInt32 OutgoingCompressedBytes; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.WINSTATIONNAME_LENGTH)] public String WinStationName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.DOMAIN_LENGTH)] public String Domain; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.USERNAME_LENGTH + 1)] public String UserName; [MarshalAs(UnmanagedType.I8)] public Int64 ConnectTime; [MarshalAs(UnmanagedType.I8)] public Int64 DisconnectTime; [MarshalAs(UnmanagedType.I8)] public Int64 LastInputTime; [MarshalAs(UnmanagedType.I8)] public Int64 LogonTime; [MarshalAs(UnmanagedType.I8)] public Int64 CurrentTime; } ///  /// Contains information about a client session on a Remote Desktop Session /// Host (RD Session Host) server. ///  /// http://pinvoke.net/ /// MSDN [StructLayout(LayoutKind.Sequential)] [Serializable] public struct WTS_SESSION_INFO { ///  /// A Terminal Services session identifier. To indicate the session in which the /// calling application is running.
///
public int iSessionID; /// /// Pointer to a null-terminated string that contains the WinStation name of this /// session. The WinStation name is a name that Windows associates with the session, /// for example, "services", "console", or /// "RDP-Tcp#0". /// [MarshalAs(UnmanagedType.LPStr)] public string sWinsWorkstationName; /// /// A value from the WTS_CONNECTSTATE_CLASS enumeration type that indicates /// the session's current connection state. /// /// MSDN /// public WTS_CONNECTSTATE_CLASS oState; } // Structure for Terminal Service Session Client Display /// /// Contains information about the display of a Remote Desktop Connection (RDC) /// client. /// /// MSDN [StructLayout(LayoutKind.Sequential)] public struct WTS_CLIENT_DISPLAY { /// /// Horizontal dimension, in pixels, of the client's display. /// public int iHorizontalResolution; /// /// Vertical dimension, in pixels, of the client's display. /// public int iVerticalResolution; //1 = The display uses 4 bits per pixel for a maximum of 16 colors. //2 = The display uses 8 bits per pixel for a maximum of 256 colors. //4 = The display uses 16 bits per pixel for a maximum of 2^16 colors. //8 = The display uses 3-byte RGB values for a maximum of 2^24 colors. //16 = The display uses 15 bits per pixel for a maximum of 2^15 colors. public int iColorDepth; } #endregion struct members /************************************************************************************************/ /* enum members */ /************************************************************************************************/ #region enum members /// /// Specifies the connection state of a Remote Desktop Services session. /// /// MSDN public enum WTS_CONNECTSTATE_CLASS { /// /// A user is logged on to the WinStation. /// WTSActive, /// /// The WinStation is connected to the client. /// WTSConnected, /// /// The WinStation is in the process of connecting to the client. /// WTSConnectQuery, /// /// The WinStation is shadowing another WinStation. /// WTSShadow, /// /// The WinStation is active but the client is disconnected. /// WTSDisconnected, /// /// The WinStation is waiting for a client to connect. /// WTSIdle, /// /// The WinStation is listening for a connection. A listener session waits for /// requests for new client connections. No user is logged on a listener session. A /// listener session cannot be reset, shadowed, or changed to a regular client /// session. /// WTSListen, /// /// The WinStation is being reset. /// WTSReset, /// /// The WinStation is down due to an error. /// WTSDown, /// /// The WinStation is initializing. /// WTSInit } /// /// A USHORT value that specifies information about the protocol type /// for the session. This is one of the following values: /// /// MSDN public enum WTS_CLIENT_PROTOCOL_TYPE : ushort { /// /// The console session. /// CONSOLE = 0, /// /// This value is retained for legacy purposes. /// LEGACY, /// /// The RDP protocol /// RDP, /// /// Custom value for internal use /// UNKNOWN } /// /// Contains values that indicate the type of session information to retrieve in a call to the function. /// public enum WTS_INFO_CLASS { /// /// A null-terminated string that contains the name of the initial program that Remote Desktop Services runs when the user logs on. /// WTSInitialProgram, /// /// A null-terminated string that contains the published name of the application that the session is running. /// WTSApplicationName, /// /// A null-terminated string that contains the default directory used when launching the initial program. /// WTSWorkingDirectory, /// /// This value is not used. /// WTSOEMId, /// /// A ULONG value that contains the session identifier. /// WTSSessionId, /// /// A null-terminated string that contains the name of the user associated with the session. /// WTSUserName, /// /// A null-terminated string that contains the name of the Remote Desktop Services session. /// /// /// Note Despite its name, specifying this type does not return the window station name. /// Rather, it returns the name of the Remote Desktop Services session. /// Each Remote Desktop Services session is associated with an interactive window station. /// Because the only supported window station name for an interactive window station is "WinSta0", /// each session is associated with its own "WinSta0" window station. For more information, see Window Stations. /// WTSWinStationName, /// /// A null-terminated string that contains the name of the domain to which the logged-on user belongs. /// WTSDomainName, /// /// The session's current connection state. For more information, see . /// WTSConnectState, /// /// A ULONG value that contains the build number of the client. /// WTSClientBuildNumber, /// /// A null-terminated string that contains the name of the client. /// WTSClientName, /// /// A null-terminated string that contains the directory in which the client is installed. /// WTSClientDirectory, /// /// A USHORT client-specific product identifier. /// WTSClientProductId, /// /// A ULONG value that contains a client-specific hardware identifier. This /// option is reserved for future use. PInvoke function /// WTSQuerySessionInformation will always return a value of 0. /// WTSClientHardwareId, /// /// The network type and network address of the client. For more information, see . /// /// The IP address is offset by two bytes from the start of the Address member of the structure. WTSClientAddress, /// /// Information about the display resolution of the client. For more information, see . /// WTSClientDisplay, /// /// A USHORT value that specifies information about the protocol type for the session. This is one of the following values:
/// 0 - The console session.
/// 1 - This value is retained for legacy purposes.
/// 2 - The RDP protocol.
///
WTSClientProtocolType, /// /// This value returns FALSE. If you call PInvoke function /// GetLastError to get extended error information, GetLastError returns /// ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: /// This value is not used. /// WTSIdleTime, /// /// This value returns FALSE. If you call the pinvoke GetLastError() to get extended error information, GetLastError returns ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: This value is not used. /// WTSLogonTime, /// /// This value returns FALSE. If you call the pinvoke GetLastError() to get extended error information, GetLastError returns ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: This value is not used. /// WTSIncomingBytes, /// /// This value returns FALSE. If you call the pinvoke GetLastError() to get extended error information, GetLastError returns ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: This value is not used. /// WTSOutgoingBytes, /// /// This value returns FALSE. If you call the pinvoke GetLastError() to get extended error information, GetLastError returns ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: This value is not used. /// WTSIncomingFrames, /// /// This value returns FALSE. If you call the pinvoke GetLastError() to get extended error information, GetLastError returns ERROR_NOT_SUPPORTED. /// /// /// Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP: This value is not used. /// WTSOutgoingFrames, /// /// Information about a Remote Desktop Connection (RDC) client. For more /// information, see . /// /// /// Windows Vista, Windows Server 2003, and Windows XP: This value is not /// supported. This value is supported beginning with Windows Server 2008 and /// Windows Vista with SP1. /// WTSClientInfo, /// /// Information about a client session on an RD Session Host server. For more /// information, see . /// /// /// Windows Vista, Windows Server 2003, and Windows XP: This value is not /// supported. This value is supported beginning with Windows Server 2008 and /// Windows Vista with SP1. /// WTSSessionInfo } #endregion }

Here ist the last part, I use this to query session changed events:

 using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using System.Linq; using System.Windows.Forms; using Rdp.Interfaces; using Microsoft.Win32; using System.ServiceProcess; using System.Diagnostics; using System.Threading; using Balzers.Misc.Helpers; namespace Rdp.Service { ///  ///  Terminal session info provider has 2 main functions: ///  ///  /// Provide all current terminal session information:  ///  ///  /// Observer terminal session changes:  ///  ///  ///  public class RdpSessionInfo : IDisposable { /************************************************************************************************/ /* DllImports */ /************************************************************************************************/ #region DllImports [DllImport("wtsapi32.dll", SetLastError = true)] static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName); [DllImport("wtsapi32.dll")] static extern void WTSCloseServer(IntPtr hServer); [DllImport("wtsapi32.dll")] static extern int WTSEnumerateSessions( IntPtr pServer, [MarshalAs(UnmanagedType.U4)] int iReserved, [MarshalAs(UnmanagedType.U4)] int iVersion, ref IntPtr pSessionInfo, [MarshalAs(UnmanagedType.U4)] ref int iCount); [DllImport("Wtsapi32.dll")] static extern bool WTSQuerySessionInformation( System.IntPtr pServer, int iSessionID, WTS_INFO_CLASS oInfoClass, out System.IntPtr pBuffer, out uint iBytesReturned); [DllImport("Wtsapi32.dll")] public static extern bool WTSWaitSystemEvent( IntPtr hServer, UInt32 EventMask, out IntPtr pEventFlags); [DllImport("wtsapi32.dll")] static extern void WTSFreeMemory(IntPtr pMemory); [DllImport("user32.dll")] public static extern int ExitWindowsEx(int uFlags, int dwReason); [DllImport("WtsApi32.dll")] private static extern bool WTSRegisterSessionNotification(IntPtr hWnd, [MarshalAs(UnmanagedType.U4)]int dwFlags); [DllImport("WtsApi32.dll")] private static extern bool WTSUnRegisterSessionNotification(IntPtr hWnd); public delegate int ServiceControlHandlerEx(int control, int eventType, IntPtr eventData, IntPtr context); [DllImport("advapi32.dll", SetLastError = true)] public static extern IntPtr RegisterServiceCtrlHandlerEx(string lpServiceName, ServiceControlHandlerEx cbex, IntPtr context); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle(IntPtr hObject); [DllImport("user32.dll")] static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent); #endregion #region Constants public const int SERVICE_CONTROL_STOP = 1; public const int SERVICE_CONTROL_DEVICEEVENT = 11; public const int SERVICE_CONTROL_SHUTDOWN = 5; public const int SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E; // WTSWaitSystemEvent local server handle public const int WTS_CURRENT_SERVER_HANDLE = 0; public const int WTS_CURRENT_SESSION = 0; [Flags] public enum WaitSystemEventFlags { /* ===================================================================== == EVENT - Event flags for WTSWaitSystemEvent ===================================================================== */ None = 0x00000000, // return no event CreatedWinstation = 0x00000001, // new WinStation created DeletedWinstation = 0x00000002, // existing WinStation deleted RenamedWinstation = 0x00000004, // existing WinStation renamed ConnectedWinstation = 0x00000008, // WinStation connect to client DisconnectedWinstation = 0x00000010, // WinStation logged on without client LogonUser = 0x00000020, // user logged on to existing WinStation LogoffUser = 0x00000040, // user logged off from existing WinStation WinstationStateChange = 0x00000080, // WinStation state change LicenseChange = 0x00000100, // license state change AllEvents = 0x7fffffff, // wait for all event types // Unfortunately cannot express this as an unsigned long... //FlushEvent = 0x80000000 // unblock all waiters } public const UInt32 FlushEvent = 0x80000000; #endregion /************************************************************************************************/ /* Private members */ /************************************************************************************************/ #region Private members private String m_ServerName = Environment.MachineName; private bool m_unregistered = false; private ServiceControlHandlerEx myCallback; private bool tsObserverRunning = false; private Thread tsObserverThread; IntPtr hServ; #endregion /************************************************************************************************/ /* Constructors */ /************************************************************************************************/ #region Constructors ///  /// Initializes a new instance of the RdpSessionInfo class. ///  ///  public RdpSessionInfo() : this(Environment.MachineName) { } ///  /// Initializes a new instance of the  class. ///  ///  public RdpSessionInfo(String ServerName) : base() { this.m_ServerName = ServerName; this.hServ = WTSOpenServer(this.m_ServerName); tsObserverThread = new Thread(StartTerminalSessionObservation); tsObserverThread.Start(hServ); } ~RdpSessionInfo() { } #endregion Constructors /************************************************************************************************/ /* Methods */ /************************************************************************************************/ #region Public methods public void StartTerminalSessionObservation(object hServ) { string msg; IntPtr pEvents = IntPtr.Zero; IntPtr hServer = (IntPtr)hServ; List oldSessions, newSessions; TerminalSessionInfo tsi; WM_WTSSESSION_CHANGE_TYPE changeType; // initial read actual sessions oldSessions = ListSessions(false); newSessions = new List(oldSessions.ToArray()); tsObserverRunning = true; while(this.tsObserverRunning) { if(WTSWaitSystemEvent(hServer, (UInt32)WaitSystemEventFlags.AllEvents, out pEvents)) { WaitSystemEventFlags eventType = GetSystemEventType(pEvents); switch(eventType) { case WaitSystemEventFlags.ConnectedWinstation: case WaitSystemEventFlags.CreatedWinstation: case WaitSystemEventFlags.LogonUser: newSessions = ListSessions(false); tsi = GetChangedTerminalSession(oldSessions, newSessions, out changeType); oldSessions.Clear(); oldSessions.AddRange(newSessions.ToArray()); if(tsi != null && tsi.SessionInfo.iSessionID != 0) OnSessionChanged(new TerminalSessionChangedEventArgs(changeType, tsi.SessionInfo.iSessionID, tsi)); break; case WaitSystemEventFlags.DeletedWinstation: case WaitSystemEventFlags.DisconnectedWinstation: case WaitSystemEventFlags.LogoffUser: case WaitSystemEventFlags.WinstationStateChange: newSessions = ListSessions(false); tsi = GetChangedTerminalSession(oldSessions, newSessions, out changeType); oldSessions.Clear(); oldSessions.AddRange(newSessions.ToArray()); if(tsi != null && tsi.SessionInfo.iSessionID != 0) OnSessionChanged(new TerminalSessionChangedEventArgs(changeType, tsi.SessionInfo.iSessionID, tsi)); break; default: break; } } else { uint winErrorCode = Win32Sec.GetLastError(); msg = new System.ComponentModel.Win32Exception((int)winErrorCode).Message; WindowsEventLogHelper.WriteEventLog(msg, EventLogEntryType.Error); WindowsEventLogHelper.WriteEventLog(RdpControl.SVC_NAME + " " + System.Reflection.MethodInfo.GetCurrentMethod().Name + " - methode failed: " + msg, EventLogEntryType.Error); } Thread.Sleep(100); } WTSCloseServer(hServer); } public void StopTerminalSessionObservation(object hServ) { this.tsObserverRunning = false; IntPtr pEvents = IntPtr.Zero; // unlock the waiter WTSWaitSystemEvent((IntPtr)hServ, FlushEvent, out pEvents); tsObserverThread.Join(200); } public static IntPtr OpenServer(String Name) { IntPtr server = WTSOpenServer(Name); return server; } public static void CloseServer(IntPtr ServerHandle) { WTSCloseServer(ServerHandle); } ///  /// Read all session info running on the system. ///  /// If set to , then only Rdp sessions /// will be listed; otherwise, all session types  . public List ListSessions(bool RdpOnly) { IntPtr server = IntPtr.Zero; List ret = new List(); //server = OpenServer(this.m_ServerName); try { IntPtr ppSessionInfo = IntPtr.Zero; Int32 count = 0; Int32 retval = WTSEnumerateSessions(this.hServ, 0, 1, ref ppSessionInfo, ref count); Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); Int64 current = (int)ppSessionInfo; if(retval != 0) { for(int i = 0; i < count; i++) { TerminalSessionInfo tsi = GetSessionInfo(this.hServ, (System.IntPtr)current); current += dataSize; if(tsi.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.RDP || !RdpOnly) ret.Add(tsi); } WTSFreeMemory(ppSessionInfo); } } finally { //CloseServer(server); } return ret; } #endregion Public methods #region Private methods private TerminalSessionInfo GetChangedTerminalSession(List oldSessions, List newSessions, out WM_WTSSESSION_CHANGE_TYPE sessionChangeType) { TerminalSessionInfo retval = new TerminalSessionInfo(0); sessionChangeType = (WM_WTSSESSION_CHANGE_TYPE)0; // session added if(newSessions.Count > oldSessions.Count) { retval = newSessions.Where(s => oldSessions.Where(old => old.SessionInfo.iSessionID == s.SessionInfo.iSessionID).ToList().Count == 0).FirstOrDefault(); if(retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSConnected || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSActive || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSConnectQuery) sessionChangeType = (retval.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.RDP) ? WM_WTSSESSION_CHANGE_TYPE.WTS_REMOTE_CONNECT : WM_WTSSESSION_CHANGE_TYPE.WTS_CONSOLE_CONNECT; } else if(newSessions.Count < oldSessions.Count) { retval = oldSessions.Where(s => newSessions.Where(old => old.SessionInfo.iSessionID == s.SessionInfo.iSessionID).ToList().Count == 0).FirstOrDefault(); retval.SessionInfo.oState = WTS_CONNECTSTATE_CLASS.WTSDisconnected; retval.WtsInfo.State = WTS_CONNECTSTATE_CLASS.WTSDisconnected; sessionChangeType = (retval.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.RDP) ? WM_WTSSESSION_CHANGE_TYPE.WTS_REMOTE_DISCONNECT : WM_WTSSESSION_CHANGE_TYPE.WTS_CONSOLE_DISCONNECT; } else { retval = newSessions.Where(s => oldSessions.Where(old => old.SessionInfo.iSessionID == s.SessionInfo.iSessionID && old.SessionInfo.oState != s.SessionInfo.oState).ToList().Count > 0 && s.SessionInfo.iSessionID != 0).FirstOrDefault(); if(retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSConnected || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSActive || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSConnectQuery) sessionChangeType = (retval.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.RDP) ? WM_WTSSESSION_CHANGE_TYPE.WTS_REMOTE_CONNECT : WM_WTSSESSION_CHANGE_TYPE.WTS_CONSOLE_CONNECT; else if(retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSDisconnected || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSDown || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSIdle || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSListen || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSReset || retval.SessionInfo.oState == WTS_CONNECTSTATE_CLASS.WTSShadow) sessionChangeType = (retval.ProtocolType == WTS_CLIENT_PROTOCOL_TYPE.RDP) ? WM_WTSSESSION_CHANGE_TYPE.WTS_REMOTE_DISCONNECT : WM_WTSSESSION_CHANGE_TYPE.WTS_CONSOLE_DISCONNECT; } return retval; } private WaitSystemEventFlags GetSystemEventType(IntPtr pEvents) { if(((int)pEvents & (int)WaitSystemEventFlags.ConnectedWinstation) == (int)WaitSystemEventFlags.ConnectedWinstation) return WaitSystemEventFlags.ConnectedWinstation; else if(((int)pEvents & (int)WaitSystemEventFlags.CreatedWinstation) == (int)WaitSystemEventFlags.CreatedWinstation) return WaitSystemEventFlags.CreatedWinstation; else if(((int)pEvents & (int)WaitSystemEventFlags.DisconnectedWinstation) == (int)WaitSystemEventFlags.DisconnectedWinstation) return WaitSystemEventFlags.DisconnectedWinstation; else if(((int)pEvents & (int)WaitSystemEventFlags.LicenseChange) == (int)WaitSystemEventFlags.LicenseChange) return WaitSystemEventFlags.LicenseChange; else if(((int)pEvents & (int)WaitSystemEventFlags.LogoffUser) == (int)WaitSystemEventFlags.LogoffUser) return WaitSystemEventFlags.LogoffUser; else if(((int)pEvents & (int)WaitSystemEventFlags.LogonUser) == (int)WaitSystemEventFlags.LogonUser) return WaitSystemEventFlags.LogonUser; else if(((int)pEvents & (int)WaitSystemEventFlags.RenamedWinstation) == (int)WaitSystemEventFlags.RenamedWinstation) return WaitSystemEventFlags.RenamedWinstation; else if(((int)pEvents & (int)WaitSystemEventFlags.WinstationStateChange) == (int)WaitSystemEventFlags.WinstationStateChange) return WaitSystemEventFlags.WinstationStateChange; else return WaitSystemEventFlags.None; } ///  ///  private TerminalSessionInfo GetSessionInfo(IntPtr pServer, IntPtr pSessionInfo) { int iCurrent = (int)pSessionInfo; uint iReturned = 0; WTS_CLIENT_ADDRESS oClientAddres = new WTS_CLIENT_ADDRESS(); WTS_CLIENT_DISPLAY oClientDisplay = new WTS_CLIENT_DISPLAY(); WTS_CLIENT_PROTOCOL_TYPE oClientProtocol = WTS_CLIENT_PROTOCOL_TYPE.UNKNOWN; WTS_CLIENT_INFO oClientInfo = new WTS_CLIENT_INFO(); WTSINFO oWtsInfo = new WTSINFO(); string sIPAddress = string.Empty; string sUserName = string.Empty, sClientName = string.Empty; string sDomain = string.Empty; string sClientApplicationDirectory = string.Empty; TerminalSessionInfo retval = new TerminalSessionInfo(0); // Get session info structure WTS_SESSION_INFO oSessionInfo = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)iCurrent, typeof(WTS_SESSION_INFO)); //Get the IP address of the Terminal Services User IntPtr pAddress = IntPtr.Zero; if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientAddress, out pAddress, out iReturned) == true) { oClientAddres = (WTS_CLIENT_ADDRESS)Marshal.PtrToStructure(pAddress, oClientAddres.GetType()); sIPAddress = oClientAddres.bAddress[2] + "." + oClientAddres.bAddress[3] + "." + oClientAddres.bAddress[4] + "." + oClientAddres.bAddress[5]; } //Get the User Name of the Terminal Services User if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSUserName, out pAddress, out iReturned) == true) { sUserName = Marshal.PtrToStringAnsi(pAddress); } //Get the Client Name of the Terminal Services User if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientName, out pAddress, out iReturned) == true) { sClientName = Marshal.PtrToStringAnsi(pAddress); } //Get the Domain Name of the Terminal Services User if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSDomainName, out pAddress, out iReturned) == true) { sDomain = Marshal.PtrToStringAnsi(pAddress); } //Get the Display Information of the Terminal Services User if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientDisplay, out pAddress, out iReturned) == true) { oClientDisplay = (WTS_CLIENT_DISPLAY)Marshal.PtrToStructure(pAddress, oClientDisplay.GetType()); } //Get the Application Directory of the Terminal Services User if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientDirectory, out pAddress, out iReturned) == true) { sClientApplicationDirectory = Marshal.PtrToStringAnsi(pAddress); } //Get protocol type if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientProtocolType, out pAddress, out iReturned) == true) { oClientProtocol = (WTS_CLIENT_PROTOCOL_TYPE)Marshal.ReadInt16(pAddress); } //Get client info if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSClientInfo, out pAddress, out iReturned) == true) { oClientInfo = (WTS_CLIENT_INFO)Marshal.PtrToStructure(pAddress, oClientInfo.GetType()); //sUserName = String.IsNullOrEmpty(sUserName) ? oClientInfo.UserName : sUserName; } //Get WTS info if(WTSQuerySessionInformation(pServer, oSessionInfo.iSessionID, WTS_INFO_CLASS.WTSSessionInfo, out pAddress, out iReturned) == true) { oWtsInfo = (WTSINFO)Marshal.PtrToStructure(pAddress, oWtsInfo.GetType()); } // fill result retval.SessionInfo = oSessionInfo; //retval.SessionInfo.oState = oSessionInfo.oState; //retval.SessionInfo.sWinsWorkstationName = oSessionInfo.sWinsWorkstationName == null ? "" : oSessionInfo.sWinsWorkstationName; retval.UserName = sUserName == null ? "" : sUserName; retval.ClientMachineName = sClientName == null ? "" : sClientName; retval.ClientIPAddress = sIPAddress == null ? "" : sIPAddress; retval.Domain = sDomain == null ? "" : sDomain; retval.ProtocolType = oClientProtocol; retval.ClientInfo = oClientInfo; retval.WtsInfo = oWtsInfo; return retval; } #endregion Private methods #region Handlers private event TerminalSessionChangedEventHandler mSessionChangedEventHandler; ///  /// Occurs when a terminal session has changed. ///  ///  /// Following change types will be observed:  and  ///  /// WTSRegisterSessionNotification public event TerminalSessionChangedEventHandler SessionChanged { add { if(mSessionChangedEventHandler == null || !mSessionChangedEventHandler.GetInvocationList().Contains(value)) mSessionChangedEventHandler += value; } remove { mSessionChangedEventHandler -= value; } } public void OnSessionChanged(TerminalSessionChangedEventArgs SessionChangedEventArg) { if(mSessionChangedEventHandler != null) { TerminalSessionChangedSaveInvoker.SafeInvokeEvent(mSessionChangedEventHandler, SessionChangedEventArg); } } #endregion Handlers #region IDisposable Members public void Dispose() { if(!m_unregistered) { StopTerminalSessionObservation(this.hServ); m_unregistered = true; } } #endregion } } 

There is some ballast and unused waste in it but you can pick out the essentials. This should work within desktop session as windows service too.

4.userB logout you shall use SessionSwitchReason.ConsoleDisConnect as the reason.

5.userA restore session you shall use SessionSwitchReason.ConsoleConnect as the reason.