确定上次登录时间





5.00/5 (6投票s)
查找 Windows 10 上的登录时间
引言
这是一个独立的实用程序,可以从系统读取上次登录时间,并显示自上次登录以来的时间。通过取消定义文件顶部的 STAND_ALONE
宏,并将两个非静态函数导出到另一个文件,也可以将其转换为可链接的模块。
背景
我有一个名为 DerBar 的系统状态监视器,它在我的计算机上运行,显示诸如可用/总内存、CPU 利用率等数据。它一直显示的数据之一是 Uptime,即自计算机上次重启以来的时间。
但是,自从我被迫使用 Windows 10 以来,这并没有完全传达我实际想知道的信息,因为 Windows 10 经常注销然后重新登录会话。 此注销/登录过程会导致我所有的应用程序在我登录时重新加载... 但是不会更新 Uptime 字段,我想是因为 Windows 不认为这是重启 - 但对我来说,这本质上是重启,我想显示自登录以来的时间,而不是自重启以来的时间。
但是,找到此信息(我最近一次登录的日期/时间)有点棘手... 本文将分享我用来获取此数据的技术。
我非常感谢 CodeProject 讨论论坛中的人们提供的解决这个问题所需的一些线索。 这是指向讨论线程的链接。
详细说明
寻找技术
搜索有关 Windows 的新信息通常涉及研究多个库和协议;很少通过与之前的部分相同的机制找到新的数据。 此外,我希望找到一个不需要以管理员身份运行该实用程序的源,如果可能的话。
我最初搜索此登录信息时找到了一个名为 NetUserGetInfo()
的函数,但遗憾的是,它显示的登录日期是 77 天前,而我知道我的上次登录实际上不到 1 天前。 幸运的是,一位 CodeProject 论坛用户告诉我,前面的函数实际上已经过时,他向我指出 LSA(登录安全授权)模块中的一组函数; 事实上,他给了我一个完整的运行程序,它给了我我想要的东西
INT main(void)
{
DWORD lc = 0;
DWORD status = 0;
PLUID list = nullptr;
LsaEnumerateLogonSessions(&lc, &list);
for (DWORD i = 0; i < lc; i++)
{
PSECURITY_LOGON_SESSION_DATA pData;
status = LsaGetLogonSessionData((PLUID)((INT_PTR)list + sizeof(LUID) * i), &pData);
if (0 == status)
{
if (Interactive == pData->LogonType)
{
FILETIME ft;
SYSTEMTIME st_utc, st_local;
TIME_ZONE_INFORMATION tzi;
ft.dwHighDateTime = pData->LogonTime.HighPart;
ft.dwLowDateTime = pData->LogonTime.LowPart;
GetTimeZoneInformation(&tzi);
FileTimeToSystemTime(&ft, &st_utc);
SystemTimeToTzSpecificLocalTime(&tzi, &st_utc, &st_local);
wprintf(L"UserName: %s\n", pData->UserName.Buffer);
wprintf(L"Last logon %s: %d/%d/%d-%d:%d:%d:%d\n", tzi.StandardName,
st_local.wMonth, st_local.wDay, st_local.wYear, st_local.wHour,
st_local.wMinute, st_local.wSecond, st_local.wMilliseconds);
}
LsaFreeReturnBuffer(pData);
}
}
return 0;
}
编译器异常
虽然他的实用程序运行完美,但我仍然遇到问题。 我使用 MinGW 工具链,而不是 Microsoft 工具,来构建我的所有应用程序。 事实证明,32 位版本的 MinGW LSA 标头 ntsecapi.h 不包含这些函数的声明。
经过进一步的在线研究,我找到了解决此问题的方法,该方法涉及在运行时加载相关的库 secur32.dll,然后使用 GetProcAddress()
将指针地址保存到所需的函数,如下所示(为简洁起见,省略了错误检查)
static bool load_LSA_library_pointers(void)
{
// these pointers are required for 32-buit MinGW builds
hSecur32 = LoadLibraryW(L"secur32.dll");
pfLELS = (pfLsaEnumerateLogonSessions)GetProcAddress
(hSecur32, "LsaEnumerateLogonSessions");
pfLGLSD = (pfLsaGetLogonSessionData)GetProcAddress(hSecur32, "LsaGetLogonSessionData");
pfLFRB = (pfLsaFreeReturnBuffer)GetProcAddress(hSecur32, "LsaFreeReturnBuffer");
return true;
}
有了这些函数指针,我就可以通过它们的指针来调用所需的函数
// LsaEnumerateLogonSessions(&lc, &list);
(*pfLELS)(&lc, &list); //lint !e534
for (DWORD i = 0; i < lc; i++) {
PSECURITY_LOGON_SESSION_DATA pData;
time_t logon_time ;
// status = LsaGetLogonSessionData((PLUID)((INT_PTR)list + sizeof(LUID) * i), &pData);
status = (*pfLGLSD)((PLUID)((INT_PTR)list + _
sizeof(LUID) * i), &pData); //lint !e732 !e737
if (0 == status) {
if (Interactive == pData->LogonType) { //lint !e641
logon_time = u64_to_timet(pData->LogonTime);
if (max_logon_time < logon_time) {
max_logon_time = logon_time ;
}
}
// LsaFreeReturnBuffer(pData);
(*pfLFRB)(pData);
}
}
最终细节,或者现在几点了??
最后一个琐碎的问题是,此操作将登录时间作为 64 位值给出,表示自 1601 年 1 月 1 日以来 100 纳秒间隔的数量。我从 time()
获取当前系统时间,它返回自 1970 年 1 月 1 日午夜以来的秒数。 我使用了以下函数,该函数在多个在线来源中找到,将登录时间转换为与本地时间相同的时区。
static time_t u64_to_timet(LARGE_INTEGER const& ull)
{
return ull.QuadPart / 10000000ULL - 11644473600ULL;
}
请注意,我没有包含任何将登录时间转换为当前时区的代码。 提供上述 LSA 函数的海报在他的帖子中讨论了此主题,稍后我将对此进行更多研究,但对于我当前的需求而言,这并不是非常重要。
历史
- 2021 年 6 月 8 日:初始版本