Tentukan Programatis Durasi dari Workstation Terkunci?

111

Bagaimana seseorang dapat menentukan, dalam kode, berapa lama mesin terkunci?

Ide lain di luar C # juga diterima.


Saya suka ide layanan windows (dan telah menerimanya) untuk kesederhanaan dan kebersihan, tapi sayangnya saya rasa itu tidak akan berhasil untuk saya dalam kasus khusus ini. Saya ingin menjalankan ini di tempat kerja saya di tempat kerja daripada di rumah (atau di samping rumah, saya kira), tetapi dikunci dengan cukup keras berkat DoD. Itu bagian dari alasan saya menggulung milik saya sendiri.

Saya akan tetap menulisnya dan melihat apakah berhasil. Terimakasih semuanya!

AgentConundrum
sumber

Jawaban:

138

Saya belum menemukan ini sebelumnya, tetapi dari aplikasi apa pun Anda dapat menghubungkan SessionSwitchEventHandler. Jelas aplikasi Anda perlu dijalankan, tetapi selama itu:

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
sumber
3
Diuji 100% pada Windows 7 x64 dan Windows 10 x64.
Contango
Wow, luar biasa! tidak ada kesalahan tidak ada pengecualian, halus dan bersih!
Mayer Spitzer
Ini cara yang benar untuk melakukannya. Menurut artikel Microsoft ini , "Tidak ada fungsi yang dapat Anda panggil untuk menentukan apakah workstation terkunci." Ini harus dipantau menggunakan SessionSwitchEventHandler.
JonathanDavidArndt
35

Saya akan membuat Layanan Windows (jenis proyek studio visual 2005) yang menangani acara OnSessionChange seperti yang ditunjukkan di bawah ini:

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

Apa dan bagaimana Anda mencatat aktivitas pada saat itu terserah Anda, tetapi Layanan Windows menyediakan akses cepat dan mudah ke acara windows seperti startup, shutdown, login / out, bersama dengan acara kunci dan buka kunci.

Timothy Carter
sumber
18

Solusi di bawah ini menggunakan Win32 API. OnSessionLock dipanggil saat workstation dikunci, dan OnSessionUnlock dipanggil saat tidak terkunci.

[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
sumber
1
Ini adalah opsi yang bagus jika Anda menemukan bahwa peristiwa SessionSwitch (dari jawaban lain) tidak aktif (misalnya aplikasi Anda menahannya).
kad81
Untuk pembaca masa depan ... Saya ~ pikir ~ penimpaan di sini berasal dari System.Windows.Forms.Form, seperti pada Anda mungkin menulis kelas seperti ini: kelas publik Form1: System.Windows.Forms.Form
granadaCoder
Ini berfungsi untuk saya ketika SystemEvents.SessionSwitch tidak
DCOPTimDowd
5

Saya tahu ini adalah pertanyaan lama tetapi saya telah menemukan metode untuk mendapatkan Status Kunci untuk sesi tertentu.

Saya menemukan jawaban saya di sini tetapi itu dalam C ++ jadi saya menerjemahkan sebanyak yang saya bisa ke C # untuk mendapatkan Status Kunci.

Jadi begini:

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

Catatan: Kode di atas diekstrak dari proyek yang jauh lebih besar jadi jika saya melewatkan sedikit maaf. Saya tidak punya waktu untuk menguji kode di atas tetapi berencana untuk kembali dalam satu atau dua minggu untuk memeriksa semuanya. Saya hanya mempostingnya sekarang karena saya tidak ingin lupa melakukannya.

Robert
sumber
Ini berfungsi (windows 7 diuji sejauh ini). Terima kasih, kami telah mencari ini selama beberapa minggu terakhir dan jawaban Anda datang tepat pada waktunya!
SteveP
1
Ada beberapa kesalahan dalam kode: 1. if (session_info_ex.Level != 1)- jika kondisinya benar, memori tidak akan dibebaskan. 2. jika session_info_ex.Level! = 1 Anda tidak boleh melakukan ini: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);karena ukuran buffer yang dikembalikan mungkin berbeda dari ukuran WTSINFOEX
SergeyT
(lanjutan) 3. Tidak perlu menambahkan field UInt32 Reserved;melainkan Anda harus mendefinisikan strukturnya secara WTSINFOEX_LEVEL1lengkap. Dalam hal ini kompilator akan melakukan padding (pelurusan) yang benar dari bidang di dalam struktur. 4. Fungsi WTSFreeMemoryExdisalahgunakan di sini. WTSFreeMemoryharus digunakan sebagai gantinya. WTSFreeMemoryExdimaksudkan untuk membebaskan memori setelah WTSEnumerateSessionsEx.
SergeyT
(countinued) 5. CharSet = CharSet.Autoharus digunakan di semua atribut.
SergeyT
4

Jika Anda tertarik untuk menulis layanan windows untuk "menemukan" kejadian ini, topshelf (perpustakaan / kerangka kerja yang membuat layanan windows lebih mudah) memiliki pengait.

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

dan sekarang kode untuk memasang layanan rak atas ke antarmuka / beton di atas

Semua yang di bawah ini adalah penyetelan rak atas "khas" .... kecuali untuk 2 baris yang saya tandai sebagai

/ * INI ADALAH GARIS SIHIR * /

Itulah yang membuat metode SessionChanged diaktifkan.

Saya menguji ini dengan windows 10 x64. Saya mengunci dan membuka kunci mesin saya dan mendapatkan hasil yang diinginkan.

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

Package.config saya untuk memberikan petunjuk tentang versi:

  <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
sumber
atau dimungkinkan untuk digunakan x.EnableSessionChanged();bersama dengan ServiceSessionChangeimplementasi antarmuka jika Anda telah mengimplementasikan ServiceControldan tidak membuat implikasi instance kelas layanan. Suka x.Service<ServiceImpl>();. Anda harus menerapkan ServiceSessionChangedi ServiceImplkelas:class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa
3

CATATAN : Ini bukan jawaban, tapi (kontribusi) untuk jawaban Timothy Carter , karena reputasi saya tidak memungkinkan saya untuk berkomentar sejauh ini.

Seandainya seseorang mencoba kode dari jawaban Timothy Carter dan tidak langsung berhasil di layanan Windows, ada satu properti yang perlu disetel truedi konstruktor layanan. Cukup tambahkan baris di konstruktor:

CanHandleSessionChangeEvent = true;

Dan pastikan untuk tidak menyetel properti ini setelah layanan dimulai jika tidak InvalidOperationExceptionakan terlempar.

Abdul Rahman Kayali
sumber
-3

Di bawah ini adalah 100% kode kerja untuk mengetahui apakah PC terkunci atau tidak.

Sebelum menggunakan ini gunakan 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;
}
Dekade Bulan
sumber
4
Periksa MSDN untuk OpenInputDesktop & GetUserObjectInformation, untuk mendapatkan nama desktop yang aktif. Kode di atas tidak aman / bagus untuk pengguna yang bekerja di banyak desktop, menggunakan utilitas desktops.exe dari Microsoft, atau sebaliknya. Atau lebih baik lagi, coba buat jendela di desktop aktif (SetThreadDesktop), dan jika berfungsi, tunjukkan UI Anda di atasnya. Jika tidak, maka itu adalah desktop yang dilindungi / khusus, jadi jangan.
eselk