以编程方式确定锁定工作站的持续时间? ?

如何用代码来确定机器被锁定了多长时间?

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


我喜欢窗口服务的想法(并且已经接受了它)的简单性和清洁性,但不幸的是,我不认为它将工作在这个特殊的情况下。我想在我的工作站上运行这个程序,而不是在家里(或者除了家里,我想) ,但是它被国防部锁定了。事实上,这也是我自己卷烟的原因之一。

无论如何我会写出来,看看是否有用。谢谢大家!

48932 次浏览

以下是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;
}

我将创建一个 Windows Service (一个可视化工作室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 服务提供了对诸如启动、关闭、登录/退出以及锁定和解锁事件等窗口事件的快速而简单的访问。

下面的解决方案使用 Win32API。当工作站锁定时调用 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 EventHandler。显然,您的应用程序需要运行,但只要运行:

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

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

我找到了我的答案 给你,但它是在 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;
}
}
}
}

Note: The above code was extracted from a much larger project so if i missed a bit sorry. I havn't got time to test the above code but plan to come back in a week or two to check everything. I only posted it now because i didn't want to forget to do it.

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

为了防止有人尝试了 Timothy Carter 的答案中的代码,但是没有马上在 Windows 服务中运行,在服务的构造函数中有一个属性需要设置为 true。 只需在构造函数中添加这一行:

CanHandleSessionChangeEvent = true;

And be sure not to set this property after the service is started otherwise an InvalidOperationException will be thrown.

If you're interested in writing a windows-service to "find" these events, topshelf (the library/framework that makes writing windows services much easier) has a hook.

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

现在我们来看一下将 topsheld 服务连接到上面的接口/混凝土的代码

下面的一切都是“典型的”顶架设置... 。除了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 */
});
});

My packages.config to provide hints about versions:

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

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

I realize that this is not a programmatic way. It is simpler than writing a service. It won't miss an event because your program happens to not be running at the time of lock/unlock transition.