C# WTSEnumerateSessions挂起且永远不会返回
我有一个用c#编写的.Net服务。我正在pInvokingC# WTSEnumerateSessions挂起且永远不会返回,c#,windows,session,service,C#,Windows,Session,Service,我有一个用c#编写的.Net服务。我正在pInvokingWTSEnumeratessions。会话检查由计时器每15分钟运行一次,它列出所有会话,并将用户名/域组合与预定义的用户集进行比较,以确定他们是否登录到服务器 问题在于有几个服务器对wtsenumeratessions的调用无限期挂起。它可以正常工作几次,然后突然停止,导致服务变得无响应 下面是我用来枚举会话的代码 [DllImport("wtsapi32.dll")] static extern IntPtr WTSOpenServe
WTSEnumeratessions
。会话检查由计时器每15分钟运行一次,它列出所有会话,并将用户名/域组合与预定义的用户集进行比较,以确定他们是否登录到服务器
问题在于有几个服务器对wtsenumeratessions
的调用无限期挂起。它可以正常工作几次,然后突然停止,导致服务变得无响应
下面是我用来枚举会话的代码
[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);
}
}
有人能解释一下为什么它会被挂起来,以及是否有更好的方法。我之所以依赖计时器而不是注册OnSessionChange
事件,是因为过去我发现后者不可靠,因为它有时会触发虚假事件,或者在发生会话更改时根本不会触发
正如我所说的,除了几个服务器外,这在大多数服务器上都可以正常工作。即使对于那些受问题影响的人,它也会在计时器触发经过的事件4或5次后工作,然后它只挂起下一个引发的经过的事件,之后的每个连续经过的事件也是如此
另外,所有受影响的服务器都安装了Windows Server 2008 R2
编辑:
好吧,我发现了一些很奇怪的东西。我设法让WTSEnumerateSessions不用绞刑就能工作。问题是我所做的没有真正意义,在我的生活中,我不知道该怎么做才能阻止绞刑
下面是我所做的,出于沮丧,我正在寻找其他方法来获取会话列表。其中一种方法是使用LsaEnumerateLogonSessions
来获取会话列表,而不是WTSEnumerateSessions
。因此,我在调用wtsenumeratessions
之前调用了以下函数
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;
}
令我惊讶的是,打电话就停止了悬而未决的问题。我不知道它为什么那样做。不确定它是否调用某个内部/私有方法
在不调用新方法的情况下,只要连续调用不到10次,罪犯就会被绞死,我在几台机器上尝试的每次测试都是这样。添加代码后,它就可以正常工作了。不管你在一分钟内调用wtsenumeratessions
1000次,它都不会挂断。一次也没有
有人能告诉我为什么调用
LsaEnumerateLogonSessions
和LsaGetLogonSessionData
会神奇地停止挂起。有关于这些函数在内部执行什么操作的信息吗 WaitForLsmStart的谷歌点击率非常低。只有两个,其中一个描述了您的确切问题和解决方法。@HansPassant我看到了两个答案,一个是Firefox崩溃,另一个是打印机更新问题,我相信。它们都没有提供任何真正的解决办法。我不能要求用户不安装更新。我需要一个真正的解决办法。除了微软,你不会从任何人那里得到“真正的解决办法”。如果你不愿意打电话给他们,那么就把你的发现告诉你的客户。他们可以自己决定如何继续。@HansPassant最近,我能够在我的计算机上重现这个问题,并且我已经卸载了有问题的知识库。所以可能还有别的原因。这是隐私问题还是安全问题?我尝试了很多方法,但似乎都没有效果。@HansPassant我用一个新发现更新了我的问题,这是我在试图找到阻止我的服务挂起的方法时意外发现的。你知道为什么在调用wtsenumeratessions
之前调用新方法会神奇地阻止后者挂起吗?
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;
}
[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);