Vanara/System/Computer/Sessions.cs

466 lines
23 KiB
C#
Raw Normal View History

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;
/// <summary>Represents a Windows session.</summary>
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() { }
/// <summary>
/// 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.
/// </summary>
public WTS_CLIENT_ADDRESS ClientAddress => QueryInfo<WTS_CLIENT_ADDRESS>(WTS_INFO_CLASS.WTSClientAddress);
//{
// get
// {
// var ca = QueryInfo<WTS_CLIENT_ADDRESS>(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);
// }
// }
//}
/// <summary>A ULONG value that contains the build number of the client.</summary>
public uint ClientBuildNumber => QueryInfo<uint>(WTS_INFO_CLASS.WTSClientBuildNumber);
/// <summary>A string that contains the directory in which the client is installed.</summary>
public string ClientDirectory => QueryInfo<string>(WTS_INFO_CLASS.WTSClientDirectory);
/// <summary>Information about the display resolution of the client. For more information, see WTS_CLIENT_DISPLAY.</summary>
public WTS_CLIENT_DISPLAY ClientDisplay => QueryInfo<WTS_CLIENT_DISPLAY>(WTS_INFO_CLASS.WTSClientDisplay);
/// <summary>A string that contains the name of the client.</summary>
public string ClientName => QueryInfo<string>(WTS_INFO_CLASS.WTSClientName);
/// <summary>A USHORT client-specific product identifier.</summary>
public ushort ClientProductId => QueryInfo<ushort>(WTS_INFO_CLASS.WTSClientProductId);
/// <summary>A value that specifies information about the protocol type for the session.</summary>
public SessionProtocolType ClientProtocolType => QueryInfo<SessionProtocolType>(WTS_INFO_CLASS.WTSClientProtocolType);
/// <summary>The session's current connection state. For more information, see WTS_CONNECTSTATE_CLASS.</summary>
public WTS_CONNECTSTATE_CLASS ConnectionState => QueryInfo<WTS_CONNECTSTATE_CLASS>(WTS_INFO_CLASS.WTSConnectState);
/// <summary>
/// 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.
/// </summary>
public DateTime ConnectTime => DateTime.FromFileTimeUtc(SessionInfo.ConnectTimeUTC);
/// <summary>
/// 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.
/// </summary>
public DateTime CurrentTime => DateTime.FromFileTimeUtc(SessionInfo.CurrentTimeUTC);
/// <summary>The device ID of the network adapter.</summary>
public string DeviceId => ClientInfo.DeviceId;
/// <summary>
/// <para>Specifies whether the printer connected to the client is the default printer for the user.</para>
/// <para>0</para>
/// <para>The printer connected to the client is not the default printer for the user.</para>
/// <para>1</para>
/// <para>The printer connected to the client is the default printer for the user.</para>
/// </summary>
public bool DisableDefaultMainClientPrinter => ConfigInfo.fDisableDefaultMainClientPrinter;
/// <summary>
/// <para>Specifies whether the client can use printer redirection.</para>
/// <para>0</para>
/// <para>Enable client printer redirection.</para>
/// <para>1</para>
/// <para>Disable client printer redirection.</para>
/// </summary>
public bool DisablePrinterRedirection => ConfigInfo.fDisablePrinterRedirection;
/// <summary>
/// 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.
/// </summary>
public DateTime DisconnectTime => DateTime.FromFileTimeUtc(SessionInfo.DisconnectTimeUTC);
/// <summary>
/// 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 <see langword="null"/>.
/// </summary>
public string DomainName => sessionInfo.pDomainName;
/// <summary>
/// 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 <see langword="null"/>.
/// </summary>
public string FarmName => sessionInfo.pFarmName;
/// <summary>
/// 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 <see langword="null"/>. If the session is running on a virtual
/// machine, the string contains the name of the virtual machine.
/// </summary>
public string HostName => sessionInfo.pHostName;
/// <summary>
/// The number of bytes of uncompressed Remote Desktop Protocol (RDP) data sent from the client to the server since the client connected.
/// </summary>
public uint IncomingBytes => SessionInfo.IncomingBytes;
/// <summary>The number of bytes of compressed RDP data sent from the client to the server since the client connected.</summary>
public uint IncomingCompressedBytes => SessionInfo.IncomingCompressedBytes;
/// <summary>The number of frames of RDP data sent from the client to the server since the client connected.</summary>
public uint IncomingFrames => SessionInfo.IncomingFrames;
/// <summary>A string that contains the name of the initial program that Remote Desktop Services runs when the user logs on.</summary>
public string InitialProgram { get { try { return QueryInfo<string>(WTS_INFO_CLASS.WTSInitialProgram); } catch { return string.Empty; } } }
/// <summary>
/// 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.
/// </summary>
public bool IsRemoteSession => WTSQuerySessionInformation(hServer, SessionId, WTS_INFO_CLASS.WTSIsRemoteSession, out _, out _);
/// <summary>
/// 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.
/// </summary>
public DateTime LastInputTime => DateTime.FromFileTimeUtc(SessionInfo.LastInputTimeUTC);
/// <summary>
/// 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).
/// </summary>
public DateTime LogonTime => DateTime.FromFileTimeUtc(SessionInfo.LogonTimeUTC);
/// <summary>A string that contains the name of this session. For example, "services", "console", or "RDP-Tcp#0".</summary>
public string Name => sessionInfo.pSessionName;
/// <summary>The number of bytes of uncompressed RDP data sent from the server to the client since the client connected.</summary>
public uint OutgoingBytes => SessionInfo.OutgoingBytes;
/// <summary>The number of bytes of compressed RDP data sent from the server to the client since the client connected.</summary>
public uint OutgoingCompressedBytes => SessionInfo.OutgoingCompressedBytes;
/// <summary>The number of frames of RDP data sent from the server to the client since the client connected.</summary>
public uint OutgoingFrames => SessionInfo.OutgoingFrames;
/// <summary>
/// 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.
/// </summary>
public IPAddress SessionAddressV4
{
get
{
try { return new(QueryInfo<WTS_SESSION_ADDRESS>(WTS_INFO_CLASS.WTSSessionAddressV4).Address.Take(4).ToArray()); }
catch { return null; }
}
}
/// <summary>
/// <para>The state of the session. This can be one or more of the following values.</para>
/// <para>WTS_SESSIONSTATE_UNKNOWN (4294967295 (0xFFFFFFFF))</para>
/// <para>The session state is not known.</para>
/// <para>WTS_SESSIONSTATE_LOCK (0 (0x0))</para>
/// <para>The session is locked.</para>
/// <para>WTS_SESSIONSTATE_UNLOCK (1 (0x1))</para>
/// <para>The session is unlocked.</para>
/// <para>
/// <c>Windows Server 2008 R2 and Windows 7:</c> Due to a code defect, the usage of the <c>WTS_SESSIONSTATE_LOCK</c> and
/// <c>WTS_SESSIONSTATE_UNLOCK</c> flags is reversed. That is, <c>WTS_SESSIONSTATE_LOCK</c> indicates that the session is unlocked, and
/// <c>WTS_SESSIONSTATE_UNLOCK</c> indicates the session is locked.
/// </para>
/// </summary>
public WTS_SESSIONSTATE SessionFlags => SessionInfoEx.Data.WTSInfoExLevel1.SessionFlags;
/// <summary>A session identifier assigned by the RD Session Host server, RD Virtualization Host server, or virtual machine.</summary>
public uint SessionId => sessionInfo.SessionId;
/// <summary>
/// <para>
/// 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.
/// </para>
/// <para>0</para>
/// <para>Remote control is disabled.</para>
/// <para>1</para>
/// <para>The user of remote control has full control of the user's session, with the user's permission.</para>
/// <para>2</para>
/// <para>The user of remote control has full control of the user's session; the user's permission is not required.</para>
/// <para>3</para>
/// <para>
/// The user of remote control can view the session remotely, with the user's permission; the remote user cannot actively control the session.
/// </para>
/// <para>4</para>
/// <para>
/// The user of remote control can view the session remotely but not actively control the session; the user's permission is not required.
/// </para>
/// </summary>
public uint ShadowSettings => ConfigInfo.ShadowSettings;
/// <summary>
/// Gets a string that specifies the DNS or NetBIOS name of the remote server on which the local group resides. If this value is <see
/// langword="null"/>, the local computer is assumed.
/// </summary>
/// <value>The target server.</value>
public string Target { get; }
/// <summary>
/// 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 <see langword="null"/>.
/// </summary>
public string UserName => sessionInfo.pUserName;
/// <summary>A string that contains the name of the Remote Desktop Services session.</summary>
public string WinStationName => QueryInfo<string>(WTS_INFO_CLASS.WTSWinStationName);
/// <summary>A string that contains the default directory used when launching the initial program.</summary>
public string WorkingDirectory => QueryInfo<string>(WTS_INFO_CLASS.WTSWorkingDirectory);
internal WindowsIdentity Id { get; private set; }
/// <summary>Information about a Remote Desktop Connection (RDC) client. For more information, see WTSCLIENT.</summary>
private WTSCLIENT ClientInfo => QueryInfo<WTSCLIENT>(WTS_INFO_CLASS.WTSClientInfo);
/// <summary>
/// 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.
/// </summary>
private WTSCONFIGINFO ConfigInfo => QueryInfo<WTSCONFIGINFO>(WTS_INFO_CLASS.WTSConfigInfo);
/// <summary>
/// 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.
/// </summary>
private WTSINFO SessionInfo => QueryInfo<WTSINFO>(WTS_INFO_CLASS.WTSSessionInfo);
/// <summary>
/// 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.
/// </summary>
private WTSINFOEX SessionInfoEx => QueryInfo<WTSINFOEX>(WTS_INFO_CLASS.WTSSessionInfoEx);
/// <summary>
/// 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.
/// </summary>
/// <param name="wait">
/// Indicates whether the operation is synchronous. Specify <c>TRUE</c> to wait for the operation to complete, or <c>FALSE</c> to
/// return immediately.
/// </param>
/// <returns>
/// <para>If the function succeeds, the return value is a nonzero value.</para>
/// <para>If the function fails, the return value is zero. To get extended error information, call GetLastError.</para>
/// </returns>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public bool Disconnect(bool wait) => WTSDisconnectSession(hServer, SessionId, wait);
/// <summary>Logs off a specified Remote Desktop Services session.</summary>
/// <param name="wait">
/// <para>Indicates whether the operation is synchronous.</para>
/// <para>If wait is <c>TRUE</c>, the function returns when the session is logged off.</para>
/// <para>
/// If wait is <c>FALSE</c>, the function returns immediately. To verify that the session has been logged off, specify the session
/// identifier in a call to the WTSQuerySessionInformation function. <c>WTSQuerySessionInformation</c> returns zero if the session
/// is logged off.
/// </para>
/// </param>
/// <returns>
/// <para>If the function succeeds, the return value is a nonzero value.</para>
/// <para>If the function fails, the return value is zero. To get extended error information, call GetLastError.</para>
/// </returns>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public bool Logoff(bool wait) => WTSLogoffSession(hServer, SessionId, wait);
/// <summary>Displays a message box on the client desktop of a specified Remote Desktop Services session.</summary>
/// <param name="title">A pointer to a null-terminated string for the title bar of the message box.</param>
/// <param name="message">A pointer to a null-terminated string that contains the message to display.</param>
/// <param name="style">
/// The contents and behavior of the message box. This value is typically <c>MB_OK</c>. For a complete list of values, see the uType
/// parameter of the MessageBox function.
/// </param>
/// <param name="timeout">
/// The time, in seconds, that the <c>WTSSendMessage</c> function waits for the user's response. If the user does not respond within the
/// time-out interval, the response parameter returns <c>IDTIMEOUT</c>. If the timeout parameter is zero, <c>WTSSendMessage</c> waits
/// indefinitely for the user to respond.
/// </param>
/// <param name="wait">
/// <para>
/// If <c>TRUE</c>, <c>WTSSendMessage</c> 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.
/// </para>
/// <para>
/// If <c>FALSE</c>, the function returns immediately and the response parameter returns <c>IDASYNC</c>. Use this method for simple
/// information messages (such as print jobnotification messages) that do not need to return the user's response to the calling program.
/// </para>
/// </param>
/// <param name="response">
/// <para>A pointer to a variable that receives the user's response, which can be one of the following values.</para>
/// <para>IDABORT (3)</para>
/// <para><c>Abort</c></para>
/// <para>IDCANCEL (2)</para>
/// <para><c>Cancel</c></para>
/// <para>IDCONTINUE (11)</para>
/// <para><c>Continue</c></para>
/// <para>IDIGNORE (5)</para>
/// <para><c>Ignore</c></para>
/// <para>IDNO (7)</para>
/// <para><c>No</c></para>
/// <para>IDOK (1)</para>
/// <para><c>OK</c></para>
/// <para>IDRETRY (4)</para>
/// <para><c>Retry</c></para>
/// <para>IDTRYAGAIN (10)</para>
/// <para><c>Try Again</c></para>
/// <para>IDYES (6)</para>
/// <para><c>Yes</c></para>
/// <para>IDASYNC (32001 (0x7D01))</para>
/// <para>The wait parameter was <c>FALSE</c>, so the function returned without waiting for a response.</para>
/// <para>IDTIMEOUT (32000 (0x7D00))</para>
/// <para>The wait parameter was <c>TRUE</c> and the time-out interval elapsed.</para>
/// </param>
/// <returns>
/// <para>If the function succeeds, the return value is a nonzero value.</para>
/// <para>If the function fails, the return value is zero. To get extended error information, call GetLastError.</para>
/// </returns>
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<T>(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<T>(sz);
}
}
/// <summary>A collection of Windows sessions for the specified target computer.</summary>
public class Sessions : IReadOnlyDictionary<uint, Session>
{
private readonly SafeHWTSSERVER hServer;
private readonly WindowsIdentity identity;
private readonly string target = null;
/// <summary>Initializes a new instance of the <see cref="SharedDevices"/> class.</summary>
/// <param name="serverName">Name of the computer from which to retrieve and manage the shared devices.</param>
/// <param name="accessIdentity">
/// The Windows identity used to access the shared device information. If this value <see langword="null"/>, the current identity is used.
/// </param>
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)
{
}
/// <summary>Gets the number of elements in the collection.</summary>
public int Count => EnumSessions().Length;
/// <summary>Gets an <see cref="ICollection{TValue}"/> containing the keys in the <see cref="IDictionary{TKey, TValue}"/>.</summary>
/// <value>An <see cref="ICollection{TValue}"/> containing the keys in the object that implements <see cref="IDictionary{TKey, TValue}"/>.</value>
public IEnumerable<uint> Keys => Array.ConvertAll(EnumSessions(), i => i.SessionId);
/// <summary>Gets an <see cref="ICollection{TValue}"/> containing the values in the <see cref="IDictionary{TKey, TValue}"/>.</summary>
/// <value>An <see cref="ICollection{TValue}"/> containing the values in the object that implements <see cref="IDictionary{TKey, TValue}"/>.</value>
public IEnumerable<Session> Values => Array.ConvertAll(EnumSessions(), i => new Session(target, i));
/// <summary>Gets the <see cref="Session"/> with the specified key.</summary>
/// <value>The <see cref="Session"/>.</value>
/// <param name="key">The key.</param>
/// <returns>The <see cref="Session"/> instance with the specified session id.</returns>
/// <exception cref="System.ArgumentOutOfRangeException">key</exception>
public Session this[uint key] => TryGetValue(key, out var sess) ? sess : throw new ArgumentOutOfRangeException(nameof(key));
/// <summary>Determines whether the read-only dictionary contains an element that has the specified key.</summary>
/// <param name="key">The key to locate.</param>
/// <returns><see langword="true"/> if the read-only dictionary contains an element that has the specified key; otherwise, <see langword="false"/>.</returns>
public bool ContainsKey(uint key) => EnumSessions().Any(s => Equals(key, s.SessionId));
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>An enumerator that can be used to iterate through the collection.</returns>
public IEnumerator<KeyValuePair<uint, Session>> GetEnumerator() => EnumSessions().ToDictionary(i => i.SessionId, i => new Session(target, i)).GetEnumerator();
/// <summary>Gets the value associated with the specified key.</summary>
/// <param name="key">The key whose value to get.</param>
/// <param name="value">
/// 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 <paramref name="value"/> parameter. This parameter is passed uninitialized.
/// </param>
/// <returns>
/// <see langword="true"/> if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise,
/// <see langword="false"/>.
/// </returns>
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; });
}