Home > Software engineering >  How to query all Windows Terminal Services (WTS) / RDP sessions?
How to query all Windows Terminal Services (WTS) / RDP sessions?

Time:06-05

How to query all Windows Terminal Services (WTS) / RDP sessions?

I want to know who has a session on a Windows Machine, but don't want to use and parse the output of quser, qwinsta, query session or query user.

Info:
This was just a self-answered question.

CodePudding user response:

You can use the following function.
The base was from the P/Invoke folk, but has been adapted and enhanced by me.
P/Invoke base: https://pinvoke.net/default.aspx/wtsapi32.WTSEnumerateSessions

Function

This code is a simple and high-level function.
Meanwhile I also created an enhanced version, published here: https://gist.github.com/swbbl/205694b7e1bdf09e74f25800194d5bcd

function Get-ComputerSession {
    [CmdletBinding()]
    param(
        # The computer name to get the current sessions from.
        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [AllowEmptyString()]
        [String]
        $ComputerName
    )

    begin {
        # this script is based on the PS code example for WTSEnumerateSessions from:
        # https://pinvoke.net/default.aspx/wtsapi32.WTSEnumerateSessions
        # but has many adaptions and enhancements.
        # thanks for the input!

        Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace WTS
{
    public class API {
        // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsopenserverexw
        [DllImport("wtsapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
        public static extern IntPtr WTSOpenServerEx(string pServerName);

        // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtscloseserver
        [DllImport("wtsapi32.dll", SetLastError=true, CharSet = CharSet.Unicode)]
        public static extern void WTSCloseServer(System.IntPtr hServer);

        // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsenumeratesessionsexw
        [DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern int WTSEnumerateSessionsEx(
                System.IntPtr hServer,
                ref int pLevel,
                int Filter,
                ref System.IntPtr ppSessionInfo,
                ref int pCount);

        [DllImport("wtsapi32.dll", SetLastError=true)]
        public static extern int WTSQuerySessionInformationW(
            System.IntPtr hServer,
            int SessionId,
            int WTSInfoClass ,
            ref System.IntPtr ppSessionInfo,
            ref int pBytesReturned );

        // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsfreememoryexw
        [DllImport("wtsapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool WTSFreeMemoryEx(
            WTS_TYPE_CLASS WTSTypeClass,
            IntPtr pMemory,
            UInt32 NumberOfEntries);
    }

    // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct WTS_SESSION_INFO_1
    {
        public int ExecEnvId;
        public WTS_CONNECTSTATE_CLASS State;
        public UInt32 SessionId;
        public String SessionName;
        public String HostName;
        public String UserName;
        public String DomainName;
        public String FarmName;
    }

    public enum WTS_TYPE_CLASS
    {
        TypeProcessInfoLevel0,
        TypeProcessInfoLevel1,
        TypeSessionInfoLevel1
    }

    // https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ne-wtsapi32-wts_connectstate_class
    public enum WTS_CONNECTSTATE_CLASS
    {
        [Description("A user is logged on to the WinStation. This state occurs when a user is signed in and actively connected to the device.")]
        Active,
        [Description("The WinStation is connected to the client.")]
        Connected,
        [Description("The WinStation is in the process of connecting to the client.")]
        ConnectQuery,
        [Description("The WinStation is shadowing another WinStation.")]
        Shadow,
        [Description("The WinStation is active but the client is disconnected. This state occurs when a user is signed in but not actively connected to the device, such as when the user has chosen to exit to the lock screen.")]
        Disconnected,
        [Description("The WinStation is waiting for a client to connect.")]
        Idle,
        [Description("The WinStation is listening for a connection. A listener session waits for requests for new client connections. No user is logged on a listener session. A listener session cannot be reset, shadowed, or changed to a regular client session.")]
        Listen,
        [Description("The WinStation is being reset.")]
        Reset,
        [Description("The WinStation is down due to an error.")]
        Down,
        [Description("The WinStation is initializing.")]
        Init
    }
}
'@

        $wtsSessionDataSize = [System.Runtime.InteropServices.Marshal]::SizeOf([System.Type][WTS.WTS_SESSION_INFO_1])
    }

    process {
        [UInt32] $pLevel        = 1 # for a reserved parameter. must be always 1
        [UInt32] $pCount        = 0
        [IntPtr] $ppSessionInfo = 0

        try {
            [IntPtr] $wtsServerHandle = [WTS.API]::WTSOpenServerEx($ComputerName)
            if (-not $wtsServerHandle) {
                throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
            }

            [bool] $wtsSessionsCheck = [WTS.API]::WTSEnumerateSessionsEx($wtsServerHandle, [ref] $pLevel, [UInt32] 0, [ref] $ppSessionInfo, [ref] $pCount)
            if (-not $wtsSessionsCheck) {
                throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
            }

            for ($i = 0; $i -lt $pCount; $i  ) {
                $wtsSessionInfoOffset = $wtsSessionDataSize * $i
                [System.Runtime.InteropServices.Marshal]::PtrToStructure([IntPtr]::Add($ppSessionInfo, $wtsSessionInfoOffset), [type][WTS.WTS_SESSION_INFO_1])
            }

        } catch {
            Write-Error -ErrorRecord $_

        } finally {
            try {
                $wtsSessionInfoFreeMemCheck = [WTS.API]::WTSFreeMemoryEx([WTS.WTS_TYPE_CLASS]::TypeSessionInfoLevel1, $ppSessionInfo, $pCount)

                if (-not $wtsSessionInfoFreeMemCheck) {
                    throw [System.ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error())
                }
            } finally {
                $ppSessionInfo = [IntPtr]::Zero
                [WTS.API]::WTSCloseServer($wtsServerHandle)
            }
        }
    }
}

How to use

Using the function without the parameter ComputerName will ask your for a name. Just hit [ENTER] if you want to get the sessions of the local computer/server or provide an empty string for the parameter ComputerName

Example

For a local query.

PS> Get-ComputerSession -ComputerName ''

ExecEnvId   : 0
State       : Disconnected
SessionId   : 0
SessionName : Services
HostName    :
UserName    :
DomainName  :
FarmName    :

ExecEnvId   : 1
State       : Active
SessionId   : 1
SessionName : Console
HostName    :
UserName    : svenw
DomainName  : SVEN-PC
FarmName    :

ExecEnvId   : 2
State       : Disconnected
SessionId   : 2
SessionName :
HostName    :
UserName    : Test
DomainName  : Sven-PC
FarmName    :

Each property is (more or less) explained here: https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/ns-wtsapi32-wts_session_info_1w

Enhanced version

I've also created and uploaded a more enhanced version on gist: https://gist.github.com/swbbl/205694b7e1bdf09e74f25800194d5bcd

  • Related