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

gla*_*snt 6 windows-server-2008-r2

我希望能够创建一个 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
Run Code Online (Sandbox Code Playgroud)

(头足类是我的机器名,登陆服务器框的机器)

.

我可以看到它HKCU:\Volatile Environment确实有客户端名称,并且temp密钥中包含用户名,但是如果会话当前处于活动状态,我无法单独从密钥建立。

我是否错过了将所有这些信息集中在一处的 API 调用?

基本要求:grep 出任务管理器> 用户列表中的用户和客户端名称,其中状态为活动。

Rya*_*ies 8

我所知道的没有 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
Run Code Online (Sandbox Code Playgroud)

将所有这些复制到名为 QuerySessionInformation.ps1 的文件中。现在在 C:\Windows\SysWOW64\WindowsPowershell\v1.0 中启动32 位版本的 Powershell。上面的代码使用的指针在本机 64 位环境中不起作用。

现在运行脚本。如果您之前从未在该服务器上运行 32 位版本的 Powershell,则需要使用 Set-ExecutionPolicy 修改脚本执行策略,因为 32 位和 64 位 Powershell 具有单独的执行策略。请注意,脚本本身不应该有任何输出,因为它所做的只是编译 .NET 代码并将其添加到当前环境中。另请注意,一旦使用 Add-Type 添加了类型,您就无法在不退出 Powershell 会话的情况下卸载它...... AFAIK。这使得调试这类东西真的很烦人,因为每次修改代码时都必须重新启动 Powershell。

现在代码已加载,请键入:

PS C:\> [RDPInfo]::ListUsers("REMOTESERVER")
Run Code Online (Sandbox Code Playgroud)

如果 REMOTESERVER 上有任何活动的用户会话,输出将如下所示:

DOMAIN\UserName  SessionID: 2    ClientName: RYAN-PC
Run Code Online (Sandbox Code Playgroud)

这将适用于远程计算机以及本地计算机,但请注意,如果运行此程序的用户对远程计算机没有足够的权限,它将静默失败(无输出)。

编辑:WTS_INFO_CLASS 中还有其他一些您可能感兴趣的信息,例如 WTSConnectState 和 WTSClientAddress。您所要做的就是查询它们。

编辑:我还将此解决方案转换为本地代码 (C) 以在命令行上使用:

http://www.myotherpcisacloud.com/post/2013/01/16/Usersexe-v1003.aspx