How to tell what time a domain user logged in?
Solution 1
If you're looking for the interactive logon on a given client computer, the event long on that client computer is where you need to be looking. A user who logs on with, for example, cached credentials, won't be creating entries in the event log of a domain controller. Even if you're not looking for logons with cached credentials, configuring the client computers to audit logon events and looking in the client computer's event log is going to give you the best, most accurate, and easy to locate information.
Solution 2
If you know the machine your user is currently logged into, try psloggedon from the Sysinternals Suite.
Solution 3
There is an attribute on the AD User object called Last-Logon-Timestamp. It's updated every time a user logs in, but isn't replicated more than every 14 days as it is intended to be used to search for dead accounts. It can be used as a more accurate counter if you're willing to poll each DC in the domain for this information. From that you can build a trail of when users authenticated to the domain whenever it happens, not just in the morning.
Solution 4
Taking the first question from your title "How to tell what time a domain user logged in" depends on what platform the user is logging into. For Windows 2000/XP/2003 event id 528 with logon type 2 will show you interactive logons from a local or domain account. LogParser is a great tool to parse the event logs from a large number of machines and supports a large number of outputs. So for example you could use the following to query the security log on a remote machine and output to a tab seperated file:
c:>logparser.exe "select TimeGenerated, SID from \\wksname\Security where EventID = 528" -i EVT -resolveSIDs:ON -q:ON -headers:off -o:TSV >> c:\UserLogons.txt
Querying events from the Security logs on Windows Vista/2008/7 is slightly different in that the log file format has changed, as well as the event ids. Event id 4624 with logon type 2 will show successful interactive logins. We can use the wevtutil to query for similar data and output it in XML format:
c:>wevtutil qe Security /q:"*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and Task=12544 and (EventID=4624)] and EventData[Data[@Name='LogonType']='2']]" /e:Events > c:\UserLogons.xml
As for seeing event id 540 appear in your Security event logs on your domain controller:
Event 540 gets logged for a few different reasons. So for example you could see event id 540 with logon type 3 when a shared resource is accessed by the server service. Here are the logon types for this event id provided by Microsoft:
2 Interactive A user logged on to this computer at the console.
3 Network A user or computer logged on to this computer from the network.
4 Batch Batch logon type is used by batch servers, where processes might run on behalf of a user without the user's direct intervention.
5 Service A service was started by the Service Control Manager.
7 Unlock This workstation was unlocked.
8 NetworkCleartext A user logged on to a network. The user's password was passed to the authentication package in its unhashed form. The built-in authentication packages all hash credentials before sending them across the network. The credentials do not traverse the network in plaintext (also called cleartext).
9 NewCredentials A caller cloned its current token and specified new credentials for outbound connections. The new logon session has the same local identity, but it uses different credentials for other network connections.
10 RemoteInteractive A user logged on to this computer remotely using Terminal Services or a Remote Desktop connection.
11 CachedInteractive A user logged on to this computer with network credentials that were stored locally on the computer. The domain controller was not contacted to verify the credentials.
Happy hunting.
Solution 5
Apologies for the long post, but this is what I use. You could probably streamline this a bit:
' **********************************************************************
' AuditUsers
' ==========
'
' UserAccountControl
' SCRIPT 0x0001 1
' ACCOUNTDISABLE 0x0002 2
' HOMEDIR_REQUIRED 0x0008 8
' LOCKOUT 0x0010 16
' PASSWD_NOTREQD 0x0020 32
' PASSWD_CANT_CHANGE 0x0040 64
' ENCRYPTED_TEXT_PWD_ALLOWED 0x0080 128
' TEMP_DUPLICATE_ACCOUNT 0x0100 256
' NORMAL_ACCOUNT 0x0200 512
' INTERDOMAIN_TRUST_ACCOUNT 0x0800 2048
' WORKSTATION_TRUST_ACCOUNT 0x1000 4096
' SERVER_TRUST_ACCOUNT 0x2000 8192
' DONT_EXPIRE_PASSWORD 0x10000 65536
' MNS_LOGON_ACCOUNT 0x20000 131072
' SMARTCARD_REQUIRED 0x40000 262144
' TRUSTED_FOR_DELEGATION 0x80000 524288
' NOT_DELEGATED 0x100000 1048576
' USE_DES_KEY_ONLY 0x200000 2097152
' DONT_REQ_PREAUTH 0x400000 4194304
' PASSWORD_EXPIRED 0x800000 8388608
' TRUSTED_TO_AUTH_FOR_DELEGATION 0x1000000 16777216
'
' objUser.get("userAccountControl")
' **********************************************************************
option explicit
' *** Global constants
const HKEY_CLASSES_ROOT = &H80000000
const HKEY_CURRENT_USER = &H80000001
const HKEY_LOCAL_MACHINE = &H80000002
const HKEY_USERS = &H80000003
const HKEY_CURRENT_CONFIG = &H80000005
const REG_SZ = 1
const REG_EXPAND_SZ = 2
const REG_BINARY = 3
const REG_DWORD = 4
const REG_MULTI_SZ = 7
' *** User account status flags
const SCRIPT = &H0001
const ACCOUNTDISABLE = &H0002
const HOMEDIR_REQUIRED = &H0008
const LOCKOUT = &H0010
const PASSWD_NOTREQD = &H0020
const PASSWD_CANT_CHANGE = &H0040
const ENCRYPTED_TEXT_PWD_ALLOWED = &H0080
const TEMP_DUPLICATE_ACCOUNT = &H0100
const NORMAL_ACCOUNT = &H0200
const INTERDOMAIN_TRUST_ACCOUNT = &H0800
const WORKSTATION_TRUST_ACCOUNT = &H1000
const SERVER_TRUST_ACCOUNT = &H2000
const DONT_EXPIRE_PASSWORD = &H10000
const MNS_LOGON_ACCOUNT = &H20000
const SMARTCARD_REQUIRED = &H40000
const TRUSTED_FOR_DELEGATION = &H80000
const NOT_DELEGATED = &H100000
const USE_DES_KEY_ONLY = &H200000
const DONT_REQ_PREAUTH = &H400000
const PASSWORD_EXPIRED = &H800000
const TRUSTED_TO_AUTH_FOR_DELEGATION = &H1000000
dim wsh_shell, wsh_env, domain_name, server_name
dim initial_ou, computer, last_logon, i
dim users(4, 1000) ' 0 = username, 1 = display_name, 2 = is_disabled, 3 = lastlogon_date, 4 = group membership
dim num_users
const MAX_USERS = 1000
wscript.echo "Audit users started at " & formatdatetime(now(), 0)
' *** Get the domain name
set wsh_shell = Wscript.CreateObject("Wscript.Shell")
set wsh_env = wsh_shell.Environment("PROCESS")
domain_name = wsh_env("USERDNSDOMAIN")
server_name = wsh_env("COMPUTERNAME")
set wsh_env = nothing
set wsh_shell = nothing
' *** Open the Computers container
domain_name = split(domain_name, ".")
initial_ou = "LDAP://DC=" & domain_name(0)
for i = 1 to ubound(domain_name)
initial_ou = initial_ou & ",DC=" & domain_name(i)
next
wscript.echo "Checking domain " & initial_ou
' *** Find all users
set initial_ou = GetObject(initial_ou)
num_users = 0
FindAllUsers initial_ou
' *** Post the data
for i = 0 to num_users-1
wscript.echo users(0, i) & "," & users(1, i) & "," & users(2, i) & "," & users(3, i) & "," & users(4, i)
next
' *** All done
wscript.echo "Audit users finished at " & formatdatetime(now(), 0)
set initial_ou = nothing
wscript.quit 0
' **********************************************************************
' FindAllUsers
' ------------
' **********************************************************************
sub FindAllUsers(fau_OU)
dim ou_name, user, user_dn, display_name, lastlogon_date
dim ldap_user, group_array, i
ou_name = fau_OU.distinguishedName
' *** First list users in this OU
for each user in fau_OU
if lcase(user.class) = "user" then
user_dn = "LDAP://CN=" & user.displayName & "," & ou_name
' *** Check we haven't found too many users
if num_users >= MAX_USERS then
wscript.echo "WARNING: exceeded maximum number of users - " & cstr(MAX_USERS)
exit for
end if
' *** New user
users(0, num_users) = lcase(user.samAccountName)
' *** Get the display name; error trap this because it can fail
users(1, num_users) = ""
on error resume next
err = 0
display_name = user.get("displayName")
if err = 0 then users(1, num_users) = display_name
on error goto 0
' *** Get the enabled/disabled status
users(2, num_users) = user.get("UserAccountControl") and ACCOUNTDISABLE
if users(2, num_users) = 0 then
users(2, num_users) = "0"
else
users(2, num_users) = "1"
end if
' *** Get the last logon date; this may fail so trap errors
lastlogon_date = 0
on error resume next
set lastlogon_date = user.get("lastLogon")
if err = 0 then
if not isempty(lastlogon_date) then
lastlogon_date = LongTimeToDate(lastlogon_date)
if lastlogon_date < 0 then lastlogon_date = 0
end if
end if
on error goto 0
users(3, num_users) = formatdatetime(lastlogon_date, 0)
' *** Get the group membership
users(4, num_users) = ""
on error resume next
err = 0
set ldap_user = GetObject(user_dn)
if err = 0 then
on error goto 0
group_array = ldap_user.MemberOf
if not isempty(group_array) then
if TypeName(group_array) = "String" then
users(4, num_users) = group_array
else
for i = lbound(group_array) to ubound(group_array)
if users(4, num_users) <> "" then users(4, num_users) = users(4, num_users) & ";"
users(4, num_users) = users(4, num_users) & TrimGroupName(group_array(i))
next
end if
end if
set ldap_user = nothing
end if
on error goto 0
' *** Finished with this user
num_users = num_users + 1
end if
next
' *** Now recurse into subcontainers
for each user in fau_OU
if lcase(user.class) = "organizationalunit" or lcase(user.class) = "container" then
FindAllUsers user
end if
next
' *** All done
end sub
' **********************************************************************
' TrimGroupName
' -------------
' Turn the distinguished name into a simply group name
' **********************************************************************
function TrimGroupName(tgn_FullName)
dim group_name, len_group, c
TrimGroupName = ""
group_name = ""
len_group = len(tgn_FullName)
if len_group < 4 then exit function
for i = 4 to len_group
c = mid(tgn_FullName, i, 1)
if c = "," then exit for
group_name = group_name + c
next
group_name = lcase(group_name)
TrimGroupName = group_name
end function
' **********************************************************************
' LongTimeToDate
' --------------
' Convert the ADSI longint timestamp to a VBScript format date
' **********************************************************************
function LongTimeToDate(lt_Time)
dim ltdate
ltdate = lt_Time.HighPart * (2^32) + lt_Time.LowPart
ltdate = ltdate / (60 * 10000000)
ltdate = ltdate / 1440
ltdate = ltdate + #1/1/1601#
LongTimeToDate = ltdate
end function
JR
Related videos on Youtube
Comments
-
ℳ . almost 2 years
In the "Security" event log on a Windows 2003 domain controller, I see several entries of event 540 -- "Successful Network Logon" averaging a few minutes apart throughout the day.
Is the first one of these for a particular user on a particular day necessarily the time that the user logged in to their machine?
If not (or even if so) is there another (better?) way to tell what time the user logged in in the morning?
-
Spence about 15 yearsThis isn't necessarily going to be the time the user pressed <Ctrl>-<Alt>-<Del> and logged-on to their client computer. This is just when their client computer talked to a DC. For a wired client on a desk, that might be one in the same. For other machines, it might not.
-
Zypher about 15 yearsThere are only two levels in the security log "Success Audit" and "Failure Audit". Also many security certifications require you to audit these types of events (We have to for PCI, as well as individual client requirements) - I turned them on and let the security guy figure them out. Also I would add that you can get alot of good diagnostic info out of the sev2 events that precede the sev3 events.
-
ℳ . about 15 yearsThis is handy, and heaps quicker than trolling through the event logs. I can see myself using this a bit. But it only works for the current session...
-
Shaun Hess about 15 yearsHere is a link to the new event ids and what they mean in Windows Vista/2008/7 support.microsoft.com/kb/947226
-
SpaceManSpiff about 15 yearsThats an awsome idea, I'm going to go that today!
-
ansonl about 15 yearsI concur, IMHO that's the best/easiest way to capture the logon time of a user, without mistaking it for any other extraneous information.
-
ℳ . about 15 yearsI don't know, this seems kind of... dirty to me.
-
Tubs about 15 yearsIt is, but it works quite well. Sometimes infromation is so inaccesible or in such a format to make it more economical to do something else.