首页 文章

从登录和注销获得通知

提问于
浏览
21

我必须开发一个程序,它在本地PC上作为服务运行,为服务器提供几个用户状态 . 一开始我必须检测用户 logonlogoff .

我的想法是使用 ManagementEventWatcher 类并查询 Win32_LogonSession 如果发生了变化则会收到通知 .

我的第一个测试运行良好,这是代码部分(这将作为服务的线程执行):

private readonly static WqlEventQuery qLgi = new WqlEventQuery("__InstanceCreationEvent", new TimeSpan(0, 0, 1), "TargetInstance ISA \"Win32_LogonSession\"");

public EventWatcherUser() {
}

public void DoWork() {
    ManagementEventWatcher eLgiWatcher = new ManagementEventWatcher(EventWatcherUser.qLgi);
    eLgiWatcher.EventArrived += new EventArrivedEventHandler(HandleEvent);
    eLgiWatcher.Start();
}

private void HandleEvent(object sender, EventArrivedEventArgs e)
{
    ManagementBaseObject f = (ManagementBaseObject)e.NewEvent["TargetInstance"];
    using (StreamWriter fs = new StreamWriter("C:\\status.log", true))
    {
        fs.WriteLine(f.Properties["LogonId"].Value);
    }
}

但我有一些理解问题,我不确定这是否是解决该任务的常用方法 .

  • 如果我查询 Win32_LogonSession ,我会得到几条与同一用户相关联的记录 . 例如,我得到这个ID 7580798和7580829,如果我查询

ASSOCIATORS OF WHERE ResultClass = Win32_UserAccount

我获得了不同ID的相同记录 . (Win32_UserAccount.Domain = “PC-名称”,名称= “用户1”)

为什么有多个与同一用户的登录会话?获取当前用户签名的常用方法是什么?或者更好的方法是如何通过用户登录正确收到通知?

  • 我以为我可以用 __InstanceDeletionEvent 以相同的方式确定用户是否注销 . 但我想如果事件被提出,那么在此之后我无法查询 Win32_UserAccount 的用户名 . 我是正确的?

我是在正确的方向还是有更好的方法?如果你可以帮助我,那真是太棒了!

Edit WTSRegisterSessionNotification类是正确的方法吗?我没有't know if it'可能,因为在服务中我没有窗口处理程序 .

