Programmgesteuert die Dauer einer gesperrten Workstation bestimmen?

111

Wie kann man im Code bestimmen, wie lange die Maschine gesperrt ist?

Andere Ideen außerhalb von C # sind ebenfalls willkommen.


Ich mag die Windows-Service-Idee (und habe sie akzeptiert) aus Gründen der Einfachheit und Sauberkeit, aber leider glaube ich nicht, dass sie in diesem speziellen Fall für mich funktionieren wird. Ich wollte dies auf meiner Workstation bei der Arbeit und nicht zu Hause ausführen (oder zusätzlich zu Hause, nehme ich an), aber es ist dank des DoD ziemlich hart gesperrt. Das ist ein Teil des Grundes, warum ich meine eigenen rolle.

Ich werde es trotzdem aufschreiben und sehen, ob es funktioniert. Vielen Dank an alle!

AgentConundrum
quelle

Antworten:

138

Ich hatte das vorher noch nicht gefunden, aber von jeder Anwendung aus können Sie einen SessionSwitchEventHandler anschließen. Natürlich muss Ihre Anwendung ausgeführt werden, aber solange es so ist:

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
    }
}
Timothy Carter
quelle
3
100% unter Windows 7 x64 und Windows 10 x64 getestet.
Contango
Wow, funktioniert super! Keine Fehler, keine Ausnahmen, glatt und sauber!
Mayer Spitzer
Dies ist der richtige Weg. In diesem Microsoft-Artikel heißt es: "Sie können keine Funktion aufrufen, um festzustellen, ob die Workstation gesperrt ist." Es muss mit dem SessionSwitchEventHandler überwacht werden.
JonathanDavidArndt
35

Ich würde einen Windows-Dienst (einen Visual Studio 2005-Projekttyp) erstellen, der das OnSessionChange-Ereignis wie folgt behandelt:

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

Was und wie Sie die Aktivität zu diesem Zeitpunkt protokollieren, liegt bei Ihnen. Ein Windows-Dienst bietet jedoch schnellen und einfachen Zugriff auf Windows-Ereignisse wie Starten, Herunterfahren, Anmelden / Abmelden sowie Ereignisse zum Sperren und Entsperren.

Timothy Carter
quelle
18

Die folgende Lösung verwendet die Win32-API. OnSessionLock wird aufgerufen, wenn die Workstation gesperrt ist, und OnSessionUnlock wird aufgerufen, wenn sie entsperrt ist.

[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
quelle
1
Dies ist eine gute Option, wenn Sie feststellen, dass das SessionSwitch-Ereignis (aus anderen Antworten) nicht ausgelöst wird (z. B. wenn Ihre Anwendung es unterdrückt).
Kad81
Für zukünftige Leser ... Ich denke ~ die Überschreibung hier kommt von System.Windows.Forms.Form, wie Sie vielleicht eine Klasse wie diese schreiben: public class Form1: System.Windows.Forms.Form
granadaCoder
Dies funktioniert bei mir, wenn SystemEvents.SessionSwitch dies nicht tut
DCOPTimDowd
5

Ich weiß, dass dies eine alte Frage ist, aber ich habe eine Methode gefunden, um den Sperrstatus für eine bestimmte Sitzung zu erhalten.

Ich habe meine Antwort hier gefunden, aber sie war in C ++, also habe ich so viel wie möglich in C # übersetzt, um den Sperrstatus zu erhalten.

Also los geht's:

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

Hinweis: Der obige Code wurde aus einem viel größeren Projekt extrahiert. Wenn ich etwas verpasst habe, tut mir leid. Ich habe keine Zeit, den obigen Code zu testen, aber ich plane, in ein oder zwei Wochen wiederzukommen, um alles zu überprüfen. Ich habe es jetzt nur gepostet, weil ich nicht vergessen wollte, es zu tun.

Robert
quelle
Dies funktioniert (Windows 7 bisher getestet). Vielen Dank, wir haben in den letzten Wochen danach gesucht und Ihre Antwort ist pünktlich gekommen!
SteveP
1
Der Code enthält nur wenige Fehler: 1. if (session_info_ex.Level != 1)- Wenn die Bedingung erfüllt ist, wird der Speicher nicht freigegeben. 2. Wenn session_info_ex.Level! = 1 ist, sollten Sie dies nicht tun: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);da die Größe des zurückgegebenen Puffers von der Größe von WTSINFOEX
SergeyT
(Fortsetzung) 3. Es war nicht erforderlich, das Feld hinzuzufügen, UInt32 Reserved;stattdessen sollten Sie die Struktur WTSINFOEX_LEVEL1vollständig definieren . In diesem Fall führt der Compiler das korrekte Auffüllen (Ausrichten) von Feldern innerhalb der Struktur durch. 4. Funktion WTSFreeMemoryExwird hier missbraucht. WTSFreeMemorymuss stattdessen verwendet werden. WTSFreeMemoryExsoll nachher Speicher freigeben WTSEnumerateSessionsEx.
SergeyT
(Fortsetzung) 5. CharSet = CharSet.Automuss in allen Attributen verwendet werden.
SergeyT
4

