Monday, May 21, 2012

Logging user session info with VB Scripts.

Windows networks log a wealth of data for administrators both on Domain Controllers and on the local machine.  However it's really security logging, and it's not very friendly to search, nor is it centralised (it's spread over your domain controllers).

We wanted to create a way to log information about a user's session to a central logfile (by "user's session", I mean the time between logon and logoff) to aid in troubleshooting, and also to easily track what computers users are using and for how long.
While there are probably some good software packages out there that can do this, we thought it shouldn't be too hard to do with some VBScript snippets taken from around the internet.  And it wasn't either!

By running a script at logon and logoff (using Group Policy), we were able to gather a wealth of information (which was still really only scratching the surface of what is possible) about each users session which is then logged to a CSV file on a (hidden) network share*.

We are currently logging the following information to our CSV file:
  • Logon Date
  • Logon Time
  • Username
  • Domain
  • User CN
  • User Groups
  • Logon Server
  • IP Address
  • Comp. Name
  • Comp. Manufacturer
  • Comp. Model
  • OS Ver.
  • Service Pack Number
  • Logon Free Phys. Memory
  • Logon CPU Load %
  • CPU Model
  • Last Boot Time
  • Uptime
  • Logoff Date
  • Logoff Time
  • Session Duration (Secs)
  • Session Duration
  • Logoff Free Phys. Memory
  • Logoff CPU Load %
All this information allows us to do a number of things.  Our support techs are able to refer to the logging information to help troubleshoot problems reported by users.  The data allows us to generate real-world usage statistics, showing how much use our client PC's are getting, and the usage rate of certain users.  Also, it allows a very easy way to track down information on an individual user session.  Say a teacher wants to know when and where a student used computers in the school, and for how long (a frequent request when investigating problematic student behaviour), we can easily extract that information from the log and provide it to the requesting teacher.

To date, these scripts have produced information that has helped make decisions about the future of certain groups of computers (based on the usage rate), and identify some behavioural issues (some on request, and others that had been obviated by the data itself).

The scripts we're using are as follows (click the links for a neater version)**:  

Run at logon
(This script collects information as it's reported at the time of logon and writes it to a temporary file in the user's temp folder)


Dim vaSessionData(17)

'Get date and time.
vaSessionData(0)=Date
vaSessionData(1)=Time

'Get user and domain.
Dim objNet
On Error Resume Next

'In case we fail to create object then display our custom error

Set objNet = CreateObject("WScript.NetWork")
If  Err.Number <> 0 Then                'If error occured then display notice
    MsgBox "Don't be Shy." & vbCRLF &_
               "Do not press ""No"" If your browser warns you."
    Document.Location = "UserInfo.html"    
                                        'Place the Name of the document.
                                    'It will display again
End if
  
vaSessionData(2)=objNet.UserName
vaSessionData(3)=objNet.UserDomain
  
Set objNet = Nothing                    'Destroy the Object to free the Memory

'Get the users OU.
On Error Resume Next
Dim objSysInfo, objLuser
Set objSysInfo = CreateObject("ADSystemInfo")

Set objLuser = GetObject("LDAP://" & objSysInfo.UserName)

vaSessionData(4)="""" & objLuser.distinguishedName & """"

'Get the logged on users group memberships.
Dim objNetwork, strDomain, strUser, objUser, objGroup, strGroupMemberships

Set objNetwork = CreateObject("WScript.Network")
strDomain = objNetwork.UserDomain
strUser = objNetwork.UserName

Set objUser = GetObject("WinNT://" & strDomain & "/" & strUser)

For Each objGroup In objUser.Groups
    strGroupMemberships = strGroupMemberships & objGroup.Name & ","
Next

vaSessionData(5)="""" & strGroupMemberships & """"

'Get the logon server.
Set objWshShell = CreateObject("Wscript.Shell")
strLogonServer = objWshShell.ExpandEnvironmentStrings("%Logonserver%")
vaSessionData(6)=strLogonServer

'Get IP addresses from all DHCPd interfaces.
strQuery = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress > ''"

Set objWMIService = GetObject( "winmgmts://./root/CIMV2" )
Set colItems      = objWMIService.ExecQuery( strQuery, "WQL", 48 )

For Each objItem In colItems
    If IsArray( objItem.IPAddress ) Then
        If UBound( objItem.IPAddress ) = 0 Then
            strIP = "IP Address: " & objItem.IPAddress(0)
        Else
            strIP = Join( objItem.IPAddress, "," )
        End If
    End If
Next

vaSessionData(7)="""" & strIP & """"


'Get some useful local computer information
strComputer = "."
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_ComputerSystem")
For Each objComputer in colSettings
    vaSessionData(8)=objComputer.Name
    vaSessionData(9)=objComputer.Manufacturer
    vaSessionData(10)=objComputer.Model
Next
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colSettings
    vaSessionData(11)=objOperatingSystem.Version
    vaSessionData(12)=objOperatingSystem.ServicePackMajorVersion & "." & objOperatingSystem.ServicePackMinorVersion
    vaSessionData(13)=objOperatingSystem.FreePhysicalMemory
Next

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor")
For Each objItem in colItems
    vaSessionData(14)=objItem.LoadPercentage
    vaSessionData(15)="""" & objItem.Name & """"
Next

strComputer = "." ' Local computer

set objWMIDateTime = CreateObject("WbemScripting.SWbemDateTime")
set objWMI = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
set colOS = objWMI.InstancesOf("Win32_OperatingSystem")
for each objOS in colOS
    objWMIDateTime.Value = objOS.LastBootUpTime
    vaSessionData(16)=objWMIDateTime.GetVarDate
    vaSessionData(17)=TimeSpan(objWMIDateTime.GetVarDate,Now)
next

Function TimeSpan(dt1, dt2)
    ' Function to display the difference between
    ' 2 dates in hh:mm:ss format
    If (isDate(dt1) And IsDate(dt2)) = false Then
        TimeSpan = "00:00:00"
        Exit Function
        End If

        seconds = Abs(DateDiff("S", dt1, dt2))
        minutes = seconds \ 60
        hours = minutes \ 60
        minutes = minutes mod 60
        seconds = seconds mod 60

        if len(hours) = 1 then hours = "0" & hours

        TimeSpan = hours & ":" & _
            RIGHT("00" & minutes, 2) & ":" & _
            RIGHT("00" & seconds, 2)
End Function

JoinedArray=(join(vaSessionData,","))

'Write our to file in users temp dir.
Dim objFileSystem, objOutputFile, objOutputFile2
Dim strOutputFile
Dim strOutputFile2

Set objTemp = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2)
strOutputFile = objTemp & "\Session_Info.log"
strOutputFile2 = objTemp & "\Logon_Time.tmp"

