How to make a .NET Windows Service detect Logon, L

2019-01-22 08:57发布

I need to track the current in user (the one using the console) on Windows XP SP3.

I tried the following:

  • Microsoft.Win32.SystemEvents.SessionSwitch: Works for single logon/logout events, but fails to detect switch user.

    If the following occurs:

    1. userA log in
    2. userA switch user
    3. userB login
    4. userB logout
    5. userA restore session

    Events 3 and 4 are not detected by SystemEvents.SessionSwitch

  • Monitoring the "Security" EventLog: Events are inconsistent and arrive out of order. For instance, if the list above is replayed, I receive an event id 528 (Logon), followed by two 538 (Logoff) for userA after he restores his session. Checking event.TimeGenerated doesn't help. This method also does not work if auditing is disabled on SecPol.msc.

  • P/Invoking WTSRegisterSessionNotification: Works fine. I had to create a hidden form, override its WndProc to handle WM_WTSSESSION_CHANGE messages, and then call WTSQuerySessionInformation to get the username associated with the event. This method looks too complex, is there a simpler way?

Edit:

  • Calling WTSGetActiveConsoleSessionId every n milliseconds works too, but I'm looking for an event based method.

5条回答
可以哭但决不认输i
2楼-- · 2019-01-22 09:29

Ok the WTSRegisterSessionNotification - solution only works with a control or form in your hand, but what about a console applicaton or class running in a windows service environment and have no the ServiceBase object because its a plugin - based architecture.

Here are some structure definitions first:

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
    }
}
查看更多
老娘就宠你
3楼-- · 2019-01-22 09:30

Here ist the next part:

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

}
查看更多
放我归山
4楼-- · 2019-01-22 09:33

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 {

/// <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
}
}

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.

查看更多
Luminary・发光体
5楼-- · 2019-01-22 09:36

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

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

查看更多
Root(大扎)
6楼-- · 2019-01-22 09:38

If you're making a service, your class is derived from ServiceBase. Consider investigating the OnSessionChange method. Override this method to detect different types of session changes. It provides both a reason for the session change, and the new session identifier. Be sure in your constructor to set CanHandleSessionChangeEvent to true, otherwise your override will not be called.

查看更多
登录 后发表回答