Vanara/System/Computer/Sessions.cs

466 lines
23 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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