Set objFileSystem = CreateObject("Scripting.fileSystemObject")
Set objOutputFile = objFileSystem.CreateTextFile(strOutputFile, TRUE)
Set objOutputFile2 = objFileSystem.CreateTextFile(strOutputFile2, TRUE)

objOutputFile.WriteLine (JoinedArray)
objOutputFile.Close

objOutputFile2.WriteLine (Date & " " & Time)
objOutputFile2.Close

Set objFileSystem = Nothing

'Wscript.Echo "DONE!"

WScript.Quit(0)



Run at Logoff
(This script collects additional information unique at the time of logoff, which is added to the temp file created at logon.  Some calculations are done about the total session length, then all the data is appended as one line to the central logfile, in CSV format)


Dim vaEndSessionData(5)

'Get date and time.
vaEndSessionData(0)=Date
vaEndSessionData(1)=Time
strTimeNow = Date & " " & Time

'Get time of logon and calculate difference
Set objTemp = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2)
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.OpenTextFile(objTemp & "\Logon_Time.tmp", 1)
strTimeThen = objFile.ReadLine
objFile.Close

intSecsDifferent=DateDiff("s", strTimeThen, strTimeNow)
vaEndSessionData(2)=intSecsDifferent

Function SplitSec(pNumSec)
  Dim d, h, m, s
  Dim h1, m1

  d = int(pNumSec/86400)
  h1 = pNumSec - (d * 86400)
  h = int(h1/3600)
  m1 = h1 - (h * 3600)
  m = int(m1/60)
  s = m1 - (m * 60)

  SplitSec = cStr(d) & "d " & cStr(h) & "h " & cStr(m) & "m " & cStr(s) & "s"
End Function

vaEndSessionData(3)=SplitSec(intSecsDifferent)

'Update some local computer information

strQuery = "SELECT * FROM Win32_NetworkAdapterConfiguration WHERE MACAddress > ''"

Set objWMIService = GetObject( "winmgmts://./root/CIMV2" )
Set colItems      = objWMIService.ExecQuery( strQuery, "WQL", 48 )

'Get some useful local computer information
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
    & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
Set colSettings = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colSettings
    vaEndSessionData(4)=objOperatingSystem.FreePhysicalMemory
Next

Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.ExecQuery("Select * from Win32_Processor")
For Each objItem in colItems
    vaEndSessionData(5)=objItem.LoadPercentage
Next

'write out to file and clean up
vJoinedArray=(join(vaEndSessionData,","))

Set objTemp = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2)
Set objFSOr = CreateObject("Scripting.FileSystemObject")
Set objFiled = objFSOr.OpenTextFile(objTemp & ".\Session_Info.log", 1)
strLogonInfo = objFiled.ReadLine
objFiled.Close

'Wscript.Echo strLogonInfo & "," & vJoinedArray
strSessionInfo = strLogonInfo & "," & vJoinedArray

Dim objFileSystem, objOutputFile
Dim strOutputFile

strOutputFile = "\\server.domain\share$\logfile.log"

Set objFileSystem = CreateObject("Scripting.fileSystemObject")
Set objOutputFile = objFileSystem.OpenTextFile(strOutputFile, 8)

objOutputFile.WriteLine (strSessionInfo)
objOutputFile.Close

Set objFileSystem = Nothing

Set objTemp = WScript.CreateObject("Scripting.FileSystemObject").GetSpecialFolder(2)
Set objFSO = CreateObject("Scripting.FileSystemObject")
objFSO.DeleteFile(objTemp & "\Session_Info.log")
objFSO.DeleteFile(objTemp & "\Logon_Time.tmp")

WScript.Quit(0)



Finally, the logfile can be searched using a simple text editor, or preferably, dropped into Excel and searched, manipulated and otherwise finessed to draw out the sort of great information that hides in any raw data.

Enjoy!

* This may not be the most secure method of creating the central logfile, some NTFS permissions can be put in place to prevent users accessing the logfile, but ultimately you can never completely secure the share because the user has to be able to write to the file.

** I'll be the first to admit the scipt is ugly.  I'm sure people with more scripting skills than me can suggest ways to clean it up.  If so, go right ahead.

No comments: