我有一个用c#编写的.Net服务。我正在考虑WTSEnumerateSessions
。会话检查每15分钟由一个计时器运行,它列出所有会话,并将用户名/域组合与预定义的用户组进行比较,以确定它们是否已登录到服务器上。
问题出在几个服务器上,WTSEnumerateSessions
的调用无限期挂起。它工作正常几次,然后突然停止,导致服务无响应。
以下是我用来枚举会话的代码。
[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
}
private void ListUsers_Elapsed()
{
IntPtr serverHandle = IntPtr.Zero;
List<String> resultList = new List<string>();
serverHandle = WTSOpenServer("");
try
{
IntPtr SessionInfoPtr = IntPtr.Zero;
IntPtr userPtr = IntPtr.Zero;
IntPtr domainPtr = 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);
//Then I compare the session information against a list of users to determine if they are still active
WTSFreeMemory(userPtr);
WTSFreeMemory(domainPtr);
}
WTSFreeMemory(SessionInfoPtr);
}
}
finally
{
CloseServer(serverHandle);
}
}
我生成了一个用户转储,似乎有一个永远不会超时的WaitForSingleObject:
00000000`cd78dd78 000007fe`fd6810dc ntdll!NtWaitForSingleObject+0xa
00000000`cd78dd80 000007fe`fce11442 KERNELBASE!WaitForSingleObjectEx+0x79
00000000`cd78de20 000007fe`fce113fd winsta!WaitForLsmStart+0xa3
00000000`cd78de50 000007fe`fce11336 winsta!OpenLocalLSM+0xe0
00000000`cd78deb0 000007fe`fce12669 winsta!CPublicBinding::GetLSMBinding+0x51
00000000`cd78def0 000007fe`fce12576 winsta!_tsrpcEnumerate+0xd3
00000000`cd78df90 000007fe`fb073500 winsta!WinStationEnumerateW+0x22
00000000`cd78dfc0 000007fe`8de35caf wtsapi32!WTSEnumerateSessionsA+0x60
00000000`cd78e030 000007fe`8de351c0 0x000007fe`8de35caf
00000000`cd78e120 000007fe`8de34b61 0x000007fe`8de351c0
00000000`cd78e260 000007fe`8de3849a 0x000007fe`8de34b61
00000000`cd78e2d0 000007fe`eb922c73 0x000007fe`8de3849a
00000000`cd78e370 000007fe`ec2639a5 System_ni+0x922c73
00000000`cd78e3e0 000007fe`ec263719 mscorlib_ni+0x4a39a5
00000000`cd78e540 000007fe`ec30be92 mscorlib_ni+0x4a3719
00000000`cd78e570 000007fe`ec30bc7e mscorlib_ni+0x54be92
00000000`cd78e600 000007fe`ec2c12e4 mscorlib_ni+0x54bc7e
00000000`cd78e670 000007fe`ed45a7f3 mscorlib_ni+0x5012e4
00000000`cd78e710 000007fe`ed45a6de clr!CallDescrWorkerInternal+0x83
00000000`cd78e750 000007fe`ed45ae76 clr!CallDescrWorkerWithHandler+0x4a
00000000`cd78e790 000007fe`ed519033 clr!MethodDescCallSite::CallTargetWorker+0x251
00000000`cd78e940 000007fe`ed45c121 clr!AppDomainTimerCallback_Worker+0x23
00000000`cd78ea30 000007fe`ed45c0a8 clr!ManagedThreadBase_DispatchInner+0x2d
00000000`cd78ea70 000007fe`ed45c019 clr!ManagedThreadBase_DispatchMiddle+0x6c
00000000`cd78eb70 000007fe`ed45c15f clr!ManagedThreadBase_DispatchOuter+0x75
00000000`cd78ec00 000007fe`ed518fc1 clr!ManagedThreadBase_FullTransitionWithAD+0x2f
00000000`cd78ec60 000007fe`ed518f35 clr!AppDomainTimerCallback+0x66
00000000`cd78ecc0 000007fe`ed518b42 clr!ThreadpoolMgr::AsyncTimerCallbackCompletion+0x36
00000000`cd78ed10 000007fe`ed4df046 clr!UnManagedPerAppDomainTPCount::DispatchWorkItem+0x122
00000000`cd78edb0 000007fe`ed4def3a clr!ThreadpoolMgr::ExecuteWorkRequest+0x46
00000000`cd78ede0 000007fe`ed59fcb6 clr!ThreadpoolMgr::WorkerThreadStart+0xf4
00000000`cd78ee70 00000000`775f59ed clr!Thread::intermediateThreadProc+0x7d
00000000`cd78f930 00000000`7772ba01 kernel32!BaseThreadInitThunk+0xd
00000000`cd78f960 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
有人可以解释为什么它会悬挂,如果有更好的方法可以做到这一点。我依赖计时器而不是注册OnSessionChange
事件的理由是,在过去我发现后者不可靠,因为它有时会触发虚假事件,或者在发生会话更改时根本不会触发。
正如我所说,除了其中几个服务器之外,这在大多数服务器上运行良好。即使对于那些受此问题影响的人,它也可以在计时器在前4或5次触发已用事件后工作,然后它会挂起在下一个已经过去的事件上,并且在此之后每个连续经过的事件也会如此。
P.S。受影响的服务器都安装了Windows Server 2008 R2
编辑:
好的,我发现了一些非常奇怪的东西。我设法让WTSEnumerateSessions无需挂起即可工作。问题是我所做的事情并没有真正意义,在我的生活中,我无法弄清楚它是做些什么来阻止悬挂。
所以这就是我所做的,出于沮丧,我正在寻找其他方法来获取会话列表。其中一种方法是使用LsaEnumerateLogonSessions
来获取会话列表而不是WTSEnumerateSessions
。所以我在调用WTSEnumerateSessions
之前调用了以下函数。
public static List<string> PopulateSessionsList()
{
var outList = new List<string>();
System.Security.Principal.WindowsIdentity currentUser = System.Security.Principal.WindowsIdentity.GetCurrent();
DateTime systime = new DateTime(1601, 1, 1, 0, 0, 0, 0); //win32 systemdate
UInt64 count;
IntPtr luidPtr = IntPtr.Zero;
CommonWin32.LsaEnumerateLogonSessions(out count, out luidPtr); //gets an array of pointers to LUIDs
IntPtr iter = luidPtr; //set the pointer to the start of the array
for (ulong i = 0; i < count; i++) //for each pointer in the array
{
IntPtr sessionData;
CommonWin32.LsaGetLogonSessionData(iter, out sessionData);
CommonWin32.SECURITY_LOGON_SESSION_DATA data = (CommonWin32.SECURITY_LOGON_SESSION_DATA)Marshal.PtrToStructure(sessionData, typeof(CommonWin32.SECURITY_LOGON_SESSION_DATA));
//if we have a valid logon
if (data.PSiD != IntPtr.Zero)
{
//get the security identifier for further use
System.Security.Principal.SecurityIdentifier sid = new System.Security.Principal.SecurityIdentifier(data.PSiD);
//extract some useful information from the session data struct
var ptrToStringUni = Marshal.PtrToStringUni(data.Username.buffer);
if (ptrToStringUni != null)
{
string username = ptrToStringUni.Trim(); //get the account username
var toStringUni = Marshal.PtrToStringUni(data.LoginDomain.buffer);
if (toStringUni != null)
{
string domain = toStringUni.Trim(); //domain for this account
var stringUni = Marshal.PtrToStringUni(data.AuthenticationPackage.buffer);
if (stringUni != null)
{
string authpackage = stringUni.Trim(); //authentication package
}
string session = data.Session.ToString();
CommonWin32.SECURITY_LOGON_TYPE secType = (CommonWin32.SECURITY_LOGON_TYPE)data.LogonType;
DateTime time = systime.AddTicks((long)data.LoginTime); //get the datetime the session was logged in
outList.Add("Session: " + session + " User: " + username + " *** Domain: " + domain + " *** Login Type: (" + data.LogonType + ") " + secType.ToString() + " *** Login Time: " + time.ToLocalTime().ToString());
}
}
}
iter = (IntPtr)((int)iter + Marshal.SizeOf(typeof(CommonWin32.LUID))); //move the pointer forward
CommonWin32.LsaFreeReturnBuffer(sessionData); //free the SECURITY_LOGON_SESSION_DATA memory in the struct
}
CommonWin32.LsaFreeReturnBuffer(luidPtr); //free the array of LUIDs
return outList;
}
这是我的pInvoke代码:
[DllImport("secur32.dll", SetLastError = false)]
public static extern uint LsaFreeReturnBuffer(IntPtr buffer);
[DllImport("Secur32.dll", SetLastError = false)]
public static extern uint LsaEnumerateLogonSessions(out UInt64 LogonSessionCount, out IntPtr LogonSessionList);
[DllImport("Secur32.dll", SetLastError = false)]
public static extern uint LsaGetLogonSessionData(IntPtr luid, out IntPtr ppLogonSessionData);
令我惊讶的是,这称呼停止了悬挂问题。我不确定为什么会这样做。不确定它是否调用了一些内部/私有方法。
如果没有调用新方法,只需要不到10次连续调用就可以挂起罪魁祸首,我在几台机器上尝试的每次测试都是如此。添加代码后,它就可以了。如果你在一分钟内拨打WTSEnumerateSessions
1000次就没关系,这无关紧要。甚至没有一次。
有人可以告诉我为什么打电话给LsaEnumerateLogonSessions
和LsaGetLogonSessionData
会神奇地停止悬挂。有关这些功能在内部执行的任何信息?