7

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?

8 Answers 8

4

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.

1
  • I concur, IMHO that's the best/easiest way to capture the logon time of a user, without mistaking it for any other extraneous information. Jun 3, 2009 at 18:12
4

If you know the machine your user is currently logged into, try psloggedon from the Sysinternals Suite.

1
  • This 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...
    – ℳ  .
    Jun 3, 2009 at 4:57
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.

1
  • This 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. Jun 3, 2009 at 3:00
3

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.

1
2

You could, in a logon script, create a line that writes a time stamp to a file somewhere?

Something like?

net time >> \server\logonlogs\%username%.txt

3
  • Thats an awsome idea, I'm going to go that today! Jun 3, 2009 at 11:12
  • I don't know, this seems kind of... dirty to me.
    – ℳ  .
    Jun 4, 2009 at 3:56
  • It 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.
    – Tubs
    Jun 4, 2009 at 9:42
2

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

1

Not related to your question, but why not filter out those events, you shouldn't give a damn when you have those level of event returns, Errors with severity of 3 or higher man, don't waste your time!

1
  • There 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.
    – Zypher
    Jun 3, 2009 at 3:29
1

If you don't want to have to wade through your security logs, you could also try the account lockout and management tools (link). This adds an extra property page to account details in ADUC, including "last logon time".

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .