Déterminer par programme une durée d'un poste de travail verrouillé?

111

Comment déterminer, dans le code, combien de temps la machine est verrouillée?

D'autres idées en dehors de C # sont également les bienvenues.


J'aime l'idée du service Windows (et je l'ai acceptée) pour sa simplicité et sa propreté, mais malheureusement je ne pense pas que cela fonctionnera pour moi dans ce cas particulier. Je voulais l'exécuter sur mon poste de travail au travail plutôt qu'à la maison (ou en plus de la maison, je suppose), mais c'est verrouillé assez durement grâce au DoD. C'est en partie la raison pour laquelle je roule moi-même, en fait.

Je vais l'écrire de toute façon et voir si cela fonctionne. Merci tout le monde!

AgentConundrum
la source

Réponses:

138

Je n'avais pas trouvé cela auparavant, mais à partir de n'importe quelle application, vous pouvez connecter un SessionSwitchEventHandler. Évidemment, votre application devra être en cours d'exécution, mais tant qu'elle le sera:

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
la source
3
Testé à 100% sur Windows 7 x64 et Windows 10 x64.
Contango
Wow, fonctionne à merveille! aucune erreur aucune exception, lisse et propre!
Mayer Spitzer
C'est la bonne façon de procéder. Selon cet article de Microsoft , «il n'existe aucune fonction que vous pouvez appeler pour déterminer si la station de travail est verrouillée». Il doit être surveillé à l'aide de SessionSwitchEventHandler.
JonathanDavidArndt
35

Je créerais un service Windows (un type de projet Visual Studio 2005) qui gère l'événement OnSessionChange comme indiqué ci-dessous:

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

Ce que vous enregistrez et comment vous enregistrez l'activité à ce stade dépend de vous, mais un service Windows fournit un accès rapide et facile aux événements Windows tels que le démarrage, l'arrêt, la connexion / déconnexion, ainsi que les événements de verrouillage et de déverrouillage.

Timothy Carter
la source
18

La solution ci-dessous utilise l'API Win32. OnSessionLock est appelé lorsque le poste de travail est verrouillé et OnSessionUnlock est appelé lorsqu'il est déverrouillé.

[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
la source
1
C'est une bonne option si vous trouvez que l'événement SessionSwitch (à partir d'autres réponses) ne se déclenche pas (par exemple, votre application le supprime).
kad81
Pour les futurs lecteurs ... Je ~ pense ~ que le remplacement ici vient de System.Windows.Forms.Form, comme dans vous pourriez écrire une classe comme celle-ci: public class Form1: System.Windows.Forms.Form
granadaCoder
Cela fonctionne pour moi lorsque SystemEvents.SessionSwitch ne fonctionne pas
DCOPTimDowd
5

Je sais que c'est une vieille question, mais j'ai trouvé une méthode pour obtenir l'état de verrouillage pour une session donnée.

J'ai trouvé ma réponse ici mais c'était en C ++, donc j'ai traduit autant que possible en C # pour obtenir l'état de verrouillage.

Alors voilà:

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

Remarque: le code ci-dessus a été extrait d'un projet beaucoup plus important, donc si j'ai raté un peu, désolé. Je n'ai pas eu le temps de tester le code ci-dessus mais je prévois de revenir dans une semaine ou deux pour tout vérifier. Je l'ai seulement posté maintenant parce que je ne voulais pas oublier de le faire.

Robert
la source
Cela fonctionne (Windows 7 testé jusqu'à présent). Merci, nous cherchons cela depuis quelques semaines et votre réponse est arrivée à temps!
SteveP
1
Il y a peu d'erreurs dans le code: 1. if (session_info_ex.Level != 1)- si la condition est vraie, la mémoire ne sera pas libérée. 2. si session_info_ex.Level! = 1 vous ne devriez pas faire ceci: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);parce que la taille du buffer retourné peut différer de la taille de WTSINFOEX
SergeyT
(suite) 3. Il n'était pas nécessaire d'ajouter le champ à la UInt32 Reserved;place, vous devez définir la structure WTSINFOEX_LEVEL1complètement. Dans ce cas, le compilateur effectuera un remplissage (alignement) correct des champs à l'intérieur de la structure. 4. La fonction WTSFreeMemoryExest mal utilisée ici. WTSFreeMemorydoit être utilisé à la place. WTSFreeMemoryExest destiné à libérer de la mémoire après WTSEnumerateSessionsEx.
SergeyT
(countinued) 5. CharSet = CharSet.Autodoit être utilisé dans tous les attributs.
SergeyT
4

Si vous êtes intéressé par l'écriture d'un service Windows pour "trouver" ces événements, topshelf (la bibliothèque / structure qui rend l'écriture des services Windows beaucoup plus facile) a un hook.

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

et maintenant le code pour câbler le service topshelf à l'interface / béton ci-dessus

Tout ce qui est ci-dessous est une configuration topshelf "typique" .... sauf pour 2 lignes que j'ai marquées comme

/ * CECI EST LA LIGNE MAGIQUE * /

C'est ce qui déclenche la méthode SessionChanged.

J'ai testé cela avec Windows 10 x64. J'ai verrouillé et déverrouillé ma machine et j'ai obtenu le résultat souhaité.

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

Mon packages.config pour fournir des conseils sur les versions:

  <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" />
grenadeCoder
la source
ou il est possible de l'utiliser x.EnableSessionChanged();en conjonction avec l' ServiceSessionChangeimplémentation d'interface si vous avez implémenté ServiceControlet ne créez pas l'implicité d'instance de classe de service. Comme x.Service<ServiceImpl>();. Vous devez implémenter ServiceSessionChangeen ServiceImplclasse:class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa
3

NOTE : Ce n'est pas une réponse, mais une (contribution) à la réponse de Timothy Carter , car ma réputation ne me permet pas de commenter jusqu'à présent.

Juste au cas où quelqu'un aurait essayé le code de la réponse de Timothy Carter et ne l'a pas fait fonctionner immédiatement dans un service Windows, il y a une propriété qui doit être définie truedans le constructeur du service. Ajoutez simplement la ligne dans le constructeur:

CanHandleSessionChangeEvent = true;

Et assurez-vous de ne pas définir cette propriété après le démarrage du service, sinon un InvalidOperationExceptionsera renvoyé.

Abdul Rahman Kayali
la source
-3

Vous trouverez ci-dessous le code de fonctionnement à 100% pour savoir si le PC est verrouillé ou non.

Avant d'utiliser cela, utilisez l'espace de noms 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;
}
Decade Moon
la source
4
Vérifiez MSDN pour OpenInputDesktop & GetUserObjectInformation, pour obtenir le nom du bureau actif à la place. Le code ci-dessus n'est pas sûr / agréable pour les utilisateurs qui travaillent sur plusieurs bureaux, à l'aide de l'utilitaire desktops.exe de Microsoft ou autrement. Ou mieux encore, essayez simplement de créer une fenêtre sur le bureau actif (SetThreadDesktop), et si cela fonctionne, affichez votre interface utilisateur dessus. Sinon, c'est un bureau protégé / spécial, alors ne le faites pas.
eselk