从登录和注销获得通知

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

我的想法是使用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); } } 

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

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

    委员会{Win32_LogonSession.LogonId = X} WHERE ResultClass = Win32_UserAccount

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

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

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

我正朝着正确的方向前进,还是有更好的方法? 如果你能帮帮我的话真棒!

编辑 WTSRegisterSessionNotification类是否正确? 我不知道是否可能,因为在服务中我没有窗口处理程序。

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

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

有关MSDN文档的详细信息和链接,请查看我昨天写的这个答案 。

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

当框架调用OnSessionChange覆盖时,您得到的是SessionChangeDescription结构,其中包含原因(注销,登录,…)和可用于获取信息的会话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; ... } 

您可以使用系统事件通知服务技术,该技术是Windows的一部分。 它具有ISensLogon2接口 ,可提供登录/注销事件(以及其他事件,如远程会话连接)。

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

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

添加对名为COM + 1.0 Admin Type Library (即COMAdmin)的COM组件的引用。

注意务必将Embed Interop Types设置为’False’,否则会出现以下错误:“无法嵌入Interop类型’COMAdminCatalogClass’。请改用适用的接口。”

与互联网上有关在.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 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 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快捷方式并单击run as administrator运行,确保Visual Studio以管理员权限run as administrator ,否则在运行程序时将抛出System.UnauthorizedAccessException

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

  [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 } 

注意:记得添加CanHandleSessionChangeEvent = true; 在从ServiceBaseinheritance的类的构造函数中

我使用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 User = new Dictionary(); 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 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); }