잠긴 워크 스테이션의 기간을 프로그래밍 방식으로 결정합니까?


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
Windows 7 x64 및 Windows 10 x64에서 100 % 테스트되었습니다.
Contango

와우, 훌륭하게 작동합니다! 오류 없음 예외 없음, 부드럽고 깨끗합니다!
Mayer Spitzer

이것이 올바른 방법입니다. 이 Microsoft 문서 에 따르면 "워크 스테이션이 잠겨 있는지 확인하기 위해 호출 할 수있는 함수가 없습니다." SessionSwitchEventHandler를 사용하여 모니터링해야합니다.
JonathanDavidArndt

35

다음과 같이 OnSessionChange 이벤트를 처리하는 Windows 서비스 (Visual Studio 2005 프로젝트 유형)를 만듭니다.

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에서 온 것입니다. 다음과 같은 클래스를 작성할 수 있습니다. public class Form1 : System.Windows.Forms.Form
granadaCoder

이것은 SystemEvents.SessionSwitch 가 작동 하지 않을 때 작동 합니다
DCOPTimDowd

5

나는 이것이 오래된 질문이라는 것을 알고 있지만 주어진 세션에 대한 잠금 상태를 얻는 방법을 찾았습니다.

여기 에서 내 대답을 찾았 지만 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;
            }
        }
    }
}

참고 : 위의 코드는 훨씬 더 큰 프로젝트에서 추출되었으므로 조금 놓친 경우 죄송합니다. 위의 코드를 테스트 할 시간이 없지만 1 ~ 2 주 후에 모든 것을 확인하기 위해 돌아올 계획입니다. 잊고 싶지 않았기 때문에 지금 만 게시했습니다.


이것은 작동합니다 (지금까지 테스트 된 Windows 7). 감사합니다, 우리는 지난 몇 주 동안 이것을 찾고 있었으며 귀하의 답변은 시간이 지남에 따라 왔습니다!
SteveP

1
코드에 몇 가지 오류가 if (session_info_ex.Level != 1)있습니다. 1.-조건이 참이면 메모리가 해제되지 않습니다. 2. session_info_ex.Level! = 1이면이 작업을 수행해서는 안됩니다. Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);반환 된 버퍼의 크기가 WTSINFOEX의 크기와 다를 수 있기 때문입니다
SergeyT

(계속) 3. 필드를 추가 할 필요가 없었 습니다. UInt32 Reserved;대신 구조를 WTSINFOEX_LEVEL1완전히 정의해야합니다 . 이 경우 컴파일러는 구조 내에서 필드의 올바른 패딩 (정렬)을 수행합니다. 4. WTSFreeMemoryEx여기서 기능 이 잘못 사용되었습니다. WTSFreeMemory대신 사용해야합니다. WTSFreeMemoryEx이후에 메모리를 해제하기위한 것 WTSEnumerateSessionsEx입니다.
SergeyT

(계산 됨) 5. CharSet = CharSet.Auto모든 속성에 사용되어야합니다.
SergeyT

4

이러한 이벤트를 "찾기"하기 위해 Windows 서비스를 작성하는 데 관심이있는 경우 topshelf (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);
    }   
}

이제 topshelf 서비스를 위의 인터페이스 / 콘크리트에 연결하는 코드

아래의 모든 것은 "일반적인"topshelf 설정입니다.

/ * 이것은 마법 라인입니다 * /

이것이 바로 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 */
                });
            });                 

버전에 대한 힌트를 제공하는 내 packages.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>();. 당신은 구현해야 ServiceSessionChangeServiceImpl클래스 :class ServiceImpl : ServiceControl, ServiceSessionChange
oleksa

3

참고 : 이것은 대답이 아니라 Timothy Carter의 대답에 대한 (기여)입니다. 제 평판이 지금까지 언급 할 수 없기 때문입니다.

누군가 Timothy Carter의 답변에서 코드를 시도했지만 Windows 서비스에서 즉시 작동하지 않는 true경우 서비스 생성자에서 설정해야하는 속성이 하나 있습니다 . 생성자에 다음 줄을 추가하면됩니다.

CanHandleSessionChangeEvent = true;

그리고 서비스가 시작된 후에는이 속성을 설정하지 마십시오 InvalidOperationException. 그렇지 않으면 이 발생합니다.


-3

아래는 PC가 잠겨 있는지 여부를 확인하는 100 % 작동 코드입니다.

이것을 사용하기 전에 네임 스페이스를 사용하십시오 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
대신 활성 데스크톱 이름을 얻으려면 MSDN에서 OpenInputDesktop 및 GetUserObjectInformation을 확인하십시오. 위의 코드는 Microsoft의 desktops.exe 유틸리티를 사용하는 등 여러 데스크톱에서 작업하는 사용자에게 안전하지 않습니다. 또는 더 좋은 방법은 활성 데스크톱 (SetThreadDesktop)에 창을 만들고 작동하면 UI를 표시하는 것입니다. 그렇지 않은 경우 보호 / 특수 데스크톱이므로 그러지 마십시오.
eselk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.