65.9K
CodeProject 正在变化。 阅读更多。
Home

确定上次登录时间

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2021年6月8日

CPOL

3分钟阅读

viewsIcon

5892

downloadIcon

167

查找 Windows 10 上的登录时间

sample console output

引言

这是一个独立的实用程序,可以从系统读取上次登录时间,并显示自上次登录以来的时间。通过取消定义文件顶部的 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 日:初始版本
© . All rights reserved.