Get notified from logon and logoff
我必须开发一个在本地PC上作为服务运行的程序,向服务器提供几个用户状态。开始时,我必须检测用户的登录和注销。
我的想法是使用
我的第一个测试工作得很好,这里是代码部分(这将作为来自服务的线程执行):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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.logonid=x_其中resultclass=win32_用户帐户
不同身份证的记录是一样的。(win32_useraccount.domain="pc name",name="user1")。
为什么同一用户有多个登录会话?什么是获取当前登录用户的常用方法?或者更好的方法是如何得到通知通过用户登录正确吗?
我想我可以用同样的方法和
我走对了方向还是有更好的方法?如果你能帮助我,那就太棒了!
编辑wtsregistersessionnotification类是否正确?我不知道是否可能,因为在服务中我没有窗口处理程序。
因为您在服务上,所以可以直接获取会话更改事件。
您可以注册接收
有关详细信息和相关的msdn文档的链接,请查看我昨天写的这个答案。
在C中,这更容易,因为ServiceBase已经包装了服务控制例程,并将事件公开为可重写的
当框架调用您的
编辑:示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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; ... } |
您可以使用作为Windows一部分的系统事件通知服务技术。它具有ISensLogon2接口,提供登录/注销事件(以及远程会话连接等其他事件)。
下面是一段代码(一个示例控制台应用程序),演示了如何执行该操作。您可以使用来自另一台计算机的远程桌面会话对其进行测试,例如,这将触发sessiondisconnect、sessionconnect事件。
此代码应支持从XP到Windows 8的所有Windows版本。
Add reference to the COM component named, COM+ 1.0 Admin Type Library aka COMAdmin.
注意:请确保将嵌入互操作类型设置为"false",否则将出现以下错误:"无法嵌入互操作类型"comadmincatalogclass。请改用适用的接口。"
与你在互联网上看到的其他关于在.NET中使用此技术的文章相反,它没有引用sens.dll,因为…它似乎不存在于Windows8上(我不知道为什么)。然而,该技术似乎得到了支持,SENS服务确实已安装并在Windows 8上运行良好,因此您只需手动声明接口和guid(如本示例中所示),或者引用在早期版本的Windows上创建的互操作程序集(由于guid和各种接口没有更改,它应该可以正常工作)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 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 } |
注意:通过右键单击Visual Studio快捷方式并单击
我使用ServiceBase.OnSessionChange捕获不同的用户事件,然后加载必要的信息。
4要加载会话信息,我使用wts_info_类。请参阅下面的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | 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); } } } |
以下代码使用来自
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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); } |
这是代码(它们都位于一个类中;在我的例子中,是继承
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | [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; } |
使用类中的上述代码,您只需在重写的方法中获取用户名,如下所示:
1 2 3 4 5 | protected override void OnSessionChange(SessionChangeDescription changeDescription) { string username = GetUsername(changeDescription.SessionId); //continue with any other thing you wish to do } |
注意:记住在继承