WMI 对象是否获取具有客户端名称的当前会话?

WMI 对象是否获取具有客户端名称的当前会话?

我希望能够创建一个 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个终端服务器上。

  1. 从此链接下载并安装
  2. PS > Import-Module PSTerminalServices
  3. PS > Get-tssession -computername {name}

这是一个非常棒的实用工具。

答案3

执行 quser /server:[服务器名称] >[文本文件路径].txt

它列出了所有信息,并将其传输到空格分隔的文本文件中,以便轻松导入和分析。效果很好,避免了调用依赖于 32 位或 64 位的本机 API 的任何复杂性。如果是 .Net 重点应用,则可以在托管代码中完成所有操作。

答案4

相关内容