简体   繁体   English

从登录和注销获得通知

[英]Get notified from logon and logoff

I have to develop a program which runs on a local pc as a service an deliver couple of user status to a server. 我必须开发一个程序,它在本地PC上作为服务运行,为服务器提供几个用户状态。 At the beginning I have to detect the user logon and logoff . 一开始我必须检测用户登录注销

My idea was to use the ManagementEventWatcher class and to query the Win32_LogonSession to be notified if something changed. 我的想法是使用ManagementEventWatcher类并查询Win32_LogonSession ,以便在发生更改时收到通知。

My first test works well, here is the code part (This would executed as a thread from a service) : 我的第一个测试运行良好,这是代码部分(这将作为服务的线程执行)

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);
    }
}

But I have some understanding problems and I'm not sure if this is the common way to solve that task. 但我有一些理解问题,我不确定这是否是解决该任务的常用方法。

  1. If I query Win32_LogonSession I get several records which are associated to the same user. 如果我查询Win32_LogonSession我会得到几条与同一用户相关联的记录。 For example I get this IDs 7580798 and 7580829 and if I query 例如,我得到这个ID 7580798和7580829,如果我查询

    ASSOCIATORS OF {Win32_LogonSession.LogonId=X} WHERE ResultClass=Win32_UserAccount ASSOCIATORS OF {Win32_LogonSession.LogonId = X} WHERE ResultClass = Win32_UserAccount

    I get the same record for different IDs. 我获得了不同ID的相同记录。 (Win32_UserAccount.Domain="PC-Name",Name="User1") (Win32_UserAccount.Domain = “PC-名称”,名称= “用户1”)

    Why are there several logon session with the same user? 为什么有多个与同一用户的登录会话? What is the common way to get the current signed in user? 获取当前用户签名的常用方法是什么? Or better how to get notified correctly by the login of a user? 或者更好的方法是如何通过用户登录正确收到通知?

  2. I thought I could use the same way with __InstanceDeletionEvent to determine if a user is log off. 我以为我可以用__InstanceDeletionEvent以相同的方式来确定用户是否注销。 But I guess if the event is raised, I cant query Win32_UserAccount for the username after that. 但我想如果事件被提出,那么在此之后我无法查询Win32_UserAccount的用户名。 I'm right? 我是正确的?

I'm at the right direction or are there better ways? 我是在正确的方向还是有更好的方法? It would be awesome if you could help me! 如果你可以帮助我,那真是太棒了!

Edit Is the WTSRegisterSessionNotification class the correct way? 编辑 WTSRegisterSessionNotification类是否正确? I don't know if it's possible, because in a service I haven't a window handler. 我不知道是否可能,因为在服务中我没有窗口处理程序。

Since you are on a service, you can get session change events directly. 由于您使用的是服务,因此可以直接获取会话更改事件。

You can register yourself to receive the SERVICE_CONTROL_SESSIONCHANGE event. 您可以注册自己以接收SERVICE_CONTROL_SESSIONCHANGE事件。 In particular, you will want to look for the WTS_SESSION_LOGON and WTS_SESSION_LOGOFF reasons. 特别是,您需要查找WTS_SESSION_LOGONWTS_SESSION_LOGOFF原因。

For details and links to the relevant MSDN docs, check this answer I wrote just yesterday . 有关MSDN文档的详细信息和链接,请查看我昨天写的这个答案

In C# it is even easier, as ServiceBase already wraps the service control routine and exposes the event as an overridable OnSessionChange method for you. 在C#中,它更容易,因为ServiceBase已经包装了服务控制例程并将事件公开为可OnSessionChange方法。 See MSDN docs for ServiceBase , and do not forget to set the CanHandleSessionChangeEvent property to true to enable the execution of this method. 请参阅ServiceBase的MSDN文档 ,并且不要忘记将CanHandleSessionChangeEvent属性设置为true以启用此方法的执行。

What you get back when the framework calls your OnSessionChange override is a SessionChangeDescription Structure with a reason (logoff, logon, ...) and a session ID you can use to obtain information, for example, on the user logging on/off (see the link to my prev answer for details) 当框架调用OnSessionChange覆盖时,您得到的是SessionChangeDescription结构,其中包含原因(注销,登录,...)和可用于获取信息的会话ID,例如,用户登录/注销信息(请参阅我的热门答案链接详情)

EDIT: sample code 编辑:示例代码

 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;
           ...
        }

You could use the System Event Notification Service technology which is part of Windows. 您可以使用作为Windows一部分的系统事件通知服务技术。 It has the ISensLogon2 interface that provides logon/logoff events (and other events such as remote session connections). 它具有ISensLogon2接口 ,可提供登录/注销事件(以及其他事件,如远程会话连接)。

