Я хочу иметь возможность создать сценарий 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
(cephalopod - это имя моей машины, машина, которая подключена к серверу)
.
Я это вижу HKCU:\Volatile Environment
есть имя клиента, а temp
В key есть имя пользователя, но я не могу установить только с помощью ключей, активен ли сеанс в данный момент.
Мне не хватает вызова API, который предоставит мне всю эту информацию в одном месте?
Основное требование: найдите в Диспетчере задач> Список пользователей для имени пользователя и клиента, где активен статус.
Насколько мне известно, для этого не существует интерфейса WMI.
Мне не хватает вызова API, который предоставит мне всю эту информацию в одном месте?
Ага. Вы можете получить данные из Win32 API. Точнее, из wtsapi32.dll. Вы можете написать программу на C или вызвать ее с помощью P / Invoke из C # или даже Powershell.
Поскольку вам, вероятно, нужен 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 ... AFAIK. Это делает отладку такого рода вещей действительно раздражающей, поскольку вам приходится перезапускать 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
Бы PS Терминальные службы (для Powershell) сделать трюк? Я использую это все время на наших 10 терминальных серверах.
PS > Import-Module PSTerminalServices
PS > Get-tssession -computername {name}
Это замечательная утилита.
Запустить quser / server: [имя сервера]> [путь к текстовому файлу] .txt
Он перечисляет всю информацию, передает ее в текстовый файл с разделителями-пробелами, чтобы ее можно было легко импортировать и анализировать. Отлично работает и позволяет избежать каких-либо сложностей с вызовом собственных API-интерфейсов, которые зависят от 32 или 64 бит. Может быть выполнено все в управляемом коде, если это приложение, ориентированное на .Net.
http://weblogs.asp.net/owscott/archive/2003/12/30/Managing-Terminal-Services-Sessions-Remotely.aspx
может быть вам полезнее :)
Большое спасибо за сценарий.
Я обнаружил одно ограничение; Из-за "Console.WriteLine" невозможно передать результат (например, в Select-String). Я изменил ваш класс, чтобы это было возможно. Метод Listuser возвращает список
После загрузки класса просто вызовите [RDPInfo] :: NEW (). Listusers ("myserver"), а затем вы можете передать результат по конвейеру.
# QuerySessionInformation.ps1
# Written by Ryan Ries, Jan. 2013, with help from MSDN and Stackoverflow.
# Modified lolix2 Jan 2019.
$Code = @'
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
public class RDPInfo
{
List<String> resultList = new List<string>();
[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 IntPtr OpenServer(String Name)
{
IntPtr server = WTSOpenServer(Name);
return server;
}
public void CloseServer(IntPtr ServerHandle)
{
WTSCloseServer(ServerHandle);
}
public List<String> ListUsers(String ServerName)
{
IntPtr serverHandle = IntPtr.Zero;
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)
resultList.Add(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: n/a");
else
resultList.Add(Marshal.PtrToStringAnsi(domainPtr) + "\\" + Marshal.PtrToStringAnsi(userPtr) + "\tSessionID: " + si.SessionID + "\tClientName: " + Marshal.PtrToStringAnsi(clientNamePtr));
}
WTSFreeMemory(clientNamePtr);
WTSFreeMemory(userPtr);
WTSFreeMemory(domainPtr);
}
WTSFreeMemory(SessionInfoPtr);
}
}
catch(Exception ex)
{
resultList.Add("Exception: " + ex.Message);
}
finally
{
CloseServer(serverHandle);
}
return resultList;
}
}
'@
Add-Type $Code