Wenn Sie einen Windows-Dienst schreiben möchten, um diese Ereignisse zu "finden", hat topshelf (die Bibliothek / das Framework, die das Schreiben von Windows-Diensten erheblich vereinfacht) einen Haken.

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

und jetzt den Code, um den Topshelf-Service mit der Schnittstelle / dem Beton oben zu verbinden

Alles unten ist "typisches" Topshelf-Setup ... mit Ausnahme von 2 Zeilen, die ich als markiert habe

/ * DAS IST MAGISCHE LINIE * /

Dadurch wird die SessionChanged-Methode ausgelöst.

Ich habe dies mit Windows 10 x64 getestet. Ich habe meine Maschine gesperrt und entsperrt und das gewünschte Ergebnis erzielt.

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

Meine packages.config, um Hinweise zu Versionen zu geben:

  <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
quelle
oder es ist möglich, x.EnableSessionChanged();in Verbindung mit der ServiceSessionChangeSchnittstellenimplementierung zu verwenden, wenn Sie eine ServiceControlimplizite Serviceklasseninstanz implementiert haben und diese nicht erstellen. Wie x.Service<ServiceImpl>();. Sie müssen ServiceSessionChangein der ServiceImplKlasse implementieren :class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa
3

HINWEIS : Dies ist keine Antwort, sondern ein (Beitrag) zur Antwort von Timothy Carter , da ich aufgrund meines Rufs bisher keine Kommentare abgeben kann.

Nur für den Fall, dass jemand den Code aus Timothy Carters Antwort ausprobiert hat und ihn in einem Windows-Dienst nicht sofort zum Laufen gebracht hat, muss trueim Konstruktor des Dienstes eine Eigenschaft festgelegt werden. Fügen Sie einfach die Zeile im Konstruktor hinzu:

CanHandleSessionChangeEvent = true;

Und stellen Sie sicher, dass Sie diese Eigenschaft nicht festlegen, nachdem der Dienst gestartet wurde, da sonst eine InvalidOperationExceptionausgelöst wird.

Abdul Rahman Kayali
quelle
-3

Unten finden Sie den 100% igen Arbeitscode, um festzustellen, ob der PC gesperrt ist oder nicht.

Bevor Sie dies verwenden, verwenden Sie den Namespace 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;
}
Jahrzehntelanger Mond
quelle
4
Überprüfen Sie MSDN auf OpenInputDesktop & GetUserObjectInformation, um stattdessen den aktiven Desktopnamen zu erhalten. Der obige Code ist nicht sicher / nützlich für Benutzer, die auf mehreren Desktops arbeiten, das Dienstprogramm desktops.exe von Microsoft verwenden oder auf andere Weise. Oder noch besser, versuchen Sie einfach, ein Fenster auf dem aktiven Desktop (SetThreadDesktop) zu erstellen. Wenn dies funktioniert, zeigen Sie Ihre Benutzeroberfläche darauf an. Wenn nicht, handelt es sich um einen geschützten / speziellen Desktop.
Eselk