Programowe określenie czasu trwania zablokowanej stacji roboczej?


111

Jak można określić w kodzie, jak długo maszyna jest zablokowana?

Inne pomysły poza C # są również mile widziane.


Podoba mi się pomysł na obsługę okien (i zaakceptowałem go) ze względu na prostotę i czystość, ale niestety nie sądzę, że zadziała w tym konkretnym przypadku. Chciałem uruchomić to na mojej stacji roboczej w pracy, a nie w domu (lub jak przypuszczam oprócz domu), ale jest to dość trudne dzięki uprzejmości DoD. Właściwie to jeden z powodów, dla których skręcam własne.

I tak to spiszę i zobaczę, czy zadziała. Dziękuję wszystkim!

Odpowiedzi:


138

Nie znalazłem tego wcześniej, ale z dowolnej aplikacji można podłączyć SessionSwitchEventHandler. Oczywiście Twoja aplikacja będzie musiała być uruchomiona, ale dopóki jest:

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
Przetestowano w 100% na Windows 7 x64 i Windows 10 x64.
Contango,

Wow, działa niesamowicie! bez błędów bez wyjątków, gładko i czysto!
Mayer Spitzer,

To jest właściwy sposób, aby to zrobić. Zgodnie z tym artykułem firmy Microsoft „Nie ma funkcji, którą można wywołać w celu określenia, czy stacja robocza jest zablokowana”. Musi być monitorowany przy użyciu SessionSwitchEventHandler.
JonathanDavidArndt

35

Utworzyłbym usługę Windows (typ projektu Visual Studio 2005), która obsługuje zdarzenie OnSessionChange, jak pokazano poniżej:

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

To, co i jak zarejestrujesz aktywność w tym momencie, zależy od Ciebie, ale usługa Windows zapewnia szybki i łatwy dostęp do zdarzeń systemu Windows, takich jak uruchamianie, zamykanie, logowanie / wylogowywanie, a także zdarzenia blokowania i odblokowywania.


18

Poniższe rozwiązanie wykorzystuje interfejs API Win32. OnSessionLock jest wywoływana, gdy stacja robocza jest zablokowana, a OnSessionUnlock jest wywoływana, gdy jest odblokowana.

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

1
Jest to dobra opcja, jeśli okaże się, że zdarzenie SessionSwitch (z innych odpowiedzi) nie uruchamia się (np. Twoja aplikacja je blokuje).
kad81

Dla przyszłych czytelników ... myślę, że to zastąpienie pochodzi z System.Windows.Forms.Form, ponieważ możesz napisać taką klasę: public class Form1: System.Windows.Forms.Form
granadaCoder

Działa to dla mnie, gdy SystemEvents.SessionSwitch nie działa
DCOPTimDowd

5

Wiem, że to stare pytanie, ale znalazłem metodę uzyskania stanu blokady dla danej sesji.

Znalazłem moją odpowiedź tutaj, ale była w C ++, więc przetłumaczyłem jak najwięcej na C #, aby uzyskać stan blokady.

Więc oto idzie:

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

Uwaga: powyższy kod został wyodrębniony ze znacznie większego projektu, więc jeśli trochę przegapiłem. Nie miałem czasu na przetestowanie powyższego kodu, ale planuję wrócić za tydzień lub dwa, aby wszystko sprawdzić. Opublikowałem to tylko teraz, ponieważ nie chciałem o tym zapomnieć.


To działa (do tej pory testowano system Windows 7). Dzięki, szukaliśmy tego przez ostatnie kilka tygodni i Twoja odpowiedź nadeszła w ostatniej chwili!
SteveP

1
W kodzie jest kilka błędów: 1. if (session_info_ex.Level != 1)- jeśli warunek jest prawdziwy, pamięć nie zostanie zwolniona. 2. jeśli session_info_ex.Level! = 1 nie powinieneś tego robić: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);ponieważ rozmiar zwracanego bufora może różnić się od rozmiaru WTSINFOEX
SergeyT

(ciąg dalszy) 3. Dodanie pola nie było konieczne, UInt32 Reserved;zamiast tego należy WTSINFOEX_LEVEL1całkowicie zdefiniować strukturę . W tym przypadku kompilator wykona poprawne wypełnienie (wyrównanie) pól wewnątrz struktury. 4. Funkcja WTSFreeMemoryExjest tu niewłaściwie używana. WTSFreeMemorynależy użyć zamiast tego. WTSFreeMemoryExma zwolnić pamięć po WTSEnumerateSessionsEx.
SergeyT

(liczone) 5. CharSet = CharSet.Autonależy używać we wszystkich atrybutach.
SergeyT

4

Jeśli jesteś zainteresowany napisaniem usługi Windows, która "wyszukuje" te zdarzenia, topshelf (biblioteka / framework, który znacznie ułatwia pisanie usług Windows) ma haczyk.

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

a teraz kod do połączenia usługi górnej półki z interfejsem / betonem powyżej

Wszystko poniżej jest "typową" konfiguracją górnej półki… z wyjątkiem 2 linii, które oznaczyłem jako

/ * TO JEST MAGICZNA LINIA * /

To one powodują uruchomienie metody SessionChanged.

Przetestowałem to w systemie Windows 10 x64. Zablokowałem i odblokowałem moją maszynę i uzyskałem pożądany rezultat.

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

My packages.config, aby zapewnić wskazówki dotyczące wersji:

  <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" />

lub jest możliwe do użycia x.EnableSessionChanged();w połączeniu z ServiceSessionChangeimplementacją interfejsu, jeśli zaimplementowano ServiceControli nie utworzono implicity instancji klasy usług. Lubię x.Service<ServiceImpl>();. Trzeba zaimplementować ServiceSessionChangew ServiceImplklasie:class ServiceImpl : ServiceControl, ServiceSessionChange
Oleksa

3

UWAGA : To nie jest odpowiedź, ale (wkład) do odpowiedzi Timothy'ego Cartera , ponieważ moja reputacja nie pozwala mi do tej pory komentować.

Na wypadek gdyby ktoś wypróbował kod z odpowiedzi Timothy'ego Cartera i nie udało mu się go od razu uruchomić w usłudze Windows, jest jedna właściwość, którą należy ustawić truew konstruktorze usługi. Po prostu dodaj linię w konstruktorze:

CanHandleSessionChangeEvent = true;

I pamiętaj, aby nie ustawiać tej właściwości po uruchomieniu usługi, w przeciwnym razie InvalidOperationExceptionzostanie wyrzucony.


-3

Poniżej znajduje się w 100% działający kod, aby sprawdzić, czy komputer jest zablokowany, czy nie.

Przed użyciem tego użyj przestrzeni nazw 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
Sprawdź MSDN pod kątem OpenInputDesktop i GetUserObjectInformation, aby zamiast tego uzyskać nazwę aktywnego pulpitu. Powyższy kod nie jest bezpieczny / miły dla użytkowników, którzy pracują na wielu pulpitach, używając narzędzia desktops.exe firmy Microsoft lub w inny sposób. Albo jeszcze lepiej, po prostu spróbuj utworzyć okno na aktywnym pulpicie (SetThreadDesktop), a jeśli to zadziała, pokaż na nim swój interfejs użytkownika. Jeśli nie, to jest to chroniony / specjalny pulpit, więc nie rób tego.
eselk
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.