Não há interface WMI para isso que eu saiba.
Am I missing an API call that will get me all this information in one place?
Sim. Você pode obter os dados da API do Win32. De wtsapi32.dll, para ser específico. Você pode escrever um programa em C, ou você pode P / invocá-lo a partir de C # ou mesmo Powershell.
Como você provavelmente quer o Powershell, escrevi isso para você esta manhã:
# 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
{
[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
}
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;
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)
Console.WriteLine(Marshal.PtrToStringAnsi(domainPtr) + "\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: n/a");
else
Console.WriteLine(Marshal.PtrToStringAnsi(domainPtr) + "\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: " + Marshal.PtrToStringAnsi(clientNamePtr));
}
WTSFreeMemory(clientNamePtr);
WTSFreeMemory(userPtr);
WTSFreeMemory(domainPtr);
}
WTSFreeMemory(SessionInfoPtr);
}
}
catch(Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
finally
{
CloseServer(serverHandle);
}
}
}
'@
Add-Type $Code
Copie tudo isso em um arquivo chamado QuerySessionInformation.ps1. Agora, abra a versão de 32 bits do Powershell em C: \ Windows \ SysWOW64 \ WindowsPowershell \ v1.0. O código acima usa ponteiros que não funcionam em um ambiente nativo de 64 bits.
Agora, execute o script. Se você nunca executou a versão de 32 bits do Powershell nesse servidor antes, será necessário modificar a política de execução de scripts com Set-ExecutionPolicy, já que o Powershell de 32 bits e 64 bits tem políticas de execução separadas. Observe que não deve haver saída do próprio script, pois tudo o que ele está fazendo é compilar o código .NET e adicioná-lo ao ambiente atual. Observe também que, depois que um tipo é adicionado ao Add-Type, você não pode descarregá-lo sem sair da sessão do Powershell ... AFAIK. Isso torna a depuração desse tipo de coisa realmente irritante, já que você precisa reiniciar o Powershell toda vez que modificar o código.
Agora que o código está carregado, digite:
PS C:\> [RDPInfo]::ListUsers("REMOTESERVER")
Se houver alguma sessão de usuário ativa no REMOTESERVER, a saída ficará assim:
DOMAIN\UserName SessionID: 2 ClientName: RYAN-PC
Isso funcionará em computadores remotos, bem como no computador local, mas lembre-se de que, se o usuário que estiver executando não tiver permissões suficientes para o computador remoto, ele falhará silenciosamente (sem saída).
Edit: Existem outros bits de informação em WTS_INFO_CLASS que podem ser do seu interesse, como WTSConnectState e WTSClientAddress. Tudo o que você precisa fazer é consultá-los.
Edit: Eu também converti esta solução para o código nativo (C) para uso na linha de comando: