Подтвердить что ты не робот

Получать уведомления от входа в систему и выхода из системы

Мне нужно разработать программу, которая работает на локальном компьютере как услуга, доставляющая пару статуса пользователя на сервер. Вначале я должен обнаружить пользователя logon и выйти.

Моя идея состояла в том, чтобы использовать класс 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, и если я запрошу

    АССОЦИАТОРЫ {Win32_LogonSession.LogonId = X}   WHERE Resultclass= Win32_UserAccount

    Я получаю одну и ту же запись для разных идентификаторов. (Win32_UserAccount.Domain = "PC-Name", Name= "User1" )

    Почему существует несколько сеансов входа в систему с одним и тем же пользователем? Что это общий способ получить текущий подписанный пользователь? Или лучше, как получить уведомление правильно по логину пользователя?

  • Я думал, что могу использовать то же самое с __InstanceDeletionEvent, чтобы определить, отключен ли пользователь. Но я думаю, что если событие будет поднято, я cant query Win32_UserAccount для имени пользователя после этого. Im правильно?

Im в правильном направлении или есть лучшие способы? Было бы здорово, если бы вы могли мне помочь!

Изменить Является ли класс WTSRegisterSessionNotification правильным? Я не знаю, возможно ли это, потому что в службе у меня нет обработчика окна.

4b9b3361

Ответ 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);
        }
    }
}

В следующем коде используется статическое событие AvailabilityChanged из User, которое запускается, как только изменяется состояние сеанса. Аргу 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);
}

Ответ 2

Вы можете использовать технологию System Notification Service, которая является частью Windows. Он имеет интерфейс ISensLogon2, который предоставляет события входа/выхода из системы (и другие события, такие как удаленные сеансы сеанса связи).

Вот фрагмент кода (пример консольного приложения), демонстрирующий, как это сделать. Например, вы можете протестировать его, используя сеанс удаленного рабочего стола с другого компьютера, например, это вызовет события SessionDisconnect, SessionReconnect.

Этот код должен поддерживать все версии Windows от XP до Windows 8.

Добавьте ссылку на COM-компонент с именем COM + 1.0. Библиотека типа администратора aka COMAdmin.

Примечание Обязательно установите для встроенных типов взаимодействий значение "False", иначе вы получите следующую ошибку: "Interop type" COMAdminCatalogClass "не может быть встроен. Вместо этого используйте соответствующий интерфейс."

В отличие от других статей, которые вы найдете в Интернете об использовании этой технологии в .NET, она не ссылается на Sens.dll, потому что... похоже, что она не существует в Windows 8 (я не знаю почему), Однако технология поддерживается, и служба SENS действительно установлена ​​и отлично работает в Windows 8, поэтому вам просто нужно вручную объявить интерфейсы и подсказки (например, в этом примере) или ссылаться на сборку interop, созданную на более ранней версии Windows (он должен работать нормально, поскольку гиды и различные интерфейсы не изменились).

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 работает с правами администратора, щелкнув правой кнопкой мыши ярлык Visual Studio и нажав run as administrator, иначе при запуске программы будет вызываться System.UnauthorizedAccessException.

Ответ 3

Поскольку вы находитесь в службе, вы можете напрямую получать события смены сеанса.

Вы можете зарегистрироваться для получения события SERVICE_CONTROL_SESSIONCHANGE. В частности, вы захотите найти причины WTS_SESSION_LOGON и WTS_SESSION_LOGOFF.

Подробнее и ссылки на соответствующие документы MSDN, проверьте этот ответ, который я написал только вчера.

В С# это еще проще, поскольку ServiceBase уже обертывает процедуру управления сервисом и предоставляет событие как переопределяемый метод OnSessionChange для вас. См. Документы MSDN для ServiceBase и не забудьте установить для свойства CanHandleSessionChangeEvent значение true, чтобы включить выполнение этого метода.

Что вы вернетесь, когда фреймворк вызывает ваш переопределитель OnSessionChange, это SessionChangeDescription Structure по причине (выход из системы, вход в систему,...) и идентификатор сеанса, который вы можете использовать для получения информации, например, при входе/выключении входа пользователя (см. ссылку на мой предыдущий ответ для деталей).

EDIT: пример кода

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

Ответ 4

Здесь код (все они находятся внутри класса, в моем случае класс, наследующий 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
}

NB: Не забудьте добавить CanHandleSessionChangeEvent = true; В конструкторе класса, наследующего от ServiceBase