Here is a piece of code (a sample Console Application) that demonstrates how to do it. 这是一段代码(示例控制台应用程序),演示了如何执行此操作。 You can test it using a remote desktop session from another computer for example, this will trigger the SessionDisconnect, SessionReconnect events for example. 您可以使用来自另一台计算机的远程桌面会话对其进行测试,例如,这将触发SessionDisconnect,SessionReconnect事件。

This code should support all versions of Windows from XP to Windows 8. 此代码应支持从XP到Windows 8的所有Windows版本。

Add reference to the COM component named, COM+ 1.0 Admin Type Library aka COMAdmin. 添加对名为COM + 1.0 Admin Type Library (即COMAdmin)的COM组件的引用。

Note Be sure to set the Embed Interop Types to 'False', otherwise you will get the following error: "Interop type 'COMAdminCatalogClass' cannot be embedded. Use the applicable interface instead." 注意务必将嵌入互操作类型设置为'False',否则将出现以下错误:“无法嵌入Interop类型'COMAdminCatalogClass'。请改用相应的接口。”

Contrary to other articles you will find on the Internet about using this technology in .NET, it does not references the Sens.dll because ... it does not seem to exist on Windows 8 (I don't know why). 与互联网上有关在.NET中使用此技术的其他文章相反,它不引用Sens.dll,因为它在Windows 8上似乎不存在(我不知道为什么)。 However the technology seems supported and the SENS service is indeed installed and runs fine on Windows 8, so you just to need to declare the interfaces and guids manually (like in this sample), or reference an interop assembly created on an earlier version of Windows (it should work fine as the guids and various interfaces have not changed). 然而,该技术似乎得到支持,并且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: Ensure that Visual Studio is running with administrator priviledges by right-clicking your Visual Studio shortcut and clicking run as administrator , otherwise an System.UnauthorizedAccessException will be thrown when the program is run. 注意:通过右键单击Visual Studio快捷方式并单击run as administrator运行,确保Visual Studio以管理员权限run as administrator ,否则在运行程序时将抛出System.UnauthorizedAccessException

Here's the code (all of them residing inside a class; in my case, the class inheriting ServiceBase ). 这是代码(所有代码都驻留在类中;在我的例子中,是继承ServiceBase的类)。 This is especially useful if you also want to get the logged-on user's username. 如果您还想获取登录用户的用户名,这将特别有用。

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

With the above code in your class, you can simply get the username in the method you're overriding like this: 使用您的类中的上述代码,您可以简单地获取您要覆盖的方法中的用户名,如下所示:

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

NB: Remember to add CanHandleSessionChangeEvent = true; 注意:记得添加CanHandleSessionChangeEvent = true; In the constructor of the class inheriting from ServiceBase 在从ServiceBase继承的类的构造函数中

I use ServiceBase.OnSessionChange to catch the different user events and load the necessary information afterwards. 我使用ServiceBase.OnSessionChange来捕获不同的用户事件,然后加载必要的信息。

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

To load the session information I use the WTS_INFO_CLASS . 要加载会话信息,我使用WTS_INFO_CLASS See my example below: 请参阅下面的示例:

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);
        }
    }
}

The following code use the static AvailabilityChanged event from User , which gets fired as soon as the session state changes. 以下代码使用来自User的静态AvailabilityChanged事件,一旦会话状态更改,就会触发该事件。 The arg e contains the specific user. 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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 读取远程服务器事件日志以获取AD用户登录和注销事件并将其保存到SQL - Read remote server event log to get AD user logon and logoff events and save it to SQL 如何可靠地捕获Windows登录,注销,锁定和解锁服务中的事件? - How to reliably capture Windows logon, logoff, lock and unlock events from a service? 如何监控Active Directory用户登录/注销? - How to monitor Active Directory user logon/logoff? Windows 7 SystemEvents捕获登录/注销服务应用程序 - Windows 7 SystemEvents catch Logon/Logoff service application 域控制器中的Active Directory用户登录/注销历史记录 - Active Directory user logon/logoff history in domain controller 从ASP.NET页面获取Windows登录 - Get Windows Logon From Within ASP.NET Page 如何从 .Net 中的 UserPrincipal 获取下级登录名 - How to get down-level logon name from UserPrincipal in .Net 从登录ID获取用户SID(Windows XP及更高版本) - Get User SID From Logon ID (Windows XP and Up) 如何使.NET Windows服务检测登录,注销和切换用户事件? - How to make a .NET Windows Service detect Logon, Logoff and Switch User events? 如何通过C#以编程方式向GPO添加登录/注销/启动/关闭脚本? - How does one programmatically add a logon/logoff/startup/shutdown script to a GPO via c#?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM