using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Net; using System.Runtime.InteropServices; using System.Security.Principal; using Vanara.Extensions; using Vanara.PInvoke; using Vanara.Security; using static Vanara.PInvoke.WTSApi32; namespace Vanara; /// Represents a Windows session. public class Session : INamedEntity { private readonly SafeHWTSSERVER hServer; private WTS_SESSION_INFO_1 sessionInfo; internal Session(string target, in WTS_SESSION_INFO_1 info) { sessionInfo = info; //Id = new WindowsIdentity($"{info.pDomainName}\\{info.pUserName}"); hServer = target is null ? SafeHWTSSERVER.Current : WTSOpenServerEx(target); } private Session() { } /// /// The network type and network address of the client. For more information, see WTS_CLIENT_ADDRESS. The IP address is offset by two /// bytes from the start of the Address member of the WTS_CLIENT_ADDRESS structure. /// public WTS_CLIENT_ADDRESS ClientAddress => QueryInfo(WTS_INFO_CLASS.WTSClientAddress); //{ // get // { // var ca = QueryInfo(WTS_INFO_CLASS.WTSClientAddress); // switch ((Ws2_32.ADDRESS_FAMILY)ca.AddressFamily) // { // case Ws2_32.ADDRESS_FAMILY.AF_INET: // try { return IPAddress.Parse(string.Join(".", ca.Address.Skip(2).Take(4))); } // catch { return null; } // case Ws2_32.ADDRESS_FAMILY.AF_INET6: // try { return new(ca.Address.Take(16).ToArray()); } // catch { return null; } // default: // return new(ca.Address); // } // } //} /// A ULONG value that contains the build number of the client. public uint ClientBuildNumber => QueryInfo(WTS_INFO_CLASS.WTSClientBuildNumber); /// A string that contains the directory in which the client is installed. public string ClientDirectory => QueryInfo(WTS_INFO_CLASS.WTSClientDirectory); /// Information about the display resolution of the client. For more information, see WTS_CLIENT_DISPLAY. public WTS_CLIENT_DISPLAY ClientDisplay => QueryInfo(WTS_INFO_CLASS.WTSClientDisplay); /// A string that contains the name of the client. public string ClientName => QueryInfo(WTS_INFO_CLASS.WTSClientName); /// A USHORT client-specific product identifier. public ushort ClientProductId => QueryInfo(WTS_INFO_CLASS.WTSClientProductId); /// A value that specifies information about the protocol type for the session. public SessionProtocolType ClientProtocolType => QueryInfo(WTS_INFO_CLASS.WTSClientProtocolType); /// The session's current connection state. For more information, see WTS_CONNECTSTATE_CLASS. public WTS_CONNECTSTATE_CLASS ConnectionState => QueryInfo(WTS_INFO_CLASS.WTSConnectState); /// /// The time of the most recent client connection to the session. This value is stored as a large integer that represents the number of /// 100-nanosecond intervals since January 1, 1601 Coordinated Universal Time. /// public DateTime ConnectTime => DateTime.FromFileTimeUtc(SessionInfo.ConnectTimeUTC); /// /// The time that this structure was filled. This value is stored as a large integer that represents the number of 100-nanosecond /// intervals since January 1, 1601 Coordinated Universal Time. /// public DateTime CurrentTime => DateTime.FromFileTimeUtc(SessionInfo.CurrentTimeUTC); /// The device ID of the network adapter. public string DeviceId => ClientInfo.DeviceId; /// /// Specifies whether the printer connected to the client is the default printer for the user. /// 0 /// The printer connected to the client is not the default printer for the user. /// 1 /// The printer connected to the client is the default printer for the user. /// public bool DisableDefaultMainClientPrinter => ConfigInfo.fDisableDefaultMainClientPrinter; /// /// Specifies whether the client can use printer redirection. /// 0 /// Enable client printer redirection. /// 1 /// Disable client printer redirection. /// public bool DisablePrinterRedirection => ConfigInfo.fDisablePrinterRedirection; /// /// The time of the most recent client disconnection to the session. This value is stored as a large integer that represents the number /// of 100-nanosecond intervals since January 1, 1601 Coordinated Universal Time. /// public DateTime DisconnectTime => DateTime.FromFileTimeUtc(SessionInfo.DisconnectTimeUTC); /// /// A string that contains the domain name of the user who is logged on to the session. If no user is logged on to the session, the /// string contains . /// public string DomainName => sessionInfo.pDomainName; /// /// A string that contains the name of the farm that the virtual machine is joined to. If the session is not running on a virtual machine /// that is joined to a farm, the string contains . /// public string FarmName => sessionInfo.pFarmName; /// /// A string that contains the name of the computer that the session is running on. If the session is running directly on an RD Session /// Host server or RD Virtualization Host server, the string contains . If the session is running on a virtual /// machine, the string contains the name of the virtual machine. /// public string HostName => sessionInfo.pHostName; /// /// The number of bytes of uncompressed Remote Desktop Protocol (RDP) data sent from the client to the server since the client connected. /// public uint IncomingBytes => SessionInfo.IncomingBytes; /// The number of bytes of compressed RDP data sent from the client to the server since the client connected. public uint IncomingCompressedBytes => SessionInfo.IncomingCompressedBytes; /// The number of frames of RDP data sent from the client to the server since the client connected. public uint IncomingFrames => SessionInfo.IncomingFrames; /// A string that contains the name of the initial program that Remote Desktop Services runs when the user logs on. public string InitialProgram { get { try { return QueryInfo(WTS_INFO_CLASS.WTSInitialProgram); } catch { return string.Empty; } } } /// /// Determines whether the current session is a remote session. The WTSQuerySessionInformation function returns a value of TRUE to /// indicate that the current session is a remote session, and FALSE to indicate that the current session is a local session. This value /// can only be used for the local machine, so the hServer parameter of the WTSQuerySessionInformation function must contain /// WTS_CURRENT_SERVER_HANDLE. Windows Server 2008 and Windows Vista: This value is not supported. /// public bool IsRemoteSession => WTSQuerySessionInformation(hServer, SessionId, WTS_INFO_CLASS.WTSIsRemoteSession, out _, out _); /// /// The time of the last user input in the session. This value is stored as a large integer that represents the number of 100-nanosecond /// intervals since January 1, 1601 Coordinated Universal Time. /// public DateTime LastInputTime => DateTime.FromFileTimeUtc(SessionInfo.LastInputTimeUTC); /// /// The time that the user logged on to the session. This value is stored as a large integer that represents the number of 100-nanosecond /// intervals since January 1, 1601 Coordinated Universal Time (Greenwich Mean Time). /// public DateTime LogonTime => DateTime.FromFileTimeUtc(SessionInfo.LogonTimeUTC); /// A string that contains the name of this session. For example, "services", "console", or "RDP-Tcp#0". public string Name => sessionInfo.pSessionName; /// The number of bytes of uncompressed RDP data sent from the server to the client since the client connected. public uint OutgoingBytes => SessionInfo.OutgoingBytes; /// The number of bytes of compressed RDP data sent from the server to the client since the client connected. public uint OutgoingCompressedBytes => SessionInfo.OutgoingCompressedBytes; /// The number of frames of RDP data sent from the server to the client since the client connected. public uint OutgoingFrames => SessionInfo.OutgoingFrames; /// /// A WTS_SESSION_ADDRESS structure that contains the IPv4 address assigned to the session. If the session does not have a virtual IP /// address, the WTSQuerySessionInformation function returns ERROR_NOT_SUPPORTED.Windows Server 2008 and Windows /// Vista: This value is not supported. /// public IPAddress SessionAddressV4 { get { try { return new(QueryInfo(WTS_INFO_CLASS.WTSSessionAddressV4).Address.Take(4).ToArray()); } catch { return null; } } } /// /// The state of the session. This can be one or more of the following values. /// WTS_SESSIONSTATE_UNKNOWN (4294967295 (0xFFFFFFFF)) /// The session state is not known. /// WTS_SESSIONSTATE_LOCK (0 (0x0)) /// The session is locked. /// WTS_SESSIONSTATE_UNLOCK (1 (0x1)) /// The session is unlocked. /// /// Windows Server 2008 R2 and Windows 7: Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK and /// WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the session is unlocked, and /// WTS_SESSIONSTATE_UNLOCK indicates the session is locked. /// /// public WTS_SESSIONSTATE SessionFlags => SessionInfoEx.Data.WTSInfoExLevel1.SessionFlags; /// A session identifier assigned by the RD Session Host server, RD Virtualization Host server, or virtual machine. public uint SessionId => sessionInfo.SessionId; /// /// /// The remote control setting. Remote control allows a user to remotely monitor the on-screen operations of another user. This member /// can be one of the following values. /// /// 0 /// Remote control is disabled. /// 1 /// The user of remote control has full control of the user's session, with the user's permission. /// 2 /// The user of remote control has full control of the user's session; the user's permission is not required. /// 3 /// /// The user of remote control can view the session remotely, with the user's permission; the remote user cannot actively control the session. /// /// 4 /// /// The user of remote control can view the session remotely but not actively control the session; the user's permission is not required. /// /// public uint ShadowSettings => ConfigInfo.ShadowSettings; /// /// Gets a string that specifies the DNS or NetBIOS name of the remote server on which the local group resides. If this value is , the local computer is assumed. /// /// The target server. public string Target { get; } /// /// A string that contains the name of the user who is logged on to the session. If no user is logged on to the session, the string /// contains . /// public string UserName => sessionInfo.pUserName; /// A string that contains the name of the Remote Desktop Services session. public string WinStationName => QueryInfo(WTS_INFO_CLASS.WTSWinStationName); /// A string that contains the default directory used when launching the initial program. public string WorkingDirectory => QueryInfo(WTS_INFO_CLASS.WTSWorkingDirectory); internal WindowsIdentity Id { get; private set; } /// Information about a Remote Desktop Connection (RDC) client. For more information, see WTSCLIENT. private WTSCLIENT ClientInfo => QueryInfo(WTS_INFO_CLASS.WTSClientInfo); /// /// A WTSCONFIGINFO structure that contains information about the configuration of a RD Session Host server. Windows Server 2008 and /// Windows Vista: This value is not supported. /// private WTSCONFIGINFO ConfigInfo => QueryInfo(WTS_INFO_CLASS.WTSConfigInfo); /// /// information about a session on a RD Session Host server. For more information, see WTSINFOEX. Windows Server 2008 and Windows Vista: /// This value is not supported. /// private WTSINFO SessionInfo => QueryInfo(WTS_INFO_CLASS.WTSSessionInfo); /// /// Extended information about a session on a RD Session Host server. For more information, see WTSINFOEX. Windows Server 2008 and /// Windows Vista: This value is not supported. /// private WTSINFOEX SessionInfoEx => QueryInfo(WTS_INFO_CLASS.WTSSessionInfoEx); /// /// Disconnects the logged-on user from this session without closing the session. If the user /// subsequently logs on to the same Remote Desktop Session Host (RD Session Host) server, the user is reconnected to the same session. /// /// /// Indicates whether the operation is synchronous. Specify TRUE to wait for the operation to complete, or FALSE to /// return immediately. /// /// /// If the function succeeds, the return value is a nonzero value. /// If the function fails, the return value is zero. To get extended error information, call GetLastError. /// /// /// /// To be able to disconnect another user's session, you need to have the Disconnect permission. For more information, see Remote /// Desktop Services Permissions. To modify permissions on a session, use the Remote Desktop Services Configuration administrative tool. /// /// /// To disconnect sessions running on a virtual machine hosted on a RD Virtualization Host server, you must be a member of the /// Administrators group on the RD Virtualization Host server. /// /// public bool Disconnect(bool wait) => WTSDisconnectSession(hServer, SessionId, wait); /// Logs off a specified Remote Desktop Services session. /// /// Indicates whether the operation is synchronous. /// If wait is TRUE, the function returns when the session is logged off. /// /// If wait is FALSE, the function returns immediately. To verify that the session has been logged off, specify the session /// identifier in a call to the WTSQuerySessionInformation function. WTSQuerySessionInformation returns zero if the session /// is logged off. /// /// /// /// If the function succeeds, the return value is a nonzero value. /// If the function fails, the return value is zero. To get extended error information, call GetLastError. /// /// /// /// To be able to log off another user's session, you need to have the Reset permission. For more information, see Remote Desktop /// Services Permissions. To modify permissions on a session, use the Remote Desktop Services Configuration administrative tool. /// /// /// To log off sessions running on a virtual machine hosted on a RD Virtualization Host server, you must be a member of the /// Administrators group on the RD Virtualization Host server. /// /// public bool Logoff(bool wait) => WTSLogoffSession(hServer, SessionId, wait); /// Displays a message box on the client desktop of a specified Remote Desktop Services session. /// A pointer to a null-terminated string for the title bar of the message box. /// A pointer to a null-terminated string that contains the message to display. /// /// The contents and behavior of the message box. This value is typically MB_OK. For a complete list of values, see the uType /// parameter of the MessageBox function. /// /// /// The time, in seconds, that the WTSSendMessage function waits for the user's response. If the user does not respond within the /// time-out interval, the response parameter returns IDTIMEOUT. If the timeout parameter is zero, WTSSendMessage waits /// indefinitely for the user to respond. /// /// /// /// If TRUE, WTSSendMessage does not return until the user responds or the time-out interval elapses. If the timeout /// parameter is zero, the function does not return until the user responds. /// /// /// If FALSE, the function returns immediately and the response parameter returns IDASYNC. Use this method for simple /// information messages (such as print job–notification messages) that do not need to return the user's response to the calling program. /// /// /// /// A pointer to a variable that receives the user's response, which can be one of the following values. /// IDABORT (3) /// Abort /// IDCANCEL (2) /// Cancel /// IDCONTINUE (11) /// Continue /// IDIGNORE (5) /// Ignore /// IDNO (7) /// No /// IDOK (1) /// OK /// IDRETRY (4) /// Retry /// IDTRYAGAIN (10) /// Try Again /// IDYES (6) /// Yes /// IDASYNC (32001 (0x7D01)) /// The wait parameter was FALSE, so the function returned without waiting for a response. /// IDTIMEOUT (32000 (0x7D00)) /// The wait parameter was TRUE and the time-out interval elapsed. /// /// /// If the function succeeds, the return value is a nonzero value. /// If the function fails, the return value is zero. To get extended error information, call GetLastError. /// public bool SendMessage(string title, string message, User32.MB_FLAGS style, TimeSpan timeout, bool wait, out User32.MB_RESULT response) { var ret = WTSSendMessage(hServer, SessionId, title, StringHelper.GetByteCount(title), message, StringHelper.GetByteCount(message), (uint)style, (uint)timeout.TotalSeconds, out var resp, wait); response = (User32.MB_RESULT)resp; return ret; } private T QueryInfo(WTS_INFO_CLASS c) { Win32Error.ThrowLastErrorIfFalse(WTSQuerySessionInformation(hServer, SessionId, c, out var mem, out var sz)); return typeof(T) == typeof(string) ? (T)(object)mem.ToString(sz) : mem.ToStructure(sz); } } /// A collection of Windows sessions for the specified target computer. public class Sessions : IReadOnlyDictionary { private readonly SafeHWTSSERVER hServer; private readonly WindowsIdentity identity; private readonly string target = null; /// Initializes a new instance of the class. /// Name of the computer from which to retrieve and manage the shared devices. /// /// The Windows identity used to access the shared device information. If this value , the current identity is used. /// public Sessions(string serverName = null, WindowsIdentity accessIdentity = null) : base() { target = serverName; identity = accessIdentity ?? WindowsIdentity.GetCurrent(); hServer = target is null ? SafeHWTSSERVER.Current : WTSOpenServerEx(target); } internal Sessions(Computer computer) : this(computer.Target, computer.Identity) { } /// Gets the number of elements in the collection. public int Count => EnumSessions().Length; /// Gets an containing the keys in the . /// An containing the keys in the object that implements . public IEnumerable Keys => Array.ConvertAll(EnumSessions(), i => i.SessionId); /// Gets an containing the values in the . /// An containing the values in the object that implements . public IEnumerable Values => Array.ConvertAll(EnumSessions(), i => new Session(target, i)); /// Gets the with the specified key. /// The . /// The key. /// The instance with the specified session id. /// key public Session this[uint key] => TryGetValue(key, out var sess) ? sess : throw new ArgumentOutOfRangeException(nameof(key)); /// Determines whether the read-only dictionary contains an element that has the specified key. /// The key to locate. /// if the read-only dictionary contains an element that has the specified key; otherwise, . public bool ContainsKey(uint key) => EnumSessions().Any(s => Equals(key, s.SessionId)); /// Returns an enumerator that iterates through the collection. /// An enumerator that can be used to iterate through the collection. public IEnumerator> GetEnumerator() => EnumSessions().ToDictionary(i => i.SessionId, i => new Session(target, i)).GetEnumerator(); /// Gets the value associated with the specified key. /// The key whose value to get. /// /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type /// of the parameter. This parameter is passed uninitialized. /// /// /// if the contains an element with the key; otherwise, /// . /// public bool TryGetValue(uint key, out Session value) { var si = EnumSessions().FirstOrDefault(s => Equals(key, s.SessionId)); if (si.SessionId == 0) { value = null; return false; } value = new Session(target, si); return true; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private WTS_SESSION_INFO_1[] EnumSessions() => identity.Run(() => { Win32Error.ThrowLastErrorIfFalse(WTSEnumerateSessionsEx(hServer, out var sessions)); return sessions; }); }