Kilitli Bir İş İstasyonunun Süresini Programlı Olarak Belirleyin?


111

Makinenin ne kadar süreyle kilitli kalacağı kod olarak nasıl belirlenebilir?

C # dışındaki diğer fikirlere de açığız.


Basitlik ve temizlik için Windows hizmet fikrini beğendim (ve kabul ettim), ancak maalesef bu özel durumda benim için işe yarayacağını düşünmüyorum. Bunu evden ziyade iş istasyonumda çalıştırmak istedim (ya da sanırım eve ek olarak), ancak DoD tarafından oldukça zor bir şekilde kilitlendi. Aslında kendi başıma oynamamın bir nedeni de bu.

Yine de yazacağım ve işe yarayıp yaramadığına bakacağım. Herkese teşekkürler!

Yanıtlar:


138

Bunu daha önce bulamadım, ancak herhangi bir uygulamadan bir SessionSwitchEventHandler'ı bağlayabilirsiniz. Açıkça uygulamanızın çalışıyor olması gerekecek, ancak çalıştığı sürece:

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 ve Windows 10 x64 üzerinde% 100 test edilmiştir.
Contango

Vay canına, harika çalışıyor! hata yok istisna yok, pürüzsüz ve temiz!
Mayer Spitzer

Bunu yapmanın doğru yolu bu. Göre bu Microsoft makalesinde , "Eğer iş istasyonu kilitli olup olmadığını belirlemek için çağırabilir hiçbir işlevi yoktur." SessionSwitchEventHandler kullanılarak izlenmelidir.
JonathanDavidArndt

35

Aşağıda gösterildiği gibi OnSessionChange olayını işleyen bir Windows Hizmeti (bir görsel stüdyo 2005 proje türü) oluşturacağım:

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

Bu noktada etkinliği ne ve nasıl günlüğe kaydedeceğiniz size bağlıdır, ancak bir Windows Hizmeti, kilitleme ve kilit açma olaylarının yanı sıra başlatma, kapatma, oturum açma / kapatma gibi Windows olaylarına hızlı ve kolay erişim sağlar.


18

Aşağıdaki çözüm, Win32 API kullanır. OnSessionLock, iş istasyonu kilitlendiğinde çağrılır ve OnSessionUnlock kilidi açıldığında çağrılır.

[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 olayının (diğer yanıtlardan) tetiklenmediğini fark ederseniz (örneğin, uygulamanız onu bastırırsa) bu iyi bir seçenektir.
kad81

Gelecekteki okuyucular için ... Buradaki geçersiz kılma System.Windows.Forms.Form'dan geliyor, sizin gibi bir sınıf yazabilirsiniz: public class Form1: System.Windows.Forms.Form
granadaCoder

Bu, SystemEvents.SessionSwitch çalışmadığında benim için çalışıyor
DCOPTimDowd

5

Bunun eski bir soru olduğunu biliyorum, ancak belirli bir oturum için Kilit Durumunu elde etmek için bir yöntem buldum.

Cevabımı burada buldum ama C ++ içindeydi, bu yüzden Kilit Durumunu elde etmek için elimden geldiğince C # 'a çevirdim.

İşte burada:

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

Not: Yukarıdaki kod çok daha büyük bir projeden çıkarıldı, bu yüzden biraz özür dilerim. Yukarıdaki kodu test edecek vaktim yok, ancak her şeyi kontrol etmek için bir veya iki hafta sonra tekrar gelmeyi planlıyorum. Şimdi gönderdim çünkü yapmayı unutmak istemedim.


Bu çalışır (şimdiye kadar Windows 7 test edilmiştir). Teşekkürler, son haftalardır bunu arıyorduk ve cevabınız kısa sürede geldi!
SteveP

1
Kodda birkaç hata var: 1. if (session_info_ex.Level != 1)- koşul doğruysa, bellek serbest kalmayacaktır. 2. eğer session_info_ex.Level! = 1 ise, bunu yapmamalısınız: Marshal.PtrToStructure<WTSINFOEX>(ppBuffer);çünkü döndürülen arabelleğin boyutu WTSINFOEX
SergeyT

(devamı) 3. Alanı eklemenize gerek yoktu, UInt32 Reserved;bunun yerine yapıyı WTSINFOEX_LEVEL1tamamen tanımlamalısınız . Bu durumda derleyici, yapı içindeki alanların doğru şekilde doldurulmasını (hizalamasını) yapacaktır. 4. İşlev WTSFreeMemoryExburada kötüye kullanılmaktadır. WTSFreeMemorybunun yerine kullanılmalıdır. WTSFreeMemoryExsonrasında hafızayı boşaltmak için tasarlanmıştır WTSEnumerateSessionsEx.
SergeyT

(sayılan) 5. CharSet = CharSet.AutoTüm özniteliklerde kullanılmalıdır.
SergeyT

4

Bu olayları "bulmak" için bir Windows hizmeti yazmakla ilgileniyorsanız, topshelf (Windows hizmetlerini yazmayı çok daha kolay hale getiren kitaplık / çerçeve) bir kancaya sahiptir.

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

ve şimdi üst raf hizmetini yukarıdaki arayüze / betona bağlamak için kod

Aşağıdaki her şey "tipik" üst raf kurulumudur .... olarak işaretlediğim 2 satır hariç

/ * BU MAGIC LINE * /

Bunlar, SessionChanged yönteminin ateşlenmesini sağlayan şeylerdir.

Bunu Windows 10 x64 ile test ettim. Makinemi kilitledim ve kilidini açtım ve istenen sonucu aldım.

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

Paketlerim.config sürümler hakkında ipuçları sağlamak için:

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

veya hizmet sınıfı örnek implicity uyguladıysanız ve oluşturmadıysanız x.EnableSessionChanged();, ServiceSessionChangearabirim uygulamasıyla birlikte kullanmak mümkündür ServiceControl. Beğen x.Service<ServiceImpl>();. Sen uygulamak zorunda ServiceSessionChangeiçinde ServiceImplsınıfında:class ServiceImpl : ServiceControl, ServiceSessionChange
Oleksa

3

NOT : Bu bir cevap değil, Timothy Carter'ın cevabına bir (katkı) çünkü itibarım şu ana kadar yorum yapmama izin vermiyor.

Birinin Timothy Carter'ın cevabındaki kodu denemesi ve bir Windows hizmetinde hemen çalışmasını sağlamaması truedurumunda, hizmetin kurucusunda ayarlanması gereken bir özellik vardır . Yapıcıya satırı eklemeniz yeterlidir:

CanHandleSessionChangeEvent = true;

Ve hizmet başlatıldıktan sonra bu özelliği ayarlamadığınızdan emin olun, aksi takdirde bir InvalidOperationExceptionatılır.


-3

Aşağıda, bilgisayarın kilitli olup olmadığını bulmak için% 100 çalışma kodu verilmiştir.

Bunu kullanmadan önce ad alanını kullanı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
Bunun yerine etkin masaüstü adını almak için OpenInputDesktop ve GetUserObjectInformation için MSDN'yi kontrol edin. Yukarıdaki kod, birden çok masaüstünde çalışan, Microsoft'un desktops.exe yardımcı programını veya başka bir şekilde kullanan kullanıcılar için güvenli / hoş değildir. Ya da daha iyisi, aktif masaüstünde (SetThreadDesktop) bir pencere oluşturmaya çalışın ve çalışıyorsa, üzerinde kullanıcı arayüzünüzü gösterin. Değilse, korumalı / özel bir masaüstü, bu yüzden yapma.
eselk
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.