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

时间:2011-03-18 14:01:01

标签: c# .net windows-xp

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

我尝试了以下内容:

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

    如果发生以下情况:

    1. userA登录
    2. userA切换用户
    3. userB login
    4. userB logout
    5. userA恢复会话
    6. SystemEvents.SessionSwitch

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

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

编辑:

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

5 个答案:

答案 0 :(得分:25)

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

答案 1 :(得分:0)

好的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                                             */
    /************************************************************************************************/
    /// <summary>
    /// Terminal Session Info data holder struct.
    /// </summary>
    /// <remarks>
    /// Structures, interfaces, p-invoke members for Terminal Service Session
    /// </remarks>
    [Serializable]
    public struct TerminalSessionInfo {
        /// <summary>
        ///  Remote Desktop Services API Structure member
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_SESSION_INFO"/>
        public WTS_SESSION_INFO SessionInfo;
        /// <summary>
        ///  Remote Desktop Services API Structure member
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_PROTOCOL_TYPE"/>
        public WTS_CLIENT_PROTOCOL_TYPE ProtocolType;
        /// <summary>
        ///  Remote Desktop Services API Structure member
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO"/>
        public WTS_CLIENT_INFO ClientInfo;
        /// <summary>
        ///  Remote Desktop Services API Structure member
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTSINFO"/>
        public WTSINFO WtsInfo;
        /// <summary>
        ///  The client user name.
        /// </summary>
        public string UserName;
        /// <summary>
        ///  The domain name of the client computer.
        /// </summary>
        public string Domain;
        /// <summary>
        /// The client network address.
        /// </summary>
        public string ClientIPAddress;
        /// <summary>
        /// The machine name of the client computer.
        /// </summary>
        public string ClientMachineName;

        /// <summary>
        /// Initializes a new instance of the <see cref="T:Oerlikon.Balzers.Rdp.Interfaces.TerminalSessionInfo"/> structure.
        /// </summary>
        /// <param name="SessionId">Only used to force an initialization of members.</param>
        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;
        }

        /// <summary>
        /// Returns the fully qualified type name of this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> containing a fully qualified type name.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        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;
        }

        /// <summary>
        /// Help method to find C++ corresponding long value of C# DateTime (starting at 01
        /// / 01 / 1970 00:00:00).
        /// </summary>
        /// <param name="date">.NET object</param>
        /// <param name="localTime">If set to <see langword="true"/>, then date will be
        /// assummed as local time and converted to UTC - time; otherwise, UTC will be
        /// assumed.</param>
        /// <returns>
        /// C++ corresponding long value
        /// </returns>
        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);
        }

        /// <summary>
        ///  Help method to find C# DateTime from C++ corresponding long value.
        /// </summary>
        /// <param name="unixTime">Unix value of date time starting at 01 / 01 / 1970
        /// 00:00:00</param>
        /// <param name="localTime">If set to <see langword="true"/>, then ; otherwise,
        /// .</param>
        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

        /// <summary>
        /// Overriding Operator ==
        /// </summary>
        /// <param name="a">Object to compare</param>
        /// <param name="b">Object to compare</param>
        /// <returns>Return true if the segment start / end values match.</returns>
        public static bool operator ==(TerminalSessionInfo a, TerminalSessionInfo b) {
            return Equals(a, b);
        }

        /// <summary>
        /// Overriding Operator !=
        /// </summary>
        /// <param name="a">Object to compare</param>
        /// <param name="b">Object to compare</param>
        /// <returns>Return true if the segment start / end values match.</returns>
        public static bool operator !=(TerminalSessionInfo a, TerminalSessionInfo b) {
            return !Equals(a, b);
        }

        /// <summary>
        /// Overriding Equals
        /// </summary>
        /// <param name="obj">Object to compare with own instance.</param>
        /// <returns>Return true if the segment start / end values match.</returns>
        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);
        }


        /// <summary>
        /// Memberwise comparison
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        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.*/
        /// <summary>
        /// Gets property with path <see paramref="name"/> from <see paramref="obj"/>.
        /// 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.
        /// </summary>
        /// <param name="obj">Object with properties.</param>
        /// <param name="name">Path to property (e.g.: TimeOfDay.Hours or Ticks)</param>
        /// <returns></returns>
        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.*/
        /// <summary>
        /// Gets property with path <see cref=""/> from <see cref=""/>. 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.
        /// </summary>
        /// <param name="obj">Object with properties.</param>
        /// <param name="name">Path to property (e.g.: TimeOfDay.Hours or Ticks)</param>
        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;
        }

        /// <summary>
        /// Gets value of property with path <paramref name="name"/>.
        /// </summary>
        /// <param name="obj">Object with properties.</param>
        /// <param name="name">Property path.</param>
        /// <example>
        ///     <code><![CDATA[
        /// System.DateTime date = new System.DateTime();
        /// int hours = (int)Balzers.Misc.ReflectionHelper.GetItem(date, "TimeOfDay.Hours");
        /// long ticks = (long)Balzers.Misc.ReflectionHelper.GetItem(date, "Ticks");
        /// ]]></code>
        /// </example>
        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
    }
}