4 回答

  • 17

    由于您使用的是服务,因此可以直接获取会话更改事件 .

    您可以注册自己以接收 SERVICE_CONTROL_SESSIONCHANGE 事件 . 特别是,您需要查找 WTS_SESSION_LOGONWTS_SESSION_LOGOFF 的原因 .

    有关相关MSDN文档的详细信息和链接,请查看this answer I wrote just yesterday .

    在C#中它更容易,因为ServiceBase已经包装了服务控制例程并将事件公开为可覆盖的 OnSessionChange 方法 . 请参阅MSDN docs for ServiceBase,并且不要忘记将 CanHandleSessionChangeEvent 属性设置为true以启用此方法的执行 .

    当框架调用 OnSessionChange 覆盖时,你得到的是SessionChangeDescription Structure,其原因(注销,登录,...)和会话ID可用于获取信息,例如,用户登录/注销(请参阅链接到我的热门答案了解详情)

    编辑:示例代码

    public class SimpleService : ServiceBase {
        ...
        public SimpleService()
        {
            CanPauseAndContinue = true;
            CanHandleSessionChangeEvent = true;
            ServiceName = "SimpleService";
        }
    
        protected override void OnSessionChange(SessionChangeDescription changeDescription)
        {
            EventLog.WriteEntry("SimpleService.OnSessionChange", DateTime.Now.ToLongTimeString() +
                " - Session change notice received: " +
                changeDescription.Reason.ToString() + "  Session ID: " + 
                changeDescription.SessionId.ToString());
    
    
            switch (changeDescription.Reason)
            {
                case SessionChangeReason.SessionLogon:
                    EventLog.WriteEntry("SimpleService.OnSessionChange: Logon");
                    break;
    
                case SessionChangeReason.SessionLogoff:       
                    EventLog.WriteEntry("SimpleService.OnSessionChange Logoff"); 
                    break;
               ...
            }
    
  • 15

    您可以使用属于Windows的System Event Notification Service技术 . 它具有ISensLogon2 interface,它提供登录/注销事件(以及其他事件,如远程会话连接) .

    这是一段代码(示例控制台应用程序),演示了如何执行此操作 . 您可以使用来自另一台计算机的远程桌面会话对其进行测试,例如,这将触发SessionDisconnect,SessionReconnect事件 .

    此代码应支持从XP到Windows 8的所有Windows版本 .

    添加对名为COM 1.0管理类型库(即COMAdmin)的COM组件的引用 .

    Note 请务必将嵌入互操作类型设置为'False',否则会出现以下错误:"Interop type 'COMAdminCatalogClass' cannot be embedded. Use the applicable interface instead."

    与互联网上有关在.NET中使用此技术的其他文章相反,它不引用Sens.dll,因为它在Windows 8上似乎不存在(我不知道为什么) . 然而,该技术似乎得到支持,并且SENS服务确实安装并在Windows 8上正常运行,因此您只需手动声明接口和guids(如本示例中所示),或引用在早期版本的Windows上创建的互操作程序集(它应该工作正常,因为guids和各种接口没有改变) .

    class Program
    {
        static SensEvents SensEvents { get; set; }
    
        static void Main(string[] args)
        {
            SensEvents = new SensEvents();
            SensEvents.LogonEvent += OnSensLogonEvent;
            Console.WriteLine("Waiting for events. Press [ENTER] to stop.");
            Console.ReadLine();
        }
    
        static void OnSensLogonEvent(object sender, SensLogonEventArgs e)
        {
            Console.WriteLine("Type:" + e.Type + ", UserName:" + e.UserName + ", SessionId:" + e.SessionId);
        }
    }
    
    public sealed class SensEvents
    {
        private static readonly Guid SENSGUID_EVENTCLASS_LOGON2 = new Guid("d5978650-5b9f-11d1-8dd2-00aa004abd5e");
        private Sink _sink;
    
        public event EventHandler<SensLogonEventArgs> LogonEvent;
    
        public SensEvents()
        {
            _sink = new Sink(this);
            COMAdminCatalogClass catalog = new COMAdminCatalogClass(); // need a reference to COMAdmin
    
            // we just need a transient subscription, for the lifetime of our application
            ICatalogCollection subscriptions = (ICatalogCollection)catalog.GetCollection("TransientSubscriptions");
    
            ICatalogObject subscription = (ICatalogObject)subscriptions.Add();
            subscription.set_Value("EventCLSID", SENSGUID_EVENTCLASS_LOGON2.ToString("B"));
            subscription.set_Value("SubscriberInterface", _sink);
            // NOTE: we don't specify a method name, so all methods may be called
            subscriptions.SaveChanges();
        }
    
        private void OnLogonEvent(SensLogonEventType type, string bstrUserName, uint dwSessionId)
        {
            EventHandler<SensLogonEventArgs> handler = LogonEvent;
            if (handler != null)
            {
                handler(this, new SensLogonEventArgs(type, bstrUserName, dwSessionId));
            }
        }
    
        private class Sink : ISensLogon2
        {
            private SensEvents _events;
    
            public Sink(SensEvents events)
            {
                _events = events;
            }
    
            public void Logon(string bstrUserName, uint dwSessionId)
            {
                _events.OnLogonEvent(SensLogonEventType.Logon, bstrUserName, dwSessionId);
            }
    
            public void Logoff(string bstrUserName, uint dwSessionId)
            {
                _events.OnLogonEvent(SensLogonEventType.Logoff, bstrUserName, dwSessionId);
            }
    
            public void SessionDisconnect(string bstrUserName, uint dwSessionId)
            {
                _events.OnLogonEvent(SensLogonEventType.SessionDisconnect, bstrUserName, dwSessionId);
            }
    
            public void SessionReconnect(string bstrUserName, uint dwSessionId)
            {
                _events.OnLogonEvent(SensLogonEventType.SessionReconnect, bstrUserName, dwSessionId);
            }
    
            public void PostShell(string bstrUserName, uint dwSessionId)
            {
                _events.OnLogonEvent(SensLogonEventType.PostShell, bstrUserName, dwSessionId);
            }
        }
    
        [ComImport, Guid("D597BAB4-5B9F-11D1-8DD2-00AA004ABD5E")]
        private interface ISensLogon2
        {
            void Logon([MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
            void Logoff([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
            void SessionDisconnect([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
            void SessionReconnect([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
            void PostShell([In, MarshalAs(UnmanagedType.BStr)] string bstrUserName, uint dwSessionId);
        }
    }
    
    public class SensLogonEventArgs : EventArgs
    {
        public SensLogonEventArgs(SensLogonEventType type, string userName, uint sessionId)
        {
            Type = type;
            UserName = userName;
            SessionId = sessionId;
        }
    
        public string UserName { get; private set; }
        public uint SessionId { get; private set; }
        public SensLogonEventType Type { get; private set; }
    }
    
    public enum SensLogonEventType
    {
        Logon,
        Logoff,
        SessionDisconnect,
        SessionReconnect,
        PostShell
    }
    

    Note: 通过右键单击Visual Studio快捷方式并单击 run as administrator ,确保Visual Studio以管理员权限运行,否则在程序运行时将抛出 System.UnauthorizedAccessException .

  • 1

    这是代码(所有代码都驻留在类中;在我的例子中,类继承 ServiceBase ) . 如果您还想获取登录用户的用户名,这将特别有用 .

    [DllImport("Wtsapi32.dll")]
        private static extern bool WTSQuerySessionInformation(IntPtr hServer, int sessionId, WtsInfoClass wtsInfoClass, out IntPtr ppBuffer, out int pBytesReturned);
        [DllImport("Wtsapi32.dll")]
        private static extern void WTSFreeMemory(IntPtr pointer);
    
        private enum WtsInfoClass
        {
            WTSUserName = 5, 
            WTSDomainName = 7,
        }
    
        private static string GetUsername(int sessionId, bool prependDomain = true)
        {
            IntPtr buffer;
            int strLen;
            string username = "SYSTEM";
            if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSUserName, out buffer, out strLen) && strLen > 1)
            {
                username = Marshal.PtrToStringAnsi(buffer);
                WTSFreeMemory(buffer);
                if (prependDomain)
                {
                    if (WTSQuerySessionInformation(IntPtr.Zero, sessionId, WtsInfoClass.WTSDomainName, out buffer, out strLen) && strLen > 1)
                    {
                        username = Marshal.PtrToStringAnsi(buffer) + "\\" + username;
                        WTSFreeMemory(buffer);
                    }
                }
            }
            return username;
        }
    

    使用您的类中的上述代码,您可以简单地获取您要覆盖的方法中的用户名,如下所示:

    protected override void OnSessionChange(SessionChangeDescription changeDescription)
    {
        string username = GetUsername(changeDescription.SessionId);
        //continue with any other thing you wish to do
    }
    

    注意:记得在继承自 ServiceBase 的类的构造函数中添加 CanHandleSessionChangeEvent = true;

  • 1

    我使用ServiceBase.OnSessionChange来捕获不同的用户事件并在之后加载必要的信息 .

    protected override void OnSessionChange(SessionChangeDescription desc)
    {
        var user = Session.Get(desc.SessionId);
    }
    

    要加载会话信息,我使用WTS_INFO_CLASS . 请参阅下面的示例:

    internal static class NativeMethods
    {
        public enum WTS_INFO_CLASS
        {
            WTSInitialProgram,
            WTSApplicationName,
            WTSWorkingDirectory,
            WTSOEMId,
            WTSSessionId,
            WTSUserName,
            WTSWinStationName,
            WTSDomainName,
            WTSConnectState,
            WTSClientBuildNumber,
            WTSClientName,
            WTSClientDirectory,
            WTSClientProductId,
            WTSClientHardwareId,
            WTSClientAddress,
            WTSClientDisplay,
            WTSClientProtocolType,
            WTSIdleTime,
            WTSLogonTime,
            WTSIncomingBytes,
            WTSOutgoingBytes,
            WTSIncomingFrames,
            WTSOutgoingFrames,
            WTSClientInfo,
            WTSSessionInfo
        }
    
        [DllImport("Kernel32.dll")]
        public static extern uint WTSGetActiveConsoleSessionId();
    
        [DllImport("Wtsapi32.dll")]
        public static extern bool WTSQuerySessionInformation(IntPtr hServer, Int32 sessionId, WTS_INFO_CLASS wtsInfoClass, out IntPtr ppBuffer, out Int32 pBytesReturned);
    
        [DllImport("Wtsapi32.dll")]
        public static extern void WTSFreeMemory(IntPtr pointer);
    }
    
    public static class Status
    {
        public static Byte Online
        {
            get { return 0x0; }
        }
    
        public static Byte Offline
        {
            get { return 0x1; }
        }
    
        public static Byte SignedIn
        {
            get { return 0x2; }
        }
    
        public static Byte SignedOff
        {
            get { return 0x3; }
        }
    }
    
    public static class Session
    {
        private static readonly Dictionary<Int32, User> User = new Dictionary<Int32, User>();
    
        public static bool Add(Int32 sessionId)
        {
            IntPtr buffer;
            int length;
    
            var name = String.Empty;
            var domain = String.Empty;
    
            if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTS_INFO_CLASS.WTSUserName, out buffer, out length) && length > 1)
            {
                name = Marshal.PtrToStringAnsi(buffer);
                NativeMethods.WTSFreeMemory(buffer);
                if (NativeMethods.WTSQuerySessionInformation(IntPtr.Zero, sessionId, NativeMethods.WTS_INFO_CLASS.WTSDomainName, out buffer, out length) && length > 1)
                {
                    domain = Marshal.PtrToStringAnsi(buffer);
                    NativeMethods.WTSFreeMemory(buffer);
                }
            }
    
            if (name == null || name.Length <= 0)
            {
                return false;
            }
    
            User.Add(sessionId, new User(name, domain));
    
            return true;
        }
    
        public static bool Remove(Int32 sessionId)
        {
            return User.Remove(sessionId);
        }
    
        public static User Get(Int32 sessionId)
        {
            if (User.ContainsKey(sessionId))
            {
                return User[sessionId];
            }
    
            return Add(sessionId) ? Get(sessionId) : null;
        }
    
        public static UInt32 GetActiveConsoleSessionId()
        {
            return NativeMethods.WTSGetActiveConsoleSessionId();
        }
    }
    
    public class AvailabilityChangedEventArgs : EventArgs
    {
        public bool Available { get; set; }
    
        public AvailabilityChangedEventArgs(bool isAvailable)
        {
            Available = isAvailable;
        }
    }
    
    public class User
    {
        private readonly String _name;
    
        private readonly String _domain;
    
        private readonly bool _isDomainUser;
    
        private bool _signedIn;
    
        public static EventHandler<AvailabilityChangedEventArgs> AvailabilityChanged;
    
        public User(String name, String domain)
        {
            _name = name;
            _domain = domain;
    
            if (domain.Equals("EXAMPLE.COM"))
            {
                _isDomainUser = true;
            }
            else
            {
                _isDomainUser = false;
            }
        }
    
        public String Name
        {
            get { return _name; }
        }
    
        public String Domain
        {
            get { return _domain; }
        }
    
        public bool IsDomainUser
        {
            get { return _isDomainUser; }
        }
    
        public bool IsSignedIn
        {
            get { return _signedIn; }
            set
            {
                if (_signedIn == value) return;
    
                _signedIn = value;
    
                OnAvailabilityChanged(this, new AvailabilityChangedEventArgs(IsSignedIn));
            }
        }
    
        protected void OnAvailabilityChanged(object sender, AvailabilityChangedEventArgs e)
        {
            if (AvailabilityChanged != null)
            {
                AvailabilityChanged(this, e);
            }
        }
    }
    

    下列代码使用来自 User 的静态 AvailabilityChanged 事件,一旦会话状态发生变化就会被触发 . arg e 包含特定用户 .

    public Main()
    {
      User.AvailabilityChanged += UserAvailabilityChanged;
    }
    
    private static void UserAvailabilityChanged(object sender, AvailabilityChangedEventArgs e)
    {
      var user = sender as User;
    
      if (user == null) return;
    
      System.Diagnostics.Debug.WriteLine(user.IsSignedIn);
    }
    

相关问题