Enum login time of users on Windows machine to check for logged on user
In my previous posting ("Check for logged on user on Windows machine") I showed a WMI-based method to check, if a certain user was logged on. Andreas Häber then suggested in a comment, to use the Win32 API function NetUserEnum instead. Since WMI always sounds to me like a heavyweight technology, I liked the idea to resort to "simple" Win32 API calls to solve the problem. Below you find the result of my attempt wrapped in a VB.NET module.
Usage:
Call GetUsersLastLogOn() to retrieve an array of all users of the machine running the code. The array contains UserLastLogOn objects providing a username and the date/time of the user´s last logon (lastLoggedOn).
Dim users() As LogOnInfo.UserLastLogOn
users = LogOnInfo.GetUsersLastLogOn()
For Each user As LogOnInfo.UserLastLogOn In users
Console.WriteLine(user.username & ", " & user.lastLoggedOn)
Next
To solve the problem of a Windows service checking for logged on users you need to compare the logon time of the users to a timestamp (e.g. the start time of the Windows service process (call Process.GetCurrentProcess.StartTime())).
However, although a user might have logged on after the Windows service started, you never know, if he/she still is logged on. The last_logoff information in the USER_INFO_2 structure is not used by Windows.
That´s the main reason why I find this solution inferior to the one using WMI. Plus, it cost much more effort to code it (compiling information, translating the USER_INFO_2 structure to VB.NET, finding a way to convert UTC seconds to DateTime (easy, but eluded me for quite some time)).
How does it work:
NetUserEnum returns a list of USER_INFO_2 structures containing information about users of a certain machine. Passing nothing as the first parameter to NetUserEnum requests users from the local machine, passing 2 as the second parameter requests the info to be returned as USER_INFO_2 structures.
GetUsersLastLogOn() iterates over all USER_INFO_2 structures, extracts the user name and the last_logon time expressed in UTC seconds since 1/1/1970. The time is converted to a DateTime structure with a simple calculation: add the seconds to midnight of 1/1/1970. The extracted information is wrapped in UserLastLogOn objects and returned as an array.
Limitation:
NetUserEnum returns several USER_INFO_2 structures with each call. Calling it once on my system thus is enough. On machines with far more user accounts, though, it might be necessary to call the function several times. However, I did not test that scenario.
Code:
Imports System.Management
Imports System.Runtime.InteropServices
Module LogOnInfo
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Friend Structure USER_INFO_2
Public name As String
Public password As String
Public password_age As Integer
Public priv As Integer
Public home_dir As String
Public comment As String
Public flags As Integer
Public script_path As String
Public auth_flags As Integer
Public full_name As String
Public usr_comment As String
Public param As String
Public workstations As String
Public last_logon As Integer
Public last_logoff As Integer
Public acct_expires As Integer
Public max_storage As Integer
Public units_per_week As Integer
Public logon_hours As Byte
Public bad_pw_count As Integer
Public num_logons As Integer
Public logon_server As String
Public country_code As Integer
Public code_page As Integer
End Structure
Public Declare Function NetUserEnum Lib "Netapi32.dll" ( _
<MarshalAs(UnmanagedType.LPWStr)> ByVal servername As String, _
ByVal level As Integer, _
ByVal filter As Integer, _
ByRef bufptr As IntPtr, _
ByVal prefmaxlen As Integer, _
ByRef entriesread As Integer, _
ByRef totalentries As Integer, _
ByRef resume_handle As Integer) As Integer
Public Declare Function NetApiBufferFree Lib "Netapi32.dll" (ByVal buffer As IntPtr) As Integer
Public Class UserLastLogOn
Private _username As String
Private _lastLoggedOn As DateTime
Friend Sub New(ByVal ui As USER_INFO_2)
_username = ui.name
_lastLoggedOn = #1/1/1970#.AddSeconds(ui.last_logon)
End Sub
Public ReadOnly Property username() As String
Get
Return _username
End Get
End Property
Public ReadOnly Property lastLoggedOn() As DateTime
Get
Return _lastLoggedOn
End Get
End Property
End Class
Public Function GetUsersLastLogOn() As UserLastLogOn()
Dim users As New ArrayList
Dim entriesRead, totalEntries, hResume As Integer
Dim bufPtr As IntPtr
NetUserEnum(Nothing, 2, 2, bufPtr, -1, entriesRead, totalEntries, hResume)
If entriesRead > 0 Then
Dim iter As IntPtr = bufPtr
For i As Integer = 0 To entriesRead - 1
users.Add(New UserLastLogOn(Marshal.PtrToStructure(iter, GetType(USER_INFO_2))))
iter = New IntPtr(iter.ToInt32 + Marshal.SizeOf(GetType(USER_INFO_2)))
Next
End If
NetApiBufferFree(bufPtr)
Return users.ToArray(GetType(UserLastLogOn))
End Function
Public Function IsUserLoggedOn(ByVal userName As String) As Boolean
Dim mc As New ManagementClass("Win32_Process")
Dim moc As ManagementObjectCollection = mc.GetInstances
Dim mo As ManagementObject
For Each mo In moc
Dim p As New ROOT.CIMV2.Process(mo)
Dim processDomain, processUser As String
p.GetOwner(processDomain, processUser)
If processUser = userName Then
Return True
End If
Next
End Function
End Module
Resources:
-Documentation of NetUserEnum Win32 API function
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/netuserenum.asp
-Documentation of USER_INFO_2 structure:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/netmgmt/netmgmt/user_info_2_str.asp