WTSEnumerateSessions挂起,永不返回

时间:2015-01-07 18:54:11

标签: c# windows session service

我有一个用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次就没关系,这无关紧要。甚至没有一次。

有人可以告诉我为什么打电话给LsaEnumerateLogonSessionsLsaGetLogonSessionData会神奇地停止悬挂。有关这些功能在内部执行的任何信息?

0 个答案:

没有答案