【问题标题】:Programmatically Determine a Duration of a Locked Workstation?以编程方式确定锁定工作站的持续时间?
【发布时间】:2010-09-07 21:10:06
【问题描述】:

如何在代码中确定机器锁定多长时间?

也欢迎 C# 之外的其他想法。


我喜欢 Windows 服务理念(并且已经接受它),因为它的简单性和清洁性,但不幸的是,我认为在这种特殊情况下它对我不起作用。我想在我的工作站上运行它而不是在家里(或者除了家之外,我想),但它被国防部非常严格地锁定了。实际上,这也是我自己推出的部分原因。

无论如何我都会写下来看看它是否有效。谢谢大家!

【问题讨论】:

    标签: c# windows


    【解决方案1】:

    我以前没有找到这个,但是你可以从任何应用程序中连接一个 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
        }
    }
    

    【讨论】:

    • 在 Windows 7 x64 和 Windows 10 x64 上测试 100%。
    • 哇,效果惊人!无错误无异常,流畅干净!
    • 这是正确的做法。根据this Microsoft article,“没有可以调用的函数来确定工作站是否被锁定。”必须使用 SessionSwitchEventHandler 对其进行监控。
    【解决方案2】:

    我将创建一个处理 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 事件,例如启动、关闭、登录/退出,以及锁定和解锁事件。

    【讨论】:

      【解决方案3】:

      以下解决方案使用 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);
      

      【讨论】:

      • 如果您发现 SessionSwitch 事件(来自其他答案)没有触发(例如您的应用程序抑制它),这是一个不错的选择。
      • 对于未来的读者...我~认为~这里的覆盖来自 System.Windows.Forms.Form,因为你可能会写一个这样的类: public class Form1 : System.Windows.Forms .Form
      • SystemEvents.SessionSwitch 不起作用时,这对我有用
      【解决方案4】:

      我知道这是一个老问题,但我找到了一种方法来获取给定会话的锁定状态。

      我找到了我的答案here,但它是用 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)。谢谢,过去几周我们一直在寻找这个问题,而您的答案来得正是时候!
      • 代码有几个错误: 1. if (session_info_ex.Level != 1) - 如果条件为真,内存不会被释放。 2. 如果 session_info_ex.Level != 1 你不应该这样做:Marshal.PtrToStructure&lt;WTSINFOEX&gt;(ppBuffer); 因为返回缓冲区的大小可能与 WTSINFOEX 的大小不同
      • (续) 3. 不必添加字段UInt32 Reserved;,而是应完全定义结构WTSINFOEX_LEVEL1。在这种情况下,编译器将对结构内的字段进行正确的填充(对齐)。 4.函数WTSFreeMemoryEx在这里被误用。 WTSFreeMemory 必须改为使用。 WTSFreeMemoryEx 用于在WTSEnumerateSessionsEx 之后释放内存。
      • (countinued) 5. CharSet = CharSet.Auto 必须在所有属性中使用。
      【解决方案5】:

      如果您有兴趣编写一个 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 setup....除了我标记为的 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 */
                      });
                  });                 
      

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

      【讨论】:

      • 或者如果你已经实现了ServiceControl并且不隐式创建服务类实例,则可以将x.EnableSessionChanged();ServiceSessionChange接口实现结合使用。喜欢x.Service&lt;ServiceImpl&gt;();。您必须在ServiceImpl 类中实现ServiceSessionChangeclass ServiceImpl : ServiceControl, ServiceSessionChange
      【解决方案6】:

      注意:这不是答案,而是对Timothy Carter 答案的(贡献),因为到目前为止我的声誉不允许我发表评论。

      以防万一有人尝试了 Timothy Carter 回答中的代码,但没有立即在 Windows 服务中运行,则需要在服务的构造函数中将一个属性设置为 true。 只需在构造函数中添加一行:

      CanHandleSessionChangeEvent = true;
      

      并且一定不要在服务启动后设置该属性,否则会抛出InvalidOperationException

      【讨论】:

        【解决方案7】:

        在 Windows 任务计划程序中,您可以创建触发 on workstation lockon workstation unlock 的任务。每个任务都可以将标志和时间戳写入文件,以说明工作站是否被锁定或解锁以及何时发生。

        我意识到这不是一种编程方式。它比编写服务更简单。它不会错过任何事件,因为您的程序恰好在锁定/解锁转换时没有运行。

        【讨论】:

          【解决方案8】:

          下面是 100% 工作代码,用于查找 PC 是否被锁定。

          在使用之前使用命名空间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;
          }
          

          【讨论】:

          • 检查 MSDN 以获取 OpenInputDesktop 和 GetUserObjectInformation,以获取活动桌面名称。对于使用 Microsoft 的 desktops.exe 实用程序或其他方式在多个桌面中工作的用户,上述代码并不安全/好用。或者更好的是,只需尝试在活动桌面 (SetThreadDesktop) 上创建一个窗口,如果可行,请在其上显示您的 UI。如果不是,那么它是一个受保护/特殊的桌面,所以不要。
          猜你喜欢
          • 2018-05-12
          • 2010-11-18
          • 2012-11-24
          • 2010-09-15
          • 2010-09-08
          • 2014-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多