Lập trình xác định khoảng thời gian của một máy trạm bị khóa?


111

Làm thế nào người ta có thể xác định, trong mã, máy bị khóa bao lâu?

Các ý tưởng khác ngoài C # cũng được hoan nghênh.


Tôi thích ý tưởng dịch vụ windows (và đã chấp nhận nó) vì sự đơn giản và sạch sẽ, nhưng tiếc là tôi không nghĩ rằng nó sẽ phù hợp với tôi trong trường hợp cụ thể này. Tôi muốn chạy điều này trên máy trạm của mình tại nơi làm việc hơn là ở nhà (hoặc ngoài nhà, tôi cho là vậy), nhưng nó bị khóa khá khó khăn do sự lịch sự của DoD. Đó là một phần lý do tôi thực sự tự mình lăn lộn.

Tôi sẽ viết nó lên và xem nó có hoạt động không. Cảm ơn mọi người!

Câu trả lời:


138

Tôi đã không tìm thấy điều này trước đây, nhưng từ bất kỳ ứng dụng nào, bạn có thể kết nối SessionSwitchEventHandler. Rõ ràng là ứng dụng của bạn sẽ cần phải chạy, nhưng miễn là nó:

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
Đã kiểm tra 100% trên Windows 7 x64 và Windows 10 x64.
Contango

Wow, hoạt động tuyệt vời! không có lỗi không có ngoại lệ, trơn tru và sạch sẽ!
Mayer Spitzer

Đây là cách làm đúng. Theo bài báo này của Microsoft , "Không có chức năng nào bạn có thể gọi để xác định xem máy trạm có bị khóa hay không." Nó phải được giám sát bằng SessionSwitchEventHandler.
JonathanDavidArndt

35

Tôi sẽ tạo một Windows Service (một loại dự án visual studio 2005) xử lý sự kiện OnSessionChange như được hiển thị bên dưới:

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

Bạn ghi nhật ký hoạt động vào thời điểm nào và như thế nào là tùy thuộc vào bạn, nhưng Dịch vụ Windows cung cấp khả năng truy cập nhanh chóng và dễ dàng vào các sự kiện của windows như khởi động, tắt máy, đăng nhập / đăng xuất cùng với các sự kiện khóa và mở khóa.


18

Giải pháp dưới đây sử dụng API Win32. OnSessionLock được gọi khi máy trạm bị khóa và OnSessionUnlock được gọi khi nó được mở khóa.

[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
Đây là một tùy chọn tốt nếu bạn thấy rằng sự kiện SessionSwitch (từ các câu trả lời khác) không kích hoạt (ví dụ: ứng dụng của bạn ngăn chặn nó).
kad81,

Đối với độc giả tương lai ... Tôi ~ ~ nghĩ ghi đè ở đây xuất phát từ System.Windows.Forms.Form, như trong bạn có thể viết một lớp học như thế này: public class Form1: System.Windows.Forms.Form
granadaCoder

Điều này hoạt động đối với tôi khi SystemEvents.SessionSwitch không
DCOPTimDowd

5

Tôi biết đây là một câu hỏi cũ nhưng tôi đã tìm thấy một phương pháp để có được Trạng thái khóa cho một phiên nhất định.

Tôi đã tìm thấy câu trả lời của mình ở đây nhưng nó bằng C ++ nên tôi đã dịch nhiều nhất có thể sang C # để có Trạng thái khóa.

Vì vậy, đây là:

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

Lưu ý: Đoạn mã trên được trích xuất từ ​​một dự án lớn hơn nhiều, vì vậy nếu tôi bỏ lỡ thì rất tiếc. Tôi không có thời gian để kiểm tra đoạn mã trên nhưng dự định quay lại sau một hoặc hai tuần để kiểm tra mọi thứ. Tôi chỉ đăng nó bây giờ vì tôi không muốn quên việc đó.


Điều này hoạt động (Windows 7 đã được thử nghiệm cho đến nay). Cảm ơn, chúng tôi đã tìm kiếm điều này trong vài tuần qua và câu trả lời của bạn đã đến sớm!
SteveP

1
Có một số lỗi trong mã: 1. if (session_info_ex.Level != 1)- nếu điều kiện là đúng, bộ nhớ sẽ không được giải phóng. 2. if session_info_ex.Level! = 1 bạn không nên làm điều này: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);vì kích thước của bộ đệm được trả về có thể khác với kích thước của WTSINFOEX
SergeyT 14/1017

