In this tip, you will find a stand-alone utility that can read last-logon time from the system and display time since last logon.
Introduction
This is a stand-alone utility which can read the last-logon time from the system, and display the time since the last logon. This can also be turned into a linkable module, by un-defining the STAND_ALONE
macro at the top of the file, and exporting the two non-static functions to another file.
Background
I have a system-status monitor called DerBar that I run on my computer, showing data such as free/total memory, CPU utilization, etc. One of the pieces of data that it has always shown is Uptime, which is how long since the computer was last rebooted.
Since I was forced onto Windows 10, however, this has not quite communicated the information that I actually want to know, because it is common with Windows 10 to log out and then log back in to a session. This logout/login procedure causes all of my applications get reloaded when I log in... but does not update the Uptime fields, I guess because Windows does not consider this a reboot - but for me, this is essentially a reboot, and I want to display the time since logon, not the time since reboot.
This information - the date/time when I most recently logged in - turned out to be a bit tricky to find out, though... This article will share the techniques that I used to get this data.
I am indebted to the folks in the CodeProject discussion forums for providing some of the clues that I needed to solve this problem. Here is a link to the discussion thread.
Details
Finding the Technique
Searching for new pieces of information about Windows, usually involves studying multiple libraries and protocols; it is rare that a new piece of data is found via the same mechanism as a previous piece. In addition, I wanted a source which did not require running the utility as Administrator, if at all possible.
My initial searches for this logon information led me to a function called NetUserGetInfo()
, but sadly that showed a logon date which was 77 days old, while I knew that my last logon was actually less than 1 day old. Fortunately, a CodeProject forum user informed me that this preceding function was in fact obsolete, and he pointed me to a set of functions in the LSA (Login Security Authority) module; in fact, he gave me a complete running program which gave me exactly what I wanted:
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;
}
Compiler Anomalies
Although his utility worked perfectly, I still had a problem. I use the MinGW toolchain, rather than Microsoft tools, to build all of my applications. It turns out that the 32-bit version of the MinGW LSA header ntsecapi.h does not contain the declarations for these functions.
After some further online research, I found a solution to this issue, which involved loading the related library secur32.dll at runtime, then using GetProcAddress()
to save pointers addresses to the required functions, as shown here (error-checking removed for brevity):
static bool load_LSA_library_pointers(void)
{
hSecur32 = LoadLibraryW(L"secur32.dll");
pfLELS = (pfLsaEnumerateLogonSessions)GetProcAddress
(hSecur32, "LsaEnumerateLogonSessions");
pfLGLSD = (pfLsaGetLogonSessionData)GetProcAddress(hSecur32, "LsaGetLogonSessionData");
pfLFRB = (pfLsaFreeReturnBuffer)GetProcAddress(hSecur32, "LsaFreeReturnBuffer");
return true;
}
With these function pointers available, I can then call the required functions, via their pointers:
(*pfLELS)(&lc, &list); for (DWORD i = 0; i < lc; i++) {
PSECURITY_LOGON_SESSION_DATA pData;
time_t logon_time ;
status = (*pfLGLSD)((PLUID)((INT_PTR)list + _
sizeof(LUID) * i), &pData); if (0 == status) {
if (Interactive == pData->LogonType) { logon_time = u64_to_timet(pData->LogonTime);
if (max_logon_time < logon_time) {
max_logon_time = logon_time ;
}
}
(*pfLFRB)(pData);
}
}
Final Details, or What Time Is It, Anyway??
One last trivial issue was, that this operation gives me the logon time as a 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601. I get current system time from time()
, which returns the number of seconds since midnight, January 1, 1970. I used the following function, found at several online sources, to convert the logon time to the same timeframe as local time.
static time_t u64_to_timet(LARGE_INTEGER const& ull)
{
return ull.QuadPart / 10000000ULL - 11644473600ULL;
}
Note that I didn't include any code to convert the logon time to my current time zone. The poster who provided the LSA function above, discussed this topic in his post, and I will do more research on this later on, but it wasn't really vital for my current requirement.
History
- 8th June, 2021: Initial version