我希望能够创建一个 powershell 脚本,该脚本将告诉我,对于计算机上当前活动的所有 RDP 会话,用户是谁,以及他们的客户端名称(机器名称)是什么。
我可以使用 win32_loggedonnuser 和 win32_logonsession 的组合来获取用户名信息,但我无法在这些对象(枚举?)中找到客户端名称。
PS C:\> $logons = gwmi win32_loggedonuser; $lstring = ""; foreach($l in $logons) { $lstring +=$l;} $lstring -match "cephalopod";
False
PS C:\> $sessions = gwmi win32_logonsession; $sstring = ""; foreach($s in $sessions) { $sstring +=$s;} $sstring -match "cephalopod";
False
(头足类是我的计算机名,即登录到服务器框的机器)
。
我可以看到确实HKCU:\Volatile Environment
有客户端名称,并且temp
密钥中包含用户名,但我无法仅根据密钥确定会话当前是否处于活动状态。
我是否缺少一个可以将所有这些信息集中在一个地方的 API 调用?
基本要求:在任务管理器 > 用户列表中查找用户和客户端名称,其中状态为活动。
答案1
据我所知,没有用于此的 WMI 接口。
我是否缺少一个可以将所有这些信息集中在一个地方的 API 调用?
是的。您可以从 Win32 API 获取数据。具体来说,是从 wtsapi32.dll 获取。您可以编写 C 程序,也可以从 C# 甚至 Powershell 对其进行 P/Invoke。
因为您可能需要 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
{
[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
将所有内容复制到名为 QuerySessionInformation.ps1 的文件中。现在启动32 位版本的 Powershell在 C:\Windows\SysWOW64\WindowsPowershell\v1.0 中。上述代码使用的指针在本机 64 位环境中不起作用。
现在运行脚本。如果您以前从未在该服务器上运行过 32 位版本的 Powershell,则需要使用 Set-ExecutionPolicy 修改脚本执行策略,因为 32 位和 64 位 Powershell 具有单独的执行策略。请注意,脚本本身不应有任何输出,因为它所做的只是编译 .NET 代码并将其添加到当前环境。另请注意,一旦使用 Add-Type 添加类型,您就无法在不退出该 Powershell 会话的情况下卸载它……据我所知。这使得调试这类东西非常烦人,因为每次修改代码时都必须重新启动 Powershell。
现在代码已加载,请输入以下内容:
PS C:\> [RDPInfo]::ListUsers("REMOTESERVER")
如果 REMOTESERVER 上有任何活动的用户会话,则输出将如下所示:
DOMAIN\UserName SessionID: 2 ClientName: RYAN-PC
这将在远程计算机和本地计算机上运行,但请注意,如果运行此程序的用户对远程计算机没有足够的权限,它将静默失败(无输出)。
编辑:WTS_INFO_CLASS 中还有其他一些信息可能让您感兴趣,例如 WTSConnectState 和 WTSClientAddress。您所要做的就是查询它们。
编辑:我还将此解决方案转换为本机代码(C)以便在命令行上使用:
http://www.myotherpcisacloud.com/post/2013/01/16/Usersexe-v1003.aspx
答案2
会PS终端服务(用于 Powershell)有用吗?我用的是这个每时每刻在我们的10个终端服务器上。
- 从此链接下载并安装
PS > Import-Module PSTerminalServices
PS > Get-tssession -computername {name}
这是一个非常棒的实用工具。
答案3
执行 quser /server:[服务器名称] >[文本文件路径].txt
它列出了所有信息,并将其传输到空格分隔的文本文件中,以便轻松导入和分析。效果很好,避免了调用依赖于 32 位或 64 位的本机 API 的任何复杂性。如果是 .Net 重点应用,则可以在托管代码中完成所有操作。