Getting RDP Sessions with Client Computer Name

I came across this question on ServerFault today, which I thought was quite interesting.  And so I spent all morning formulating a solution to it.  How do you go about getting the list of currently connected users of a Windows machine including the remote client's machine name, on the command line? You can type query session, but that doesn't tell you the client's computer name or IP address.  The various Terminal Services and Remote Desktop-related Windows event logs are of very limited help. The Users tab in Task Manager tells you the usernames and their session IDs, but not their computer name.  You've got a bunch of WMI classes like Win32_LoggedOnUser and Win32_LogonSession, but none of them seem to contain any data about the connected client's machine name or IP address.

I wanted to come up with a Powershell solution.  Something that could also be run over the network and not just on the local machine.  So after a couple hours of research, finding the WTSQuerySessionInformation documentation, and spending some quality time on, here is the "Powershell" solution that I came up with:

(I put "Powershell" in quotes because there's one single actual Powershell command in the whole thing.  Add-Type.  It's really all .NET code, which P/Invokes an unmanaged DLL.  Nevertheless, it's pretty sweet that you can do all that from within Powershell.)


# QuerySessionInformation.ps1
# Written by Ryan Ries, Jan. 2013, with help from MSDN and Stackoverflow.

$Code = @'
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class RDPInfo
    static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

    static extern void WTSCloseServer(IntPtr hServer);

    static extern Int32 WTSEnumerateSessions(
        IntPtr hServer,
        [MarshalAs(UnmanagedType.U4)] Int32 Reserved,
        [MarshalAs(UnmanagedType.U4)] Int32 Version,
        ref IntPtr ppSessionInfo,
        [MarshalAs(UnmanagedType.U4)] ref Int32 pCount);

    static extern void WTSFreeMemory(IntPtr pMemory);

    static extern bool WTSQuerySessionInformation(System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);

    private struct WTS_SESSION_INFO
        public Int32 SessionID;
        public String pWinStationName;
        public WTS_CONNECTSTATE_CLASS State;

    public enum WTS_INFO_CLASS


    public static IntPtr OpenServer(String Name)
        IntPtr server = WTSOpenServer(Name);
        return server;

    public static void CloseServer(IntPtr ServerHandle)

    public static List<string> ListUsers(String ServerName)
        IntPtr serverHandle = IntPtr.Zero;
        List<String> resultList = new List<string>();
        serverHandle = OpenServer(ServerName);

            IntPtr SessionInfoPtr = IntPtr.Zero;
            IntPtr userPtr = IntPtr.Zero;
            IntPtr domainPtr = IntPtr.Zero;
            IntPtr clientNamePtr = IntPtr.Zero;
            Int32 sessionCount = 0;
            Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
            Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
            Int32 currentSession = (int)SessionInfoPtr;
            uint bytes = 0;
            if (retVal != 0)
                for (int i = 0; i < sessionCount; i++)
                    WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)currentSession, typeof(WTS_SESSION_INFO));
                    currentSession += dataSize;

                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSUserName, out userPtr, out bytes);
                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSDomainName, out domainPtr, out bytes);
                    WTSQuerySessionInformation(serverHandle, si.SessionID, WTS_INFO_CLASS.WTSClientName, out clientNamePtr, out bytes);

                    if(Marshal.PtrToStringAnsi(domainPtr).Length > 0 && Marshal.PtrToStringAnsi(userPtr).Length > 0)
                        if(Marshal.PtrToStringAnsi(clientNamePtr).Length < 1)                       
                            resultList.Add(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: n/a");
                            resultList.Add(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: " + Marshal.PtrToStringAnsi(clientNamePtr));
        catch(Exception ex)
            Console.WriteLine("Exception: " + ex.Message);
        return resultList;

Add-Type $Code

Copy all of that into a file named QuerySessionInformation.ps1. Now launch the 32 bit version of Powershell in C:\Windows\SysWOW64\WindowsPowershell\v1.0. The code above uses pointers that will not work in a native 64 bit environment.

Now run the script. If you've never run the 32 bit version of Powershell on that server before, you will need to modify the script execution policy with Set-ExecutionPolicy, as 32 bit and 64 bit Powershell have separate execution policies. Note that there should be no output from the script itself, as all it is doing is compiling the .NET code and adding it to the current environment. Also note that once a type is added with Add-Type, you can not unload it without exiting that Powershell session... AFAIK. It makes debugging this sort of stuff really annoying as you have to restart Powershell every time you modify the code.

Now that the code is loaded, type this:

PS C:\> [RDPInfo]::ListUsers("REMOTESERVER") 

If there are any active user sessions on REMOTESERVER, the output will look like this:

DOMAIN\UserName SessionID: 2 ClientName: RYAN-PC 

This will work on remote computers as well as the local computer, but beware that if the user running this does not have sufficient permissions to the remote computer, it will fail silently (no output.)

PS: There are other bits of info in WTS_INFO_CLASS that may be of interest to you, such as WTSConnectState and WTSClientAddress. All you have to do is query for them with WTSQuerySessionInformation().

Comments (9) -

Hi Ryan

Tried your code, and ran in to some problmes.

Just error 41 Smile

I copy the QuerySessionInformation.ps1 to the location 'C:\Windows\SysWOW64\WindowsPowershell\v1.0'
And run it on a server i my environment. (no error's or pop-up) OK.

Then i what to get the info from my own computer.

This code does not work for me:

PS C:\> [RDPInfo]::ListUsers("REMOTESERVER")

Unable to find type [RDPInfo]: make sure that the assembly containing this type is loaded

Hello.  Thanks for stopping by.

That error means that the type wasn't loaded successfully.  You have to load the type in the same Powershell session you plan to use it in.

This is the perfect blog for anyone who wants to know about pc security. The article is nice and it’s pleasant to read.

Thanks for taking the time to figure it all out and blog about it.

This was a lifesaver.  Amazing when your third Google search gives you EXACTLY the answer you're looking for.  Thanks Ryan.  Super-easy to set up.

How to set the permissions for the remote computer?

Thanks for the script - works great ... when run interactively.
I cannot get it to run when I attach it to a logon task within Windows Task Scheduler.
Any pointers? I get the same with 'query session'.

Sorry I am a novice with Power Shell.  Do you happen to have the info for retrieving the IP address?   Every time I add it to the query it has little smiley faces in power shell for the output of WTSClientAddress.

This worked fine for me on Server 2008 R2 both in 64bit and 32bit PS sessions.
On Server 2012 and Server 2012 R2, however, it only worked in 32bit PS sessions; in 64bit sessions, Line 100 ('Int32 currentSession = (int)SessionInfoPtr;') threw an ' Arithmetic operation resulted in an overflow.'
Got it working by replacing the line with 'Int64 currentSession = (Int64)SessionInfoPtr;'

Pingbacks and trackbacks (1)+

Comments are closed