Skip to content

Instantly share code, notes, and snippets.

@trackd
Created November 7, 2025 09:54
Show Gist options
  • Select an option

  • Save trackd/3a0cc979261d6f879fbf87e0c1649398 to your computer and use it in GitHub Desktop.

Select an option

Save trackd/3a0cc979261d6f879fbf87e0c1649398 to your computer and use it in GitHub Desktop.
Powershell qwinsta/quser replacement using pinvoke
<#
qwinsta/quser pinvoke thing.
expanded to add Get-WTSClientInfo, Get-WTSInfo, Remove-WTSSession
Get-WTSSessionInfo also grabs a bit more info, specially with -Detailed param.
Based on jborean93's WTSAPI wrapper
https://gist.github.com/jborean93/729410684e8e30f6d79123f0bcd06d9c
#>
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace PSWtsapi32
{
public enum WtsConnectState : int
{
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ne-wtsapi32-wts_connectstate_class
Active,
Connected,
ConnectQuery,
Shadow,
Disconnected,
Idle,
Listen,
Reset,
Down,
Init
}
public enum WTS_TYPE_CLASS : int
{
WTSTypeProcessInfoLevel0 = 0,
WTSTypeProcessInfoLevel1 = 1,
WTSTypeSessionInfoLevel1 = 2
}
public enum WTS_INFO_CLASS : int
{
WTSInitialProgram = 0,
WTSApplicationName = 1,
WTSWorkingDirectory = 2,
WTSOEMId = 3,
WTSSessionId = 4,
WTSUserName = 5,
WTSWinStationName = 6,
WTSDomainName = 7,
WTSConnectState = 8,
WTSClientBuildNumber = 9,
WTSClientName = 10,
WTSClientDirectory = 11,
WTSClientProductId = 12,
WTSClientHardwareId = 13,
WTSClientAddress = 14,
WTSClientDisplay = 15,
WTSClientProtocolType = 16,
WTSIdleTime = 17,
WTSLogonTime = 18,
WTSIncomingBytes = 19,
WTSOutgoingBytes = 20,
WTSIncomingFrames = 21,
WTSOutgoingFrames = 22,
WTSClientInfo = 23,
WTSSessionInfo = 24,
WTSSessionInfoEx = 25,
WTSConfigInfo = 26,
WTSValidationInfo = 27,
WTSSessionAddressV4 = 28,
WTSIsRemoteSession = 29
}
public class SessionInfo
{
public uint SessionId { get; set; }
public WtsConnectState State { get; set; }
public string SessionName { get; set; }
// public string HostName { get; set; }
public string UserName { get; set; }
public string DomainName { get; set; }
// public string FarmName { get; set; }
public string ComputerName { get; set; }
public string ClientName { get; set; }
}
public class SessionInfoExtra : SessionInfo
{
public TimeSpan? IdleTime { get; set; }
public DateTime? LogonTime { get; set; }
}
public class ClientInfo
{
public string ClientName { get; set; }
public string Domain { get; set; }
public string UserName { get; set; }
public string WorkDirectory { get; set; }
public string InitialProgram { get; set; }
public byte EncryptionLevel { get; set; }
public AddressFamily ClientAddressFamily { get; set; }
public IPAddress ClientAddress { get; set; }
public ushort HRes { get; set; }
public ushort VRes { get; set; }
public ushort ColorDepth { get; set; }
public string ClientDirectory { get; set; }
public uint ClientBuildNumber { get; set; }
public uint ClientHardwareId { get; set; }
public ushort ClientProductId { get; set; }
public ushort OutBufCountHost { get; set; }
public ushort OutBufCountClient { get; set; }
public ushort OutBufLength { get; set; }
public string DeviceId { get; set; }
}
public class WTSInfo
{
public WtsConnectState State { get; set; }
public uint SessionId { get; set; }
public uint IncomingBytes { get; set; }
public uint OutgoingBytes { get; set; }
public uint IncomingFrames { get; set; }
public uint OutgoingFrames { get; set; }
public uint IncomingCompressedBytes { get; set; }
public uint OutgoingCompressedBy { get; set; }
public string WinStationName { get; set; }
public string Domain { get; set; }
public string UserName { get; set; }
public DateTime? ConnectTime { get; set; }
public DateTime? DisconnectTime { get; set; }
public DateTime? LastInputTime { get; set; }
public DateTime? LogonTime { get; set; }
public DateTime? CurrentTime { get; set; }
}
public class Native
{
public static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
public const uint WTS_ANY_SESSION = 0xFFFFFFFF;
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct WTS_SESSION_INFO_1W
{
public uint ExecEnvId;
public WtsConnectState State;
public uint SessionId;
[MarshalAs(UnmanagedType.LPTStr)]
public string pSessionName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pHostName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pUserName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pDomainName;
[MarshalAs(UnmanagedType.LPTStr)]
public string pFarmName;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WTSCLIENT
{
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wtsclientw
// https://stackoverflow.com/a/35550796
private const int CLIENTNAME_LENGTH = 20;
private const int DOMAIN_LENGTH = 17;
private const int USERNAME_LENGTH = 20;
private const int MAX_PATH = 260;
private const int CLIENTADDRESS_LENGTH = 30;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CLIENTNAME_LENGTH + 1)]
public string ClientName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = DOMAIN_LENGTH + 1)]
public string Domain;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = USERNAME_LENGTH + 1)]
public string UserName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
public string WorkDirectory;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
public string InitialProgram;
public byte EncryptionLevel;
public uint ClientAddressFamily;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = CLIENTADDRESS_LENGTH + 1)]
public ushort[] ClientAddress;
public ushort HRes;
public ushort VRes;
public ushort ColorDepth;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
public string ClientDirectory;
public uint ClientBuildNumber;
public uint ClientHardwareId;
public ushort ClientProductId;
public ushort OutBufCountHost;
public ushort OutBufCountClient;
public ushort OutBufLength;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH + 1)]
public string DeviceId;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct WTSINFOW
{
// https://learn.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wtsinfow
private const int WINSTATIONNAME_LENGTH = 32;
private const int DOMAIN_LENGTH = 17;
private const int USERNAME_LENGTH = 20;
public WtsConnectState State;
public uint SessionId;
public uint IncomingBytes;
public uint OutgoingBytes;
public uint IncomingFrames;
public uint OutgoingFrames;
public uint IncomingCompressedBytes;
public uint OutgoingCompressedBytes;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = WINSTATIONNAME_LENGTH)]
public string WinStationName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = DOMAIN_LENGTH)]
public string Domain;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = USERNAME_LENGTH + 2)]
public string UserName;
public long ConnectTimeUTC;
public long DisconnectTimeUTC;
public long LastInputTimeUTC;
public long LogonTimeUTC;
public long CurrentTimeUTC;
}
[DllImport("Wtsapi32.dll")]
public static extern void WTSCloseServer(IntPtr hServer);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool WTSEnumerateSessionsExW(
IntPtr hServer,
ref int pLevel,
int Filter,
out IntPtr ppSessionInfo,
out int pCount);
public static SessionInfo[] WTSEnumerateSessionsExtra(IntPtr serverHandle, string serverName)
{
int level = 1;
IntPtr rawInfo;
int count;
if (!WTSEnumerateSessionsExW(serverHandle, ref level, 0, out rawInfo, out count))
throw new System.ComponentModel.Win32Exception();
if (level != 1)
throw new InvalidOperationException(string.Format("Expected level 1 but got level {0}", level));
if (rawInfo == IntPtr.Zero)
return new SessionInfo[0];
try
{
var infoCollection = new List<SessionInfo>();
int structSize = Marshal.SizeOf<WTS_SESSION_INFO_1W>();
IntPtr currentOffset = rawInfo;
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO_1W info = Marshal.PtrToStructure<WTS_SESSION_INFO_1W>(currentOffset);
if (info.pUserName == null)
{
// Skip the session if the username is null.
currentOffset = IntPtr.Add(currentOffset, structSize);
continue;
}
string clientName = null;
if (info.State != WtsConnectState.Disconnected && info.pSessionName != "console")
{
// hack in the clientname for active sessions and not the console
clientName = QuerySessionInfoString(serverHandle, info.SessionId, WTS_INFO_CLASS.WTSClientName);
}
infoCollection.Add(new SessionInfo() {
SessionId = info.SessionId,
State = info.State,
SessionName = info.pSessionName,
// HostName = info.pHostName,
UserName = info.pUserName,
DomainName = info.pDomainName,
// FarmName = info.pFarmName,
ComputerName = serverName,
ClientName = clientName,
});
currentOffset = IntPtr.Add(currentOffset, structSize);
}
return infoCollection.ToArray();
}
finally
{
if (rawInfo != IntPtr.Zero)
WTSFreeMemoryExW((int)WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, rawInfo, count);
}
}
public static SessionInfoExtra[] WTSEnumerateSessionsWithDetails(IntPtr serverHandle, string serverName)
{
int level = 1;
IntPtr rawInfo;
int count;
if (!WTSEnumerateSessionsExW(serverHandle, ref level, 0, out rawInfo, out count))
throw new System.ComponentModel.Win32Exception();
if (level != 1)
throw new InvalidOperationException(string.Format("Expected level 1 but got level {0}", level));
if (rawInfo == IntPtr.Zero)
return new SessionInfoExtra[0];
try
{
var infoCollection = new List<SessionInfoExtra>();
int structSize = Marshal.SizeOf<WTS_SESSION_INFO_1W>();
IntPtr currentOffset = rawInfo;
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO_1W info = Marshal.PtrToStructure<WTS_SESSION_INFO_1W>(currentOffset);
if (info.pUserName == null)
{
// Skip the session if the username is null.
currentOffset = IntPtr.Add(currentOffset, structSize);
continue;
}
string clientName = null;
TimeSpan? idleTime = null;
DateTime? logonTime = null;
// Query WTSINFO once to get idle time, logon time, and other info
try
{
WTSInfo wtsInfo = WTSQueryWTSINFO(serverHandle, info.SessionId);
if (wtsInfo != null)
{
logonTime = wtsInfo.LogonTime;
// Calculate idle time from LastInputTime
if (wtsInfo.LastInputTime.HasValue && wtsInfo.CurrentTime.HasValue)
{
idleTime = wtsInfo.CurrentTime.Value - wtsInfo.LastInputTime.Value;
}
}
}
catch
{
// If WTSQueryWTSINFO fails, ignore and continue
}
// Only query client name separately if needed (active non-console sessions)
if (info.State != WtsConnectState.Disconnected && info.pSessionName != "console")
{
clientName = QuerySessionInfoString(serverHandle, info.SessionId, WTS_INFO_CLASS.WTSClientName);
}
infoCollection.Add(new SessionInfoExtra() {
SessionId = info.SessionId,
State = info.State,
SessionName = info.pSessionName,
// HostName = info.pHostName,
UserName = info.pUserName,
DomainName = info.pDomainName,
// FarmName = info.pFarmName,
ComputerName = serverName,
ClientName = clientName,
IdleTime = idleTime,
LogonTime = logonTime,
});
currentOffset = IntPtr.Add(currentOffset, structSize);
}
return infoCollection.ToArray();
}
finally
{
if (rawInfo != IntPtr.Zero)
WTSFreeMemoryExW((int)WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, rawInfo, count);
}
}
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool WTSFreeMemoryExW(
int WTSTypeClass,
IntPtr pMemory,
int NumberOfEntries);
[DllImport("Wtsapi32.dll")]
private static extern void WTSFreeMemory(IntPtr memory);
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr WTSOpenServerExW(string pServerName);
public static IntPtr WTSOpenServerEx(string serverName)
{
IntPtr handle = WTSOpenServerExW(serverName);
if (handle == IntPtr.Zero)
throw new System.ComponentModel.Win32Exception();
return handle;
}
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool WTSLogoffSession(IntPtr hServer, uint SessionId, [MarshalAs(UnmanagedType.Bool)] bool bWait);
public static object WTSQuerySessionInfo(IntPtr hServer, uint sessionId, WTS_INFO_CLASS wtsInfoClass)
{
return QuerySessionInfoString(hServer, sessionId, wtsInfoClass);
}
public static ClientInfo WTSQueryClientInfo(IntPtr hServer, uint sessionId)
{
IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
ClientInfo client = null;
try
{
if (!WTSQuerySessionInformation(hServer, sessionId, WTS_INFO_CLASS.WTSClientInfo, out buffer, out bytesReturned))
{
throw new System.ComponentModel.Win32Exception();
}
WTSCLIENT rawclient = Marshal.PtrToStructure<WTSCLIENT>(buffer);
client = new ClientInfo() {
ClientName = rawclient.ClientName,
Domain = rawclient.Domain,
UserName = rawclient.UserName,
WorkDirectory = rawclient.WorkDirectory,
InitialProgram = rawclient.InitialProgram,
EncryptionLevel = rawclient.EncryptionLevel,
ClientAddressFamily = (AddressFamily)rawclient.ClientAddressFamily,
ClientAddress = ConvertToIPAddress(rawclient.ClientAddress, (AddressFamily)rawclient.ClientAddressFamily),
HRes = rawclient.HRes,
VRes = rawclient.VRes,
ColorDepth = rawclient.ColorDepth,
ClientDirectory = rawclient.ClientDirectory,
ClientBuildNumber = rawclient.ClientBuildNumber,
ClientHardwareId = rawclient.ClientHardwareId,
ClientProductId = rawclient.ClientProductId,
OutBufCountHost = rawclient.OutBufCountHost,
OutBufCountClient = rawclient.OutBufCountClient,
OutBufLength = rawclient.OutBufLength,
DeviceId = rawclient.DeviceId,
};
}
finally
{
if (buffer != IntPtr.Zero)
{
WTSFreeMemory(buffer);
}
}
return client;
}
private static IPAddress ConvertToIPAddress(ushort[] clientAddress, AddressFamily addressFamily)
{
if (clientAddress == null || clientAddress.Length == 0)
return null;
try
{
if (addressFamily == AddressFamily.InterNetwork)
{
// IPv4: The first 4 ushorts contain the 4 IP octets
// Each ushort's low byte contains one octet of the IP address
if (clientAddress.Length < 4)
return null;
byte[] ipBytes = new byte[4];
ipBytes[0] = (byte)(clientAddress[0] & 0xFF);
ipBytes[1] = (byte)(clientAddress[1] & 0xFF);
ipBytes[2] = (byte)(clientAddress[2] & 0xFF);
ipBytes[3] = (byte)(clientAddress[3] & 0xFF);
return new IPAddress(ipBytes);
}
else if (addressFamily == AddressFamily.InterNetworkV6)
{
// IPv6: Similar to IPv4, each ushort contains one byte in the low byte
// IPv6 address is 16 bytes starting from the beginning
if (clientAddress.Length < 16)
return null;
byte[] ipBytes = new byte[16];
for (int i = 0; i < 16; i++)
{
ipBytes[i] = (byte)(clientAddress[i] & 0xFF);
}
return new IPAddress(ipBytes);
}
else
{
return null;
}
}
catch
{
return null;
}
}
private static DateTime? ConvertFileTimeToLocal(long time)
{
if (time == 0)
return null;
try
{
// The WTSINFOW struct times are in UTC
DateTime utcTime = DateTime.FromFileTimeUtc(time);
// Convert to local time using the base UTC offset (not DST-adjusted)
// Windows Terminal Services (quser) displays times with standard offset only, ignoring DST
// This matches the behavior of the quser command
TimeSpan baseOffset = TimeZoneInfo.Local.BaseUtcOffset;
DateTime localTime = utcTime.Add(baseOffset);
return localTime;
}
catch
{
return null;
}
}
public static WTSInfo WTSQueryWTSINFO(IntPtr hServer, uint sessionId)
{
IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
WTSInfo client = null;
try
{
if (!WTSQuerySessionInformation(hServer, sessionId, WTS_INFO_CLASS.WTSSessionInfo, out buffer, out bytesReturned))
{
throw new System.ComponentModel.Win32Exception();
}
WTSINFOW rawclient = Marshal.PtrToStructure<WTSINFOW>(buffer);
client = new WTSInfo() {
State = rawclient.State,
SessionId = rawclient.SessionId,
IncomingBytes = rawclient.IncomingBytes,
OutgoingBytes = rawclient.OutgoingBytes,
IncomingFrames = rawclient.IncomingFrames,
OutgoingFrames = rawclient.OutgoingFrames,
IncomingCompressedBytes = rawclient.IncomingCompressedBytes,
OutgoingCompressedBy = rawclient.OutgoingCompressedBytes,
WinStationName = rawclient.WinStationName,
Domain = rawclient.Domain,
UserName = rawclient.UserName,
ConnectTime = ConvertFileTimeToLocal(rawclient.ConnectTimeUTC),
DisconnectTime = ConvertFileTimeToLocal(rawclient.DisconnectTimeUTC),
LastInputTime = ConvertFileTimeToLocal(rawclient.LastInputTimeUTC),
LogonTime = ConvertFileTimeToLocal(rawclient.LogonTimeUTC),
CurrentTime = ConvertFileTimeToLocal(rawclient.CurrentTimeUTC),
};
// if (client.State != WtsConnectState.Disconnected && client.DisconnectTime != null)
// {
// client.DisconnectTime = null;
// }
}
finally
{
if (buffer != IntPtr.Zero)
{
WTSFreeMemory(buffer);
}
}
return client;
}
[DllImport("Wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool WTSQuerySessionInformation(
IntPtr hServer,
uint sessionId,
WTS_INFO_CLASS wtsInfoClass,
out IntPtr ppBuffer,
out uint pBytesReturned
);
// Helper method for querying string session information
public static string QuerySessionInfoString(IntPtr hServer, uint sessionId, WTS_INFO_CLASS wtsInfoClass)
{
IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
try
{
if (!WTSQuerySessionInformation(hServer, sessionId, wtsInfoClass, out buffer, out bytesReturned))
return null;
return Marshal.PtrToStringUni(buffer);
}
finally
{
if (buffer != IntPtr.Zero)
WTSFreeMemory(buffer);
}
}
public static long QuerySessionInfoLong(IntPtr hServer, uint sessionId, WTS_INFO_CLASS wtsInfoClass)
{
IntPtr buffer = IntPtr.Zero;
uint bytesReturned;
try
{
if (!WTSQuerySessionInformation(hServer, sessionId, wtsInfoClass, out buffer, out bytesReturned))
return -1;
if (bytesReturned == 8)
{
return Marshal.ReadInt64(buffer);
}
else if (bytesReturned == 4)
{
return Marshal.ReadInt32(buffer);
}
return -1;
}
finally
{
if (buffer != IntPtr.Zero)
WTSFreeMemory(buffer);
}
}
}
}
'@
# this just fixes the order on the object because we inherit from SessionInfo (to be able to pipe to the other cmdlets.)
Update-TypeData -TypeName 'PSWtsapi32.SessionInfoExtra' -DefaultDisplayPropertySet @(
'SessionId',
'State',
'SessionName',
'UserName',
'DomainName',
'ComputerName',
'ClientName',
'IdleTime',
'LogonTime'
) -Force
function Get-WTSSessionInfo {
<#
.SYNOPSIS
Enumerates sessions on a Windows host.
.DESCRIPTION
Enumerates all the sessions available on a Windows host through the WTSEnumerateSessionsExW API.
.PARAMETER ComputerName
A list of hosts to query the sessions for. Omit or set to an empty array to check the local host.
.PARAMETER Detailed
If set, retrieves additional details about each session such as idle time and logon time.
.EXAMPLE
Get-WTSSessionInfo -ComputerName Server01, Server02
.EXAMPLE
Get-WTSSessionInfo -ComputerName Server01 -Detailed
.NOTES
The output object is modeled after https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w.
#>
[OutputType([PSWtsapi32.SessionInfo], [PSWtsapi32.SessionInfoExtra])]
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
[string[]] $ComputerName = $env:COMPUTERNAME,
[Switch] $Detailed
)
process {
foreach ($name in $ComputerName) {
$serverInfo = [PSWtsapi32.Native]::WTSOpenServerEx($name)
try {
if ($Detailed.IsPresent) {
[PSWtsapi32.Native]::WTSEnumerateSessionsWithDetails($serverInfo, $Name)
}
else {
[PSWtsapi32.Native]::WTSEnumerateSessionsExtra($serverInfo, $Name)
}
}
finally {
[PSWtsapi32.Native]::WTSCloseServer($serverInfo)
}
}
}
}
function Get-WTSClientInfo {
<#
.SYNOPSIS
Retrieves client information for a session on a Windows host.
.DESCRIPTION
Retrieves client information for a session on a Windows host through the WTSQueryClientInfo API.
.PARAMETER SessionInfo
The session info object returned from Get-WTSSessionInfo.
.EXAMPLE
Get-WTSSessionInfo | Get-WTSClientInfo
.EXAMPLE
Get-WTSSessionInfo | Where-Object { $_.State -eq 'Active' } | Get-WTSClientInfo
#>
[OutputType([PSWtsapi32.ClientInfo])]
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSWtsapi32.SessionInfo] $SessionInfo
)
process {
$serverInfo = [PSWtsapi32.Native]::WTSOpenServerEx($SessionInfo.ComputerName)
try {
[PSWtsapi32.Native]::WTSQueryClientInfo($serverInfo, $SessionInfo.SessionId)
}
finally {
[PSWtsapi32.Native]::WTSCloseServer($serverInfo)
}
}
}
function Get-WTSInfo {
<#
.SYNOPSIS
Retrieves WTS information for a session on a Windows host.
.DESCRIPTION
Retrieves WTS information for a session on a Windows host through the WTSQueryWTSINFO API.
.PARAMETER SessionInfo
The session info object returned from Get-WTSSessionInfo.
.EXAMPLE
Get-WTSSessionInfo | Get-WTSWtsInfo
.EXAMPLE
Get-WTSSessionInfo | Where-Object { $_.State -eq 'Active' } | Get-WTSWtsInfo
#>
[OutputType([PSWtsapi32.WTSInfo])]
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSWtsapi32.SessionInfo] $SessionInfo
)
process {
$serverInfo = [PSWtsapi32.Native]::WTSOpenServerEx($SessionInfo.ComputerName)
try {
[PSWtsapi32.Native]::WTSQueryWTSINFO($serverInfo, $SessionInfo.SessionId)
}
finally {
[PSWtsapi32.Native]::WTSCloseServer($serverInfo)
}
}
}
function Remove-WTSSession {
<#
.SYNOPSIS
Removes a session from a Windows host.
.DESCRIPTION
Removes a session from a Windows host through the WTSLogoffSession API.
.PARAMETER SessionInfo
The session info object returned from Get-WTSSessionInfo.
,EXAMPLE
Get-WTSSessionInfo | Where-Object { $_.State -eq 'Disconnected' } | Remove-WTSSession
.EXAMPLE
Get-WTSSessionInfo | Remove-WTSSession
#>
[CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'High')]
param(
[Parameter(ValueFromPipeline)]
[PSWtsapi32.SessionInfo] $SessionInfo,
[Switch] $WaitForLogoff
)
process {
if ($PSCmdlet.ShouldProcess('Logoff Session {0} for {1}@{2} State: {3}' -f (
$SessionInfo.SessionId,
$SessionInfo.UserName,
$SessionInfo.ComputerName,
$SessionInfo.State))) {
try {
$serverInfo = [PSWtsapi32.Native]::WTSOpenServerEx($SessionInfo.ComputerName)
$logoff = [PSWtsapi32.Native]::WTSLogoffSession($serverInfo, $SessionInfo.SessionId, $WaitForLogoff.IsPresent)
[PSCustomObject]@{
SessionId = $SessionInfo.SessionId
UserName = $SessionInfo.UserName
ComputerName = $SessionInfo.ComputerName
State = $SessionInfo.State
Result = $logoff
}
}
finally {
[PSWtsapi32.Native]::WTSCloseServer($serverInfo)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment