Программно определить продолжительность заблокированной рабочей станции?

111

Как определить с помощью кода, как долго машина заблокирована?

Также приветствуются другие идеи за пределами C #.


Мне нравится идея службы Windows (и я ее принял) за простоту и чистоту, но, к сожалению, я не думаю, что она сработает для меня в данном конкретном случае. Я хотел запустить это на моей рабочей станции на работе, а не дома (или, я полагаю, в дополнение к дому), но он довольно жестко заблокирован благодаря любезности Министерства обороны. На самом деле, это одна из причин, по которой я катаю свой собственный.

Я все равно напишу это и посмотрю, работает ли. Спасибо всем!

AgentConundrum
источник

Ответы:

138

Раньше я этого не находил, но из любого приложения вы можете подключить SessionSwitchEventHandler. Очевидно, ваше приложение должно быть запущено, но пока оно:

Microsoft.Win32.SystemEvents.SessionSwitch += new Microsoft.Win32.SessionSwitchEventHandler(SystemEvents_SessionSwitch);

void SystemEvents_SessionSwitch(object sender, Microsoft.Win32.SessionSwitchEventArgs e)
{
    if (e.Reason == SessionSwitchReason.SessionLock)
    { 
        //I left my desk
    }
    else if (e.Reason == SessionSwitchReason.SessionUnlock)
    { 
        //I returned to my desk
    }
}
Тимоти Картер
источник
3
100% протестировано на Windows 7 x64 и Windows 10 x64.
контанго
Вау, отлично работает! без ошибок без исключений, гладко и чисто!
Майер Спитцер
Это правильный способ сделать это. Согласно этой статье Microsoft : «Нет функции, которую можно вызвать, чтобы определить, заблокирована ли рабочая станция». Это необходимо контролировать с помощью SessionSwitchEventHandler.
JonathanDavidArndt,
35

Я бы создал службу Windows (тип проекта Visual Studio 2005), которая обрабатывает событие OnSessionChange, как показано ниже:

protected override void OnSessionChange(SessionChangeDescription changeDescription)
{
    if (changeDescription.Reason == SessionChangeReason.SessionLock)
    { 
        //I left my desk
    }
    else if (changeDescription.Reason == SessionChangeReason.SessionUnlock)
    { 
        //I returned to my desk
    }
}

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

Тимоти Картер
источник
18

В приведенном ниже решении используется Win32 API. OnSessionLock вызывается, когда рабочая станция заблокирована, и OnSessionUnlock вызывается, когда она разблокирована.

[DllImport("wtsapi32.dll")]
private static extern bool WTSRegisterSessionNotification(IntPtr hWnd,
int dwFlags);

[DllImport("wtsapi32.dll")]
private static extern bool WTSUnRegisterSessionNotification(IntPtr
hWnd);

private const int NotifyForThisSession = 0; // This session only

private const int SessionChangeMessage = 0x02B1;
private const int SessionLockParam = 0x7;
private const int SessionUnlockParam = 0x8;

protected override void WndProc(ref Message m)
{
    // check for session change notifications
    if (m.Msg == SessionChangeMessage)
    {
        if (m.WParam.ToInt32() == SessionLockParam)
            OnSessionLock(); // Do something when locked
        else if (m.WParam.ToInt32() == SessionUnlockParam)
            OnSessionUnlock(); // Do something when unlocked
    }

    base.WndProc(ref m);
    return;
}

void OnSessionLock() 
{
    Debug.WriteLine("Locked...");
}

void OnSessionUnlock() 
{
    Debug.WriteLine("Unlocked...");
}

private void Form1Load(object sender, EventArgs e)
{
    WTSRegisterSessionNotification(this.Handle, NotifyForThisSession);
}

// and then when we are done, we should unregister for the notification
//  WTSUnRegisterSessionNotification(this.Handle);
adeel825
источник
1
Это хороший вариант, если вы обнаружите, что событие SessionSwitch (из других ответов) не срабатывает (например, ваше приложение подавляет его).
kad81
Для будущих читателей ... Я ~ думаю ~ здесь переопределение исходит от System.Windows.Forms.Form, так как вы могли бы написать такой класс: public class Form1: System.Windows.Forms.Form
granadaCoder
Это работает для меня, когда SystemEvents.SessionSwitch не работает
DCOPTimDowd
5

Я знаю, что это старый вопрос, но я нашел способ получить состояние блокировки для данного сеанса.

Я нашел свой ответ здесь, но он был на C ++, поэтому я перевел все, что мог, на C #, чтобы получить состояние блокировки.

Итак, вот и:

static class SessionInfo {
    private const Int32 FALSE = 0;

    private static readonly IntPtr WTS_CURRENT_SERVER = IntPtr.Zero;

    private const Int32 WTS_SESSIONSTATE_LOCK = 0;
    private const Int32 WTS_SESSIONSTATE_UNLOCK = 1;

    private static bool _is_win7 = false;

    static SessionInfo() {
        var os_version = Environment.OSVersion;
        _is_win7 = (os_version.Platform == PlatformID.Win32NT && os_version.Version.Major == 6 && os_version.Version.Minor == 1);
    }

    [DllImport("wtsapi32.dll")]
    private static extern Int32 WTSQuerySessionInformation(
        IntPtr hServer,
        [MarshalAs(UnmanagedType.U4)] UInt32 SessionId,
        [MarshalAs(UnmanagedType.U4)] WTS_INFO_CLASS WTSInfoClass,
        out IntPtr ppBuffer,
        [MarshalAs(UnmanagedType.U4)] out UInt32 pBytesReturned
    );

    [DllImport("wtsapi32.dll")]
    private static extern void WTSFreeMemoryEx(
        WTS_TYPE_CLASS WTSTypeClass,
        IntPtr pMemory,
        UInt32 NumberOfEntries
    );

    private enum WTS_INFO_CLASS {
        WTSInitialProgram = 0,
        WTSApplicationName = 1,
        WTSWorkingDirectory = 2,
        WTSOEMId = 3,
        WTSSessionId = 4,
        WTSUserName = 5,
        WTSWinStationName = 6,
        WTSDomainName = 7,
        WTSConnectState = 8,
        WTSClientBuildNumber = 9,
        WTSClientName = 10,
        WTSClientDirectory = 11,
        WTSClientProductId = 12,
        WTSClientHardwareId = 13,
        WTSClientAddress = 14,
        WTSClientDisplay = 15,
        WTSClientProtocolType = 16,
        WTSIdleTime = 17,
        WTSLogonTime = 18,
        WTSIncomingBytes = 19,
        WTSOutgoingBytes = 20,
        WTSIncomingFrames = 21,
        WTSOutgoingFrames = 22,
        WTSClientInfo = 23,
        WTSSessionInfo = 24,
        WTSSessionInfoEx = 25,
        WTSConfigInfo = 26,
        WTSValidationInfo = 27,
        WTSSessionAddressV4 = 28,
        WTSIsRemoteSession = 29
    }

    private enum WTS_TYPE_CLASS {
        WTSTypeProcessInfoLevel0,
        WTSTypeProcessInfoLevel1,
        WTSTypeSessionInfoLevel1
    }

    public enum WTS_CONNECTSTATE_CLASS {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

    public enum LockState {
        Unknown,
        Locked,
        Unlocked
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTSINFOEX {
        public UInt32 Level;
        public UInt32 Reserved; /* I have observed the Data field is pushed down by 4 bytes so i have added this field as padding. */
        public WTSINFOEX_LEVEL Data;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTSINFOEX_LEVEL {
        public WTSINFOEX_LEVEL1 WTSInfoExLevel1;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct WTSINFOEX_LEVEL1 {
        public UInt32 SessionId;
        public WTS_CONNECTSTATE_CLASS SessionState;
        public Int32 SessionFlags;

        /* I can't figure out what the rest of the struct should look like but as i don't need anything past the SessionFlags i'm not going to. */

    }

    public static LockState GetSessionLockState(UInt32 session_id) {
        IntPtr ppBuffer;
        UInt32 pBytesReturned;

        Int32 result = WTSQuerySessionInformation(
            WTS_CURRENT_SERVER,
            session_id,
            WTS_INFO_CLASS.WTSSessionInfoEx,
            out ppBuffer,
            out pBytesReturned
        );

        if (result == FALSE)
            return LockState.Unknown;

        var session_info_ex = Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);

        if (session_info_ex.Level != 1)
            return LockState.Unknown;

        var lock_state = session_info_ex.Data.WTSInfoExLevel1.SessionFlags;
        WTSFreeMemoryEx(WTS_TYPE_CLASS.WTSTypeSessionInfoLevel1, ppBuffer, pBytesReturned);

        if (_is_win7) {
            /* Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ee621019(v=vs.85).aspx
                * Windows Server 2008 R2 and Windows 7:  Due to a code defect, the usage of the WTS_SESSIONSTATE_LOCK
                * and WTS_SESSIONSTATE_UNLOCK flags is reversed. That is, WTS_SESSIONSTATE_LOCK indicates that the
                * session is unlocked, and WTS_SESSIONSTATE_UNLOCK indicates the session is locked.
                * */
            switch (lock_state) {
                case WTS_SESSIONSTATE_LOCK:
                    return LockState.Unlocked;

                case WTS_SESSIONSTATE_UNLOCK:
                    return LockState.Locked;

                default:
                    return LockState.Unknown;
            }
        }
        else {
            switch (lock_state) {
                case WTS_SESSIONSTATE_LOCK:
                    return LockState.Locked;

                case WTS_SESSIONSTATE_UNLOCK:
                    return LockState.Unlocked;

                default:
                    return LockState.Unknown;
            }
        }
    }
}

Примечание: приведенный выше код был извлечен из гораздо более крупного проекта, поэтому, если я пропустил, извините. У меня нет времени протестировать приведенный выше код, но я планирую вернуться через неделю или две, чтобы все проверить. Я разместил это сейчас только потому, что не хотел забыть это сделать.

Роберт
источник
Это работает (до сих пор проверено Windows 7). Спасибо, мы искали это последние несколько недель, и ваш ответ пришел как раз вовремя!
SteveP
1
В коде есть несколько ошибок: 1. if (session_info_ex.Level != 1)- при выполнении условия память не будет освобождаться. 2. если session_info_ex.Level! = 1, вам не следует этого делать: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);поскольку размер возвращаемого буфера может отличаться от размера WTSINFOEX
SergeyT
(продолжение) 3. Нет необходимости добавлять поле, UInt32 Reserved;вы должны полностью определить структуру WTSINFOEX_LEVEL1. В этом случае компилятор выполнит правильное заполнение (выравнивание) полей внутри структуры. 4. WTSFreeMemoryExЗдесь неправильно используется функция . WTSFreeMemoryдолжен использоваться вместо этого. WTSFreeMemoryExпредназначен для освобождения памяти после WTSEnumerateSessionsEx.
SergeyT
(подсчитано) 5. CharSet = CharSet.AutoДолжен использоваться во всех атрибутах.
SergeyT
4

Если вы заинтересованы в написании службы Windows для «поиска» этих событий, у topshelf (библиотека / фреймворк, которая значительно упрощает написание служб Windows) есть ловушка.

public interface IMyServiceContract
{
    void Start();

    void Stop();

    void SessionChanged(Topshelf.SessionChangedArguments args);
}



public class MyService : IMyServiceContract
{

    public void Start()
    {
    }

    public void Stop()
    {

    }

    public void SessionChanged(SessionChangedArguments e)
    {
        Console.WriteLine(e.ReasonCode);
    }   
}

а теперь код для подключения службы верхней полки к интерфейсу / конкретному выше

Все, что ниже, является "типичной" настройкой верхней полки .... кроме двух строк, которые я пометил как

/ * ЭТО ВОЛШЕБНАЯ СТРОКА * /

Это то, что запускает метод SessionChanged.

Я тестировал это с Windows 10 x64. Я заблокировал и разблокировал свою машину и получил желаемый результат.

            IMyServiceContract myServiceObject = new MyService(); /* container.Resolve<IMyServiceContract>(); */


            HostFactory.Run(x =>
            {
                x.Service<IMyServiceContract>(s =>
                {
                    s.ConstructUsing(name => myServiceObject);
                    s.WhenStarted(sw => sw.Start());
                    s.WhenStopped(sw => sw.Stop());
                    s.WhenSessionChanged((csm, hc, chg) => csm.SessionChanged(chg)); /* THIS IS MAGIC LINE */
                });

                x.EnableSessionChanged(); /* THIS IS MAGIC LINE */

                /* use command line variables for the below commented out properties */
                /*
                x.RunAsLocalService();
                x.SetDescription("My Description");
                x.SetDisplayName("My Display Name");
                x.SetServiceName("My Service Name");
                x.SetInstanceName("My Instance");
                */

                x.StartManually(); // Start the service manually.  This allows the identity to be tweaked before the service actually starts

                /* the below map to the "Recover" tab on the properties of the Windows Service in Control Panel */
                x.EnableServiceRecovery(r =>
                {
                    r.OnCrashOnly();
                    r.RestartService(1); ////first
                    r.RestartService(1); ////second
                    r.RestartService(1); ////subsequents
                    r.SetResetPeriod(0);
                });

                x.DependsOnEventLog(); // Windows Event Log
                x.UseLog4Net();

                x.EnableShutdown();

                x.OnException(ex =>
                {
                    /* Log the exception */
                    /* not seen, I have a log4net logger here */
                });
            });                 

Мой файл packages.config для подсказок о версиях:

  <package id="log4net" version="2.0.5" targetFramework="net45" />
  <package id="Topshelf" version="4.0.3" targetFramework="net461" />
  <package id="Topshelf.Log4Net" version="4.0.3" targetFramework="net461" />
granadaCoder
источник
или его можно использовать x.EnableSessionChanged();вместе с ServiceSessionChangeреализацией интерфейса, если вы реализовали ServiceControlимплицитность экземпляра класса обслуживания и не создаете его. Нравится x.Service<ServiceImpl>();. Вы должны реализовать ServiceSessionChangeв ServiceImplклассе:class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa
3

ПРИМЕЧАНИЕ : это не ответ, а (вклад) в ответ Тимоти Картера , потому что моя репутация пока не позволяет мне комментировать.

На всякий случай, если кто-то попробовал код из ответа Тимоти Картера и не заставил его работать сразу в службе Windows, есть одно свойство, которое необходимо установить trueв конструкторе службы. Просто добавьте строку в конструктор:

CanHandleSessionChangeEvent = true;

И убедитесь, что не устанавливали это свойство после запуска службы, иначе InvalidOperationExceptionбудет выдано сообщение.

Абдул Рахман Каяли
источник
-3

Ниже приведен 100% рабочий код, чтобы узнать, заблокирован ли компьютер или нет.

Перед использованием этого используйте пространство имен System.Runtime.InteropServices.

[DllImport("user32", EntryPoint = "OpenDesktopA", CharSet = CharSet.Ansi,SetLastError = true, ExactSpelling = true)]
private static extern Int32 OpenDesktop(string lpszDesktop, Int32 dwFlags, bool fInherit, Int32 dwDesiredAccess);

[DllImport("user32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern Int32 CloseDesktop(Int32 hDesktop);

[DllImport("user32", CharSet = CharSet.Ansi,SetLastError = true,ExactSpelling = true)]
private static extern Int32 SwitchDesktop(Int32 hDesktop);

public static bool IsWorkstationLocked()
{
    const int DESKTOP_SWITCHDESKTOP = 256;
    int hwnd = -1;
    int rtn = -1;

    hwnd = OpenDesktop("Default", 0, false, DESKTOP_SWITCHDESKTOP);

    if (hwnd != 0)
    {
        rtn = SwitchDesktop(hwnd);
        if (rtn == 0)
        {
            // Locked
            CloseDesktop(hwnd);
            return true;
        }
        else
        {
            // Not locked
            CloseDesktop(hwnd);
        }
    }
    else
    {
        // Error: "Could not access the desktop..."
    }

    return false;
}
Десятилетняя Луна
источник
4
Проверьте MSDN для OpenInputDesktop и GetUserObjectInformation, чтобы вместо этого получить имя активного рабочего стола. Приведенный выше код небезопасен / приятен для пользователей, которые работают на нескольких рабочих столах, используют служебную программу Desktops.exe от Microsoft или иным образом. Или еще лучше, просто попробуйте создать окно на активном рабочем столе (SetThreadDesktop), и, если оно работает, покажите на нем свой пользовательский интерфейс. Если нет, то это защищенный / специальный рабочий стол, поэтому не делайте этого.
eselk