โดยทางโปรแกรมกำหนดระยะเวลาของเวิร์กสเตชันที่ถูกล็อกหรือไม่?


111

เราจะทราบได้อย่างไรว่าเครื่องถูกล็อคในรหัสนานแค่ไหน?

นอกจากนี้ยังยินดีรับแนวคิดอื่น ๆ นอกเหนือจาก C #


ฉันชอบแนวคิดการบริการของ windows (และยอมรับแล้ว) สำหรับความเรียบง่ายและความสะอาด แต่น่าเสียดายที่ฉันไม่คิดว่ามันจะได้ผลสำหรับฉันในกรณีนี้ ฉันต้องการเรียกใช้สิ่งนี้บนเวิร์กสเตชันของฉันในที่ทำงานมากกว่าที่บ้าน (หรือนอกเหนือจากที่บ้านฉันคิดว่า) แต่มันถูกล็อคไว้ค่อนข้างยากที่จะได้รับความอนุเคราะห์จาก DoD นั่นเป็นส่วนหนึ่งของเหตุผลที่ฉันหมุนตัวเองจริงๆ

ฉันจะเขียนมันต่อไปและดูว่ามันใช้ได้ไหม ขอบคุณทุกคน!

คำตอบ:


138

ฉันไม่พบสิ่งนี้มาก่อน แต่จากแอปพลิเคชันใด ๆ คุณสามารถเชื่อมต่อ SessionSwitchEventHandler ได้ เห็นได้ชัดว่าแอปพลิเคชันของคุณจะต้องทำงาน แต่ตราบใดที่:

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
ผ่านการทดสอบ 100% บน Windows 7 x64 และ Windows 10 x64
Contango

ว้าวทำงานได้ดีมาก! ไม่มีข้อผิดพลาดไม่มีข้อยกเว้นราบรื่นและสะอาด!
Mayer Spitzer

นี่เป็นวิธีที่เหมาะสมที่จะทำ ตามบทความของ Microsoft นี้ "ไม่มีฟังก์ชันที่คุณสามารถเรียกใช้เพื่อตรวจสอบว่าเวิร์กสเตชันถูกล็อกหรือไม่" ต้องถูกมอนิเตอร์โดยใช้ SessionSwitchEventHandler
JonathanDavidArndt

35

ฉันจะสร้าง Windows Service (ประเภทโครงการ Visual Studio 2005) ที่จัดการเหตุการณ์ OnSessionChange ดังที่แสดงด้านล่าง:

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

คุณบันทึกกิจกรรม ณ จุดนั้นอย่างไรและอย่างไรนั้นขึ้นอยู่กับคุณ แต่บริการ Windows ให้การเข้าถึงเหตุการณ์ของ Windows ที่รวดเร็วและง่ายดายเช่นการเริ่มต้นการปิดระบบการเข้าสู่ระบบ / ออกพร้อมกับเหตุการณ์การล็อกและปลดล็อก


18

โซลูชันด้านล่างใช้ Win32 API OnSessionLock ถูกเรียกเมื่อเวิร์กสเตชันถูกล็อกและ OnSessionUnlock จะถูกเรียกเมื่อปลดล็อก

[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
นี่เป็นตัวเลือกที่ดีหากคุณพบว่าเหตุการณ์ SessionSwitch (จากคำตอบอื่น ๆ ) ไม่เริ่มทำงาน (เช่นแอปพลิเคชันของคุณหยุดทำงาน)
kad81

สำหรับผู้อ่านในอนาคต ... ฉัน ~ คิดว่าการแทนที่ที่นี่มาจาก System.Windows.Forms.Form เช่นเดียวกับในคุณอาจเขียนคลาสเช่นนี้: คลาสสาธารณะ Form1: System.Windows.Forms.Form
granadaCoder

สิ่งนี้ใช้ได้กับฉันเมื่อSystemEvents.SessionSwitchไม่
DCOPTimDowd

5

ฉันรู้ว่านี่เป็นคำถามเก่า แต่ฉันพบวิธีการรับ Lock State สำหรับเซสชันหนึ่ง ๆ

ฉันพบคำตอบของฉันที่นี่แต่เป็นภาษา C ++ ดังนั้นฉันจึงแปลเป็น C # ให้มากที่สุดเพื่อรับสถานะล็อค

ต่อไปนี้:

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

หมายเหตุ: โค้ดด้านบนดึงมาจากโปรเจ็กต์ที่ใหญ่กว่ามากดังนั้นหากพลาดไปก็ขออภัยด้วย ฉันไม่มีเวลาทดสอบโค้ดด้านบน แต่วางแผนที่จะกลับมาในอีกหนึ่งหรือสองสัปดาห์เพื่อตรวจสอบทุกอย่าง ฉันโพสต์ตอนนี้เท่านั้นเพราะฉันไม่อยากลืมที่จะทำ


สิ่งนี้ใช้งานได้ (ทดสอบ windows 7 แล้ว) ขอบคุณเรากำลังมองหาสิ่งนี้ในช่วงสองสามสัปดาห์ที่ผ่านมาและคำตอบของคุณก็มาถึงในเวลาอันรวดเร็ว!
SteveP

1
มีข้อผิดพลาดเล็กน้อยในรหัส: 1. if (session_info_ex.Level != 1)- หากเงื่อนไขเป็นจริงหน่วยความจำจะไม่ถูกปลดปล่อย 2. ถ้า session_info_ex.Level! = 1 คุณไม่ควรทำเช่นนี้Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);เพราะขนาดของบัฟเฟอร์ที่ส่งคืนอาจแตกต่างจากขนาดของ WTSINFOEX
SergeyT

