关于c#:从登录和注销获得通知

Get notified from logon and logoff

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

我的想法是使用ManagementEventWatcher类,并查询Win32_LogonSession以便在发生变化时得到通知。

我的第一个测试工作得很好,这里是代码部分(这将作为来自服务的线程执行):

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我会得到一些记录,它们是与同一用户关联。例如,我得到了这个ID 7580798和7580829如果我有疑问

    win32_logonsession.logonid=x_其中resultclass=win32_用户帐户

    不同身份证的记录是一样的。(win32_useraccount.domain="pc name",name="user1")。

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

  • 我想我可以用同样的方法和__InstanceDeletionEvent来确定用户是否注销。但我想如果这件事被提起,我之后不能查询Win32_UserAccount中的用户名。我说的对吗?

  • 我走对了方向还是有更好的方法?如果你能帮助我,那就太棒了!

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


    因为您在服务上,所以可以直接获取会话更改事件。

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

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

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

    当框架调用您的OnSessionChange重写时,您得到的是一个sessionChangedDescription结构,其中包含原因(注销、登录等)和一个会话ID,您可以使用它来获取信息,例如,在用户登录/注销时(有关详细信息,请参阅指向我的上一个回答的链接)。

    编辑:示例代码

    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快捷方式并单击run as administrator,确保Visual Studio以管理员权限运行,否则在程序运行时将引发System.UnauthorizedAccessException


    我使用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);
            }
        }
    }

    以下代码使用来自User的静态AvailabilityChanged事件,该事件在会话状态更改时立即被激发。arg 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);
    }


    这是代码(它们都位于一个类中;在我的例子中,是继承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
    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
    }

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