答案 2 :(得分:0)

这是下一部分:

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
    /// </remarks>
    [StructLayout(LayoutKind.Sequential)]
    public struct WTS_CLIENT_ADDRESS {
        /// <summary>
        ///  Address family. This member can
        /// be <b>AF_INET</b>, <b>AF_INET6</b>, <b>AF_IPX</b>, <b>AF_NETBIOS</b>,
        /// or <b>AF_UNSPEC</b>.
        /// </summary>
        public int iAddressFamily;
        /// <summary>
        /// Client network address. The format of the field of <b>Address</b> depends on the
        /// address type as specified by the <b>AddressFamily</b> member.
        /// <para>For an address family <b>AF_INET</b>: <b>Address </b>contains the IPV4
        /// address of the client as a null-terminated string.</para>
        ///     <para>For an family <b>AF_INET6</b>: <b>Address </b>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")</para>
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)]
        public byte[] bAddress;
    }

    /// <summary>
    /// Maximum string lengths constants used within RDP API structures <see href="https://msdn.microsoft.com/en-us/library/bb736369(v=vs.85).aspx#">MSDN</see>
    /// </summary>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/bb736369(v=vs.85).aspx">MSDN
    /// Example</seealso>
    public struct WTSAPI32_CONSTANTS {
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int USERNAME_LENGTH = 20;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int CLIENTNAME_LENGTH = 20;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int CLIENTADDRESS_LENGTH = 30;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int MAX_ELAPSED_TIME_LENGTH = 15;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int MAX_DATE_TIME_LENGTH = 15;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int WINSTATIONNAME_LENGTH = 32;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int DOMAIN_LENGTH = 17;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int WTS_DRIVE_LENGTH = 3;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int WTS_LISTENER_NAME_LENGTH = 32;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int WTS_COMMENT_LENGTH = 60;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int PRODUCTINFO_COMPANYNAME_LENGTH = 256;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int PRODUCTINFO_PRODUCTID_LENGTH = 4;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int VALIDATIONINFORMATION_LICENSE_LENGTH = 16384;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int VALIDATIONINFORMATION_HARDWAREID_LENGTH = 20;
        /// <summary>
        /// Maximum string lengths constants used within RDP API structures
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO">Example
        /// WTS_CLIENT_INFO</seealso>
        public const int MAX_PATH = 260;

    }

    //Structure for Terminal Service Client Infostructure
    /// <summary>
    /// Contains information about a Remote Desktop Connection (RDC) client.
    /// </summary>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/bb736369(v=vs.85).aspx#">MSDN</seealso>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    [Serializable]
    public struct WTS_CLIENT_INFO {
        /// <summary>
        ///  The NetBIOS name of the client computer.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.CLIENTNAME_LENGTH + 1)]
        public string ClientMachineName;
        /// <summary>
        ///  The domain name of the client computer.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.DOMAIN_LENGTH + 1)]
        public string Domain;
        /// <summary>
        ///  The client user name.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.USERNAME_LENGTH + 1)]
        public string UserName;
        /// <summary>
        ///  The folder for the initial program.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)]
        public string WorkDirectory;
        /// <summary>
        ///  The program to start on connection.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)]
        public string InitialProgram;
        /// <summary>
        ///  The security level of encryption.
        /// </summary>
        [MarshalAs(UnmanagedType.U1)]
        public byte EncryptionLevel;
        /// <summary>
        ///  The address family. This member can
        /// be <b>AF_INET</b>, <b>AF_INET6</b>, <b>AF_IPX</b>, <b>AF_NETBIOS</b>,
        /// or <b>AF_UNSPEC</b>.
        /// </summary>
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 ClientAddressFamily;
        /// <summary>
        ///  The client network address.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = WTSAPI32_CONSTANTS.CLIENTADDRESS_LENGTH + 1)]
        public UInt16[] ClientAddress;
        /// <summary>
        ///  Horizontal dimension, in pixels, of the client's display.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 HRes;
        /// <summary>
        ///  Vertical dimension, in pixels, of the client's display.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 VRes;
        /// <summary>
        ///  Color depth of the client's display. For possible values, see
        /// the <b>ColorDepth</b> member of the <b>WTS_CLIENT_DISPLAY</b> structure.
        /// </summary>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_DISPLAY"/>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 ColorDepth;
        /// <summary>
        ///  The location of the client ActiveX control DLL.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)]
        public string ClientDirectory;
        /// <summary>
        ///  The client build number.
        /// </summary>
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 ClientBuildNumber;
        /// <summary>
        ///  Reserved.
        /// </summary>
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 ClientHardwareId;
        /// <summary>
        ///  Reserved.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 ClientProductId;
        /// <summary>
        ///  The number of output buffers on the server per session.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 OutBufCountHost;
        /// <summary>
        ///  The number of output buffers on the client.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 OutBufCountClient;
        /// <summary>
        ///  The length of the output buffers, in bytes.
        /// </summary>
        [MarshalAs(UnmanagedType.U2)]
        public UInt16 OutBufLength;
        /// <summary>
        ///  The device ID of the network adapter.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = WTSAPI32_CONSTANTS.MAX_PATH + 1)]
        public string DeviceId;
    }

    /// <summary>
    /// Contains information about a Remote Desktop Services session.
    /// </summary>
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    [Serializable]
    public struct WTSINFO {
        /// <summary>
        /// A value of the <b>WTS_CONNECTSTATE_CLASS</b> enumeration type that indicates the
        /// session's current connection state.
        /// </summary>
        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;
    }

    /// <summary>
    ///     <para>Contains information about a client session on a Remote Desktop Session
    /// Host (RD Session Host) server.</para>
    /// </summary>
    /// <seealso href="http://pinvoke.net/default.aspx/Structures/_WTS_SESSION_INFO.html">http://pinvoke.net/</seealso>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/aa383864(v=vs.85).aspx">MSDN</seealso>
    [StructLayout(LayoutKind.Sequential)]
    [Serializable]
    public struct WTS_SESSION_INFO {
        /// <summary>
        /// A Terminal Services session identifier. To indicate the session in which the
        /// calling application is running.<br/>
        /// </summary>
        public int iSessionID;
        /// <summary>
        /// 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".
        /// </summary>
        [MarshalAs(UnmanagedType.LPStr)]
        public string sWinsWorkstationName;
        /// <summary>
        /// A value from the <b>WTS_CONNECTSTATE_CLASS</b> enumeration type that indicates
        /// the session's current connection state.
        /// </summary>
        /// <seealso href="https://msdn.microsoft.com/en-us/library/aa383860(v=vs.85).aspx">MSDN</seealso>
        /// <seealso cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CONNECTSTATE_CLASS"/>
        public WTS_CONNECTSTATE_CLASS oState;
    }

    // Structure for Terminal Service Session Client Display
    /// <summary>
    /// Contains information about the display of a Remote Desktop Connection (RDC)
    /// client.
    /// </summary>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/aa383858(v=vs.85).aspx">MSDN</seealso>
    [StructLayout(LayoutKind.Sequential)]
    public struct WTS_CLIENT_DISPLAY {
        /// <summary>
        /// Horizontal dimension, in pixels, of the client's display.
        /// </summary>
        public int iHorizontalResolution;
        /// <summary>
        ///  Vertical dimension, in pixels, of the client's display.
        /// </summary>
        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
    /// <summary>
    /// Specifies the connection state of a Remote Desktop Services session.
    /// </summary>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/aa383860(v=vs.85).aspx">MSDN</seealso>
    public enum WTS_CONNECTSTATE_CLASS {
        /// <summary>
        ///  A user is logged on to the WinStation.
        /// </summary>
        WTSActive,
        /// <summary>
        ///  The WinStation is connected to the client.
        /// </summary>
        WTSConnected,
        /// <summary>
        ///  The WinStation is in the process of connecting to the client.
        /// </summary>
        WTSConnectQuery,
        /// <summary>
        ///  The WinStation is shadowing another WinStation.
        /// </summary>
        WTSShadow,
        /// <summary>
        ///  The WinStation is active but the client is disconnected.
        /// </summary>
        WTSDisconnected,
        /// <summary>
        ///  The WinStation is waiting for a client to connect.
        /// </summary>
        WTSIdle,
        /// <summary>
        ///  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.
        /// </summary>
        WTSListen,
        /// <summary>
        ///  The WinStation is being reset.
        /// </summary>
        WTSReset,
        /// <summary>
        ///  The WinStation is down due to an error.
        /// </summary>
        WTSDown,
        /// <summary>
        /// The WinStation is initializing.
        /// </summary>
        WTSInit
    }

    /// <summary>
    ///     <para>A <b>USHORT</b> value that specifies information about the protocol type
    /// for the session. This is one of the following values:</para>
    /// </summary>
    /// <seealso href="https://msdn.microsoft.com/en-us/library/aa383861(v=vs.85).aspx">MSDN</seealso>
    public enum WTS_CLIENT_PROTOCOL_TYPE : ushort {
        /// <summary>
        /// The console session.
        /// </summary>
        CONSOLE = 0,
        /// <summary>
        ///  This value is retained for legacy purposes.
        /// </summary>
        LEGACY,
        /// <summary>
        ///  The RDP protocol
        /// </summary>
        RDP,
        /// <summary>
        ///  Custom value for internal use
        /// </summary>
        UNKNOWN
    }

    /// <summary>
    /// Contains values that indicate the type of session information to retrieve in a call to the <see cref="WTSQuerySessionInformation"/> function.
    /// </summary>
    public enum WTS_INFO_CLASS {
        /// <summary>
        /// A null-terminated string that contains the name of the initial program that Remote Desktop Services runs when the user logs on.
        /// </summary>
        WTSInitialProgram,

        /// <summary>
        /// A null-terminated string that contains the published name of the application that the session is running.
        /// </summary>
        WTSApplicationName,

        /// <summary>
        /// A null-terminated string that contains the default directory used when launching the initial program.
        /// </summary>
        WTSWorkingDirectory,

        /// <summary>
        /// This value is not used.
        /// </summary>
        WTSOEMId,

        /// <summary>
        /// A <B>ULONG</B> value that contains the session identifier.
        /// </summary>
        WTSSessionId,

        /// <summary>
        /// A null-terminated string that contains the name of the user associated with the session.
        /// </summary>
        WTSUserName,

        /// <summary>
        /// A null-terminated string that contains the name of the Remote Desktop Services session. 
        /// </summary>
        /// <remarks>
        /// <B>Note</B>  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 <see href="http://msdn.microsoft.com/en-us/library/windows/desktop/ms687096(v=vs.85).aspx">Window Stations</see>.
        /// </remarks>
        WTSWinStationName,

        /// <summary>
        /// A null-terminated string that contains the name of the domain to which the logged-on user belongs.
        /// </summary>
        WTSDomainName,

        /// <summary>
        /// The session's current connection state. For more information, see <see cref="WTS_CONNECTSTATE_CLASS"/>.
        /// </summary>
        WTSConnectState,

        /// <summary>
        /// A <B>ULONG</B> value that contains the build number of the client.
        /// </summary>
        WTSClientBuildNumber,

        /// <summary>
        /// A null-terminated string that contains the name of the client.
        /// </summary>
        WTSClientName,

        /// <summary>
        /// A null-terminated string that contains the directory in which the client is installed.
        /// </summary>
        WTSClientDirectory,

        /// <summary>
        /// A <B>USHORT</B> client-specific product identifier.
        /// </summary>
        WTSClientProductId,

        /// <summary>
        /// A <b>ULONG</b> 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.
        /// </summary>
        WTSClientHardwareId,

        /// <summary>
        /// The network type and network address of the client. For more information, see <see cref="WTS_CLIENT_ADDRESS"/>.
        /// </summary>
        /// <remarks>The IP address is offset by two bytes from the start of the <B>Address</B> member of the <see cref="WTS_CLIENT_ADDRESS"/> structure.</remarks>
        WTSClientAddress,

        /// <summary>
        /// Information about the display resolution of the client. For more information, see <see cref="WTS_CLIENT_DISPLAY"/>.
        /// </summary>
        WTSClientDisplay,

        /// <summary>
        /// A USHORT value that specifies information about the protocol type for the session. This is one of the following values:<BR/>
        /// 0 - The console session.<BR/>
        /// 1 - This value is retained for legacy purposes.<BR/>
        /// 2 - The RDP protocol.<BR/>
        /// </summary>
        WTSClientProtocolType,

        /// <summary>
        ///     <para>This value returns <b>FALSE</b>. If you call PInvoke function
        /// GetLastError to get extended error information, <b>GetLastError</b> returns
        /// <b>ERROR_NOT_SUPPORTED</b>.</para>
        /// </summary>
        /// <remarks>
        ///     <b>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</b>
        /// This value is not used.
        /// </remarks>
        WTSIdleTime,

        /// <summary>
        /// This value returns <B>FALSE</B>. If you call the pinvoke GetLastError() to get extended error information, <B>GetLastError</B> returns <B>ERROR_NOT_SUPPORTED</B>.
        /// </summary>
        /// <remarks>
        /// <B>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</B>  This value is not used.
        /// </remarks>
        WTSLogonTime,

        /// <summary>
        /// This value returns <B>FALSE</B>. If you call the pinvoke GetLastError() to get extended error information, <B>GetLastError</B> returns <B>ERROR_NOT_SUPPORTED</B>.
        /// </summary>
        /// <remarks>
        /// <B>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</B>  This value is not used.
        /// </remarks>
        WTSIncomingBytes,

        /// <summary>
        /// This value returns <B>FALSE</B>. If you call the pinvoke GetLastError() to get extended error information, <B>GetLastError</B> returns <B>ERROR_NOT_SUPPORTED</B>.
        /// </summary>
        /// <remarks>
        /// <B>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</B>  This value is not used.
        /// </remarks>
        WTSOutgoingBytes,

        /// <summary>
        /// This value returns <B>FALSE</B>. If you call the pinvoke GetLastError() to get extended error information, <B>GetLastError</B> returns <B>ERROR_NOT_SUPPORTED</B>.
        /// </summary>
        /// <remarks>
        /// <B>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</B>  This value is not used.
        /// </remarks>
        WTSIncomingFrames,

        /// <summary>
        /// This value returns <B>FALSE</B>. If you call the pinvoke GetLastError() to get extended error information, <B>GetLastError</B> returns <B>ERROR_NOT_SUPPORTED</B>.
        /// </summary>
        /// <remarks>
        /// <B>Windows Server 2008, Windows Vista, Windows Server 2003, and Windows XP:</B>  This value is not used.
        /// </remarks>
        WTSOutgoingFrames,

        /// <summary>
        /// Information about a Remote Desktop Connection (RDC) client. For more
        /// information, see <see cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_INFO"/>.
        /// </summary>
        /// <remarks>
        ///     <b>Windows Vista, Windows Server 2003, and Windows XP:</b> This value is not
        /// supported. This value is supported beginning with Windows Server 2008 and
        /// Windows Vista with SP1.
        /// </remarks>
        WTSClientInfo,

        /// <summary>
        /// Information about a client session on an RD Session Host server. For more
        /// information, see <see cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_SESSION_INFO"/>.
        /// </summary>
        /// <remarks>
        ///     <b>Windows Vista, Windows Server 2003, and Windows XP:</b> This value is not
        /// supported. This value is supported beginning with Windows Server 2008 and
        /// Windows Vista with SP1.
        /// </remarks>
        WTSSessionInfo
    }

    #endregion

}

答案 3 :(得分:0)

这是最后一部分,我用它来查询会话更改事件:

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 {

/// <summary>
///     <para> Terminal session info provider has 2 main functions:</para>
///     <list type="number">
///         <item>
///             <description>Provide all current terminal session information: <see cref="M:Oerlikon.Balzers.Rdp.Service.RdpSessionInfo.ListSessions(System.Boolean)"/></description>
///         </item>
///         <item>
///             <description>Observer terminal session changes: <see cref="E:Oerlikon.Balzers.Rdp.Service.RdpSessionInfo.SessionChanged"/></description>
///         </item>
///     </list>
/// </summary>
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
    /// <summary>
    /// Initializes a new instance of the <see cref="T:Oerlikon.Balzers.Rdp.Service.RdpSessionInfo">RdpSessionInfo</see> class. 
    /// </summary>
    /// <remarks></remarks>
    public RdpSessionInfo() : this(Environment.MachineName) { }

    /// <summary>
    /// Initializes a new instance of the <see cref="T:Oerlikon.Balzers.Rdp.Service.RdpSessionInfo"/> class.
    /// </summary>
    /// <param name="ServerName"></param>
    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<TerminalSessionInfo> oldSessions, newSessions;
        TerminalSessionInfo tsi;
        WM_WTSSESSION_CHANGE_TYPE changeType;

        // initial read actual sessions
        oldSessions = ListSessions(false);
        newSessions = new List<TerminalSessionInfo>(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);
    }

    /// <summary>
    /// Read all session info running on the system.
    /// </summary>
    /// <param name="RdpOnly">If set to <see langword="true"/>, then only Rdp sessions
    /// will be listed; otherwise, all session types <see cref="T:Oerlikon.Balzers.Rdp.Interfaces.WTS_CLIENT_PROTOCOL_TYPE"/> .</param>
    public List<TerminalSessionInfo> ListSessions(bool RdpOnly) {
        IntPtr server = IntPtr.Zero;
        List<TerminalSessionInfo> ret = new List<TerminalSessionInfo>();
        //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<TerminalSessionInfo> oldSessions, List<TerminalSessionInfo> 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;


    }     

    /// <param name="pServer"></param>
    /// <param name="pSessionInfo"></param>
    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;

    /// <summary>
    /// Occurs when a terminal session has changed.
    /// </summary>
    /// <remarks>
    /// Following change types will be observed: <see cref="T:Oerlikon.Balzers.Rdp.Interfaces.WM_WTSSESSION_CHANGE"/> and <see cref="F:Oerlikon.Balzers.Rdp.Interfaces.WM_WTSSESSION_CHANGE.WM_WTSSESSION_CHANGE"/>
    /// </remarks>
    /// <seealso href="http://pinvoke.net/default.aspx/wtsapi32/WTSRegisterSessionNotification.html">WTSRegisterSessionNotification</seealso>
    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
}
}

有一些镇流器和未使用的废物,但你可以挑选出必需品。 这应该在桌面会话中作为Windows服务工作。

答案 4 :(得分:-1)

4.userB注销 您应使用SessionSwitchReason.ConsoleDisConnect作为原因。

5.userA恢复会话 您应使用SessionSwitchReason.ConsoleConnect作为原因。