(ต่อ) 3. ไม่จำเป็นต้องเพิ่มฟิลด์UInt32 Reserved;แทน แต่คุณควรกำหนดโครงสร้างให้WTSINFOEX_LEVEL1เสร็จสมบูรณ์ ในกรณีนี้คอมไพเลอร์จะทำการเติมช่องว่างภายในโครงสร้างให้ถูกต้อง 4. WTSFreeMemoryExมีการใช้ฟังก์ชันในทางที่ผิด WTSFreeMemoryต้องใช้แทน WTSFreeMemoryExมีวัตถุประสงค์เพื่อเพิ่มหน่วยความจำหลังจากWTSEnumerateSessionsExนั้น
SergeyT

(countinued) 5. CharSet = CharSet.Autoต้องใช้ในแอตทริบิวต์ทั้งหมด
SergeyT

4

หากคุณสนใจที่จะเขียนบริการ windows เพื่อ "ค้นหา" เหตุการณ์เหล่านี้ชั้นบนสุด (ไลบรารี / เฟรมเวิร์กที่ช่วยให้การเขียนบริการ windows ง่ายขึ้นมาก) มีเบ็ด

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

และตอนนี้รหัสเพื่อเชื่อมต่อบริการชั้นบนสุดเข้ากับอินเทอร์เฟซ / คอนกรีตด้านบน

ทุกอย่างด้านล่างนี้เป็นการตั้งค่าชั้นบนสุด "ทั่วไป" .... ยกเว้น 2 บรรทัดที่ฉันทำเครื่องหมายไว้

/ * นี่คือสายเวทย์มนตร์ * /

นี่คือสิ่งที่ทำให้เมธอด SessionChanged เริ่มทำงาน

ฉันทดสอบกับ windows 10 x64 ฉันล็อกและปลดล็อกเครื่องและได้ผลลัพธ์ที่ต้องการ

            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 ของฉันเพื่อให้คำแนะนำเกี่ยวกับเวอร์ชัน:

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

หรือเป็นไปได้ที่จะใช้x.EnableSessionChanged();ร่วมกับServiceSessionChangeการใช้งานอินเทอร์เฟซหากคุณใช้งานServiceControlและไม่ได้สร้างความไม่สมบูรณ์ของอินสแตนซ์คลาสบริการ ชอบx.Service<ServiceImpl>();. คุณต้องนำไปใช้ServiceSessionChangeในServiceImplชั้นเรียน:class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa

3

หมายเหตุ : นี่ไม่ใช่คำตอบ แต่เป็น (การมีส่วนร่วม) สำหรับคำตอบของTimothy Carterเพราะชื่อเสียงของฉันไม่อนุญาตให้ฉันแสดงความคิดเห็นจนถึงตอนนี้

ในกรณีที่มีคนลองใช้รหัสจากคำตอบของ Timothy Carter และไม่สามารถใช้งานได้ทันทีในบริการ Windows มีคุณสมบัติอย่างหนึ่งที่ต้องตั้งค่าเป็นtrueตัวสร้างของบริการ เพียงแค่เพิ่มบรรทัดในตัวสร้าง:

CanHandleSessionChangeEvent = true;

และอย่าตั้งค่าคุณสมบัตินี้หลังจากเริ่มบริการมิฉะนั้นInvalidOperationExceptionจะถูกโยนทิ้ง


-3

ด้านล่างนี้คือรหัสการทำงาน 100% เพื่อดูว่าพีซีถูกล็อคหรือไม่

ก่อนที่จะใช้การใช้งานนี้การ System.Runtime.InteropServicesnamespace

[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
ตรวจสอบ MSDN สำหรับ OpenInputDesktop & GetUserObjectInformation เพื่อรับชื่อเดสก์ท็อปที่ใช้งานอยู่แทน รหัสด้านบนไม่ปลอดภัย / เหมาะสำหรับผู้ใช้ที่ทำงานในเดสก์ท็อปหลายเครื่องโดยใช้ยูทิลิตี้ desktops.exe จาก Microsoft หรืออย่างอื่น หรือดีกว่านั้นเพียงลองสร้างหน้าต่างบนเดสก์ท็อปที่ใช้งานอยู่ (SetThreadDesktop) และหากใช้งานได้ให้แสดง UI ของคุณ ถ้าไม่ใช่แสดงว่าเป็นเดสก์ท็อปที่มีการป้องกัน / พิเศษดังนั้นอย่าทำ
eselk
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.