(tiếp theo) 3. Không cần thiết phải thêm trường UInt32 Reserved;thay vào đó bạn nên xác định cấu trúc một cách WTSINFOEX_LEVEL1hoàn chỉnh. Trong trường hợp này, trình biên dịch sẽ thực hiện việc đệm chính xác (căn chỉnh) các trường bên trong cấu trúc. 4. Chức năng WTSFreeMemoryExđược sử dụng sai ở đây. WTSFreeMemoryphải được sử dụng thay thế. WTSFreeMemoryExnhằm mục đích giải phóng bộ nhớ sau WTSEnumerateSessionsEx.
SergeyT

(countinued) 5. CharSet = CharSet.Autophải được sử dụng trong tất cả các thuộc tính.
SergeyT

4

Nếu bạn quan tâm đến việc viết một windows-service để "tìm" những sự kiện này, thì tophelf (thư viện / khuôn khổ giúp việc viết các dịch vụ windows dễ dàng hơn nhiều) có một cái móc.

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

và bây giờ là mã để kết nối dịch vụ giá đỡ với giao diện / cụ thể ở trên

Mọi thứ bên dưới là thiết lập giá trên cùng "điển hình" .... ngoại trừ 2 dòng mà tôi đã đánh dấu là

/ * ĐÂY LÀ DÒNG MAGIC * /

Đó là những gì khiến phương thức SessionChanged kích hoạt.

Tôi đã thử nghiệm điều này với windows 10 x64. Tôi đã khóa và mở khóa máy của mình và tôi đã nhận được kết quả mong muốn.

            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 của tôi để cung cấp gợi ý về các phiên bản:

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

hoặc có thể sử dụng x.EnableSessionChanged();kết hợp với ServiceSessionChangetriển khai giao diện nếu bạn đã triển khai ServiceControlvà không tạo ra thể hiện lớp dịch vụ không đồng nhất. Thích x.Service<ServiceImpl>();. Bạn phải triển khai ServiceSessionChangetrong ServiceImpllớp:class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa

3

LƯU Ý : Đây không phải là một câu trả lời, mà là một (đóng góp) cho câu trả lời của Timothy Carter , vì danh tiếng của tôi không cho phép tôi bình luận cho đến nay.

Chỉ trong trường hợp ai đó đã thử mã từ câu trả lời của Timothy Carter và không làm cho nó hoạt động ngay lập tức trong dịch vụ Windows, có một thuộc tính cần được đặt truetrong hàm tạo của dịch vụ. Chỉ cần thêm dòng trong hàm tạo:

CanHandleSessionChangeEvent = true;

Và hãy chắc chắn không đặt thuộc tính này sau khi dịch vụ được bắt đầu, nếu không một InvalidOperationExceptionsẽ được ném.


-3

Dưới đây là mã hoạt động 100% để tìm xem PC có bị khóa hay không.

Trước khi sử dụng điều này, hãy sử dụng không gian tên 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
Kiểm tra MSDN cho OpenInputDesktop & GetUserObjectInformation, để thay thế tên màn hình hoạt động. Đoạn mã trên không an toàn / tốt cho người dùng làm việc trên nhiều máy tính để bàn, sử dụng tiện ích desktops.exe của Microsoft hoặc bằng cách khác. Hoặc tốt hơn, chỉ cần cố gắng tạo một cửa sổ trên màn hình đang hoạt động (SetThreadDesktop) và nếu nó hoạt động, hãy hiển thị giao diện người dùng của bạn trên đó. Nếu không, thì đó là một máy tính để bàn được bảo vệ / đặc biệt, vì vậy đừng.
eselk
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.