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; });
}