How do you retrieve a list of logged-in/connected users in .NET?

Here's the scenario:

You have a Windows server that users remotely connect to via RDP. You want your program (which runs as a service) to know who is currently connected. This may or may not include an interactive console session.

Please note that this is the not the same as just retrieving the current interactive user.

I'm guessing that there is some sort of API access to Terminal Services to get this info?

Answers


Here's my take on the issue:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace EnumerateRDUsers
{
  class Program
  {
    [DllImport("wtsapi32.dll")]
    static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

    [DllImport("wtsapi32.dll")]
    static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("wtsapi32.dll")]
    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);

    [DllImport("wtsapi32.dll")]
    static extern void WTSFreeMemory(IntPtr pMemory);

    [DllImport("Wtsapi32.dll")]
    static extern bool WTSQuerySessionInformation(
        System.IntPtr hServer, int sessionId, WTS_INFO_CLASS wtsInfoClass, out System.IntPtr ppBuffer, out uint pBytesReturned);

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
      public Int32 SessionID;

      [MarshalAs(UnmanagedType.LPStr)]
      public String pWinStationName;

      public WTS_CONNECTSTATE_CLASS State;
    }

    public enum WTS_INFO_CLASS
    {
      WTSInitialProgram,
      WTSApplicationName,
      WTSWorkingDirectory,
      WTSOEMId,
      WTSSessionId,
      WTSUserName,
      WTSWinStationName,
      WTSDomainName,
      WTSConnectState,
      WTSClientBuildNumber,
      WTSClientName,
      WTSClientDirectory,
      WTSClientProductId,
      WTSClientHardwareId,
      WTSClientAddress,
      WTSClientDisplay,
      WTSClientProtocolType
    }
    public enum WTS_CONNECTSTATE_CLASS
    {
      WTSActive,
      WTSConnected,
      WTSConnectQuery,
      WTSShadow,
      WTSDisconnected,
      WTSIdle,
      WTSListen,
      WTSReset,
      WTSDown,
      WTSInit
    }

    static void Main(string[] args)
    {
      ListUsers(Environment.MachineName);
    }

    public static IntPtr OpenServer(String Name)
    {
      IntPtr server = WTSOpenServer(Name);
      return server;
    }
    public static void CloseServer(IntPtr ServerHandle)
    {
      WTSCloseServer(ServerHandle);
    }
    public static void ListUsers(String ServerName)
    {
      IntPtr serverHandle = IntPtr.Zero;
      List<String> resultList = new List<string>();
      serverHandle = OpenServer(ServerName);

      try
      {
        IntPtr SessionInfoPtr = IntPtr.Zero;
        IntPtr userPtr = IntPtr.Zero;
        IntPtr domainPtr = IntPtr.Zero;
        Int32 sessionCount = 0;
        Int32 retVal = WTSEnumerateSessions(serverHandle, 0, 1, ref SessionInfoPtr, ref sessionCount);
        Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
        IntPtr currentSession = 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);

            Console.WriteLine("Domain and User: " + Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr));

            WTSFreeMemory(userPtr); 
            WTSFreeMemory(domainPtr);
          }

          WTSFreeMemory(SessionInfoPtr);
        }
      }
      finally
      {
        CloseServer(serverHandle);
      }

    }

  }
}

Another option, if you don't want to deal with the P/Invokes yourself, would be to use the Cassia library:

using System;
using System.Security.Principal;
using Cassia;

namespace CassiaSample
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            ITerminalServicesManager manager = new TerminalServicesManager();
            using (ITerminalServer server = manager.GetRemoteServer("your-server-name"))
            {
                server.Open();
                foreach (ITerminalServicesSession session in server.GetSessions())
                {
                    NTAccount account = session.UserAccount;
                    if (account != null)
                    {
                        Console.WriteLine(account);
                    }
                }
            }
        }
    }
}

Ok, one solution to my own question.

You can use WMI to retreive a list of running processes. You can also look at the owners of these processes. If you look at the owners of "explorer.exe" (and remove the duplicates) you should end up with a list of logged in users.


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace TerminalServices
{
    class TSManager
    {
    [DllImport("wtsapi32.dll")]
    static extern IntPtr WTSOpenServer([MarshalAs(UnmanagedType.LPStr)] String pServerName);

    [DllImport("wtsapi32.dll")]
    static extern void WTSCloseServer(IntPtr hServer);

    [DllImport("wtsapi32.dll")]
    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);

    [DllImport("wtsapi32.dll")]
    static extern void WTSFreeMemory(IntPtr pMemory);

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public Int32 SessionID;

        [MarshalAs(UnmanagedType.LPStr)]
        public String pWinStationName;

        public WTS_CONNECTSTATE_CLASS State;
    }

    public enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    } 

    public static IntPtr OpenServer(String Name)
    {
        IntPtr server = WTSOpenServer(Name);
        return server;
    }
    public static void CloseServer(IntPtr ServerHandle)
    {
        WTSCloseServer(ServerHandle);
    }
    public static List<String> ListSessions(String ServerName)
    {
        IntPtr server = IntPtr.Zero;
        List<String> ret = new List<string>();
        server = OpenServer(ServerName);

        try
        {
        IntPtr ppSessionInfo = IntPtr.Zero;

        Int32 count = 0;
        Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
        Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));

        Int32 current = (int)ppSessionInfo;

        if (retval != 0)
        {
            for (int i = 0; i < count; i++)
            {
            WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
            current += dataSize;

            ret.Add(si.SessionID + " " + si.State + " " + si.pWinStationName);
            }

            WTSFreeMemory(ppSessionInfo);
        }
        }
        finally
        {
        CloseServer(server);
        }

        return ret;
    }
    }
}

Need Your Help

Checking if all the array items are empty PHP

php arrays validation

I'm adding an array of items from a form and if all of them are empty, I want to perform some validation and add to an error string. So I have:

What is the meaning and difference between subject, user and principal?

java spring-security terminology security

In the context of security frameworks, a few terms commonly occur subject, user and principal, of which I have not been able to find a clear definition and the difference between them.