PowerShell version 1 script to parse a log file that documents logon and logoff events. From the log file the script outputs user sessions. Each session is defined by the name of the computer and the name of the user, so a user can have more than one session at a time on different computers. The script outputs the session (computername\username), the logon datetime, the logoff datetime, and the duration of the logon session in days.hours:minutes:seconds. The log file is created by logon and logoff scripts configured in Group Policy. Each of these scripts appends a line to a shared log file. The logon script can be as simple as the following batch file:
Windows Shell Script
Edit|Remove
@echooffechoLogon,%date%,%time%,%computername%,%username%>> \\Server\Share\Events.log
The logoff script can be similar to the following batch file:
Windows Shell Script
Edit|Remove
@echooffechoLogoff,%date%,%time%,%computername%,%username%>> \\Server\Share\Events.log
This PowerShell script assumes that the fields in the resulting log file are comma delimited. There can be more than 5 fields, but the first 5 should be:
"logon" or "logoff",date,time,computer name, user name
The script will add a header line to the beginning of the log file if there is no header line, so there is no need for you to do this. The script needs write access to the log file to add the header line. For example, two lines of the log file could be similar to:

   Logon,Mon 11/23/2015,14:34:57.66,WKSTA03,jsmith
   Logoff,Wed 11/25/2015, 9:21:44.60,WKSTA03,jsmith
The output from the script for these two events would be similar to:
   WKSTA03\jsmith,11/23/2015 14:34:57,11/25/2015 09:21:44,1.18:46:47
This script accepts a log file name (and optional path) as a parameter, or the script will prompt for the log file name. The script output is displayed at the console in comma delimited format. The output can be redirected to a text file, which can then be opened in Excel for analysis. Errors and warning messages are written to the console, but will not be redirected to the text file. The command to run this script at a PowerShell prompt could be similar to:
.\ParseLogons.ps1 Events.log > .\Output.txt
The logon sessions would be redirected to the file Output.txt. If there are error messages or warnings, they are displayed at the console, but are not redirected to the file. If there are no errors that abort the script, the script outputs information similar to the following at the console:
Windows Shell Script
Edit|Remove
ParseLogons.ps1 
Date:11/25/201514:32:56Log File: Events.log 
Totals: 
  Lines read in the log file:1,207 
  Bad lines skipped:0 
  Warnings:0 
  Sessions with no logoff:1 
  Sessions with no logon:3 
  Sessions still logged on:4 
  Total Sessions:983
"Sessions with no logoff" means that a user logged onto a computer, then later logged on again without an intervening logoff event on the computer. Either the computer crashed or the user logged off when the computer was disconnected from the network, so the logoff script could not append an event to the log file. "Sessions with no logon" means the log file has a logoff event, but the corresponding logon event is missing. Either the logon script could not append the logon event to the log file, or the logon occurred before the logon script was implemented. "Sessions still logged on" means the user was still logged on when the script ran, or the logoff event was not found in the log file. Lines in the log file are skipped if any fields are missing or the date and time raise an error when converted into a datetime value.
Warning messages are displayed at the console for the following conditions:
The following errors cause the script to abort:
Comments in the script document where accomodations had to be made to support PowerShell versions 1 and 2, as well as the .NET Framework classes that are not suppported on Windows RT 8.1.
PowerShell
Edit|Remove
# ParseLogons.ps1 
# PowerShell version 1 script to parse a log file that documents logon and logoff 
# events. From the log file the script outputs user sessions. Each session is 
# defined by the name of the computer and the name of the user, so a user can have 
# more than one session at a time on different computers. The script outputs the 
# session (computername\username), the logon datetime, the logoff datetime, and the 
# duration of the logon session in days.hours:minutes:seconds. 
# The log file is created by logon and logoff scripts configured in Group Policy. 
# Each of these scripts appends a line to a shared log file. The logon script can be 
# as simple as the following batch file: 
# 
#    @echo off 
#    echo Logon,%date%,%time%,%computername%,%username%>> \\Server\Share\Events.log 
# 
# The logoff script can be similar to the following batch file: 
# 
#    @echo off 
#    echo Logoff,%date%,%time%,%computername%,%username%>> \\Server\Share\Events.log 
# 
# This PowerShell script assumes that the fields in the resulting log file are comma 
# delimited. There can be more than 5 fields, but the first 5 should be: 
# "logon" or "logoff",date,time,computer name, user name 
# The script will add a header line to the beginning of the log file if there 
# is no header line, so there is no need for you to do this. The script needs write 
# access to the log file to add the header line. 
# For example, two lines of the log file could be similar to: 
# 
#    Logon,Mon 11/23/2015,14:34:57.66,WKSTA03,jsmith 
#    Logoff,Wed 11/25/20159:21:44.60,WKSTA03,jsmith 
# 
# The output from the script for these two events would be similar to: 
# 
#    WKSTA03\jsmith,11/23/2015 14:34:57,11/25/2015 09:21:44,1.18:46:47 
# 
# This script accepts a log file name (and optional path) as a parameter, or the 
# script will prompt for the log file name. The script output is displayed at 
# the console in comma delimited format. The output can be redirected to a text file, 
# which can then be opened in Excel for analysis. Errors and warning messages are 
# written to the console, but will not be redirected to a text file. 
 
# Author: Richard L. Mueller 
# Version 1.0 - December 62015 
 
Trap 
{ 
    If (("$_".StartsWith("Cannot convert value")) ` 
        -or ("$_".StartsWith("A parameter cannot be found that matches parameter name"))) 
    { 
        # Datetime not recognized by the Get-Date cmdlet. 
        # The first error message is generated by PowerShell V2, the second by PowerShell V1. 
        $Err = $_.ToString() 
        Write-Host "$Err. Line skipped" ` 
            -ForegroundColor red -BackgroundColor black 
        # This line is skipped but the script can continue reading events. 
        $Script:Skip = $True 
        $Script:NumBadLines = $Script:NumBadLines + 1 
        Continue 
    } 
} 
 
Function ReadLog($Log, $Start, $Count) 
{ 
    # Function to read each line of the log file and parse for sessions. 
    # When this function is called, we know the file has a header line and at least one 
    # event. A function is used because if an error is raised, especially retrieving the 
    # datetime from the Date and Time fields of the log file, the script will immediately 
    # exit whatever loop, function, For, or ForEach the script was in when the error was 
    # raised. We want to be able to skip the line with the bad date and/or time, and begin 
    # again on the next line of the log file. The variable $Script:Skip indicates if a bad 
    # line was skipped, and $Script:SkipLine indicates which line was skipped. This allows us 
    # to call the function again after the error and read the remaining lines of the log file. 
    # In this case, we only call the function a maximum of 3 times, so we continue if there 
    # are no more than 2 bad dates in the log file. Any more and we abort the script. 
 
    For ($j = $Start; $j -le ($Count - 1); $j = $j + 1) 
    { 
        $Script:SkipLine = $j 
        $Script:Skip = $False 
        # If there is only one event in the log file, then $Log is not an array. 
        If ($Count -eq 1{$Line = $Log} 
        Else {$Line = $Log[$j]} 
        $Event = $Line.Event 
        $Date = $Line.Date 
        $Time = $Line.Time 
        $Computer = $Line.Computer 
        $User = $Line.User 
        $Script:NumLines = $Script:NumLines + 1 
        # If Get-Date raises an error, the Trap above sets $Skip to $True and this function 
        # is aborted. 
        $CurrentTime  = Get-Date("$Date $Time") -ErrorAction SilentlyContinue 
        # Check that all fields are present in the line. 
        If ((-Not $Event) -Or (-Not $Date) -Or (-Not $Time) -Or (-Not $Computer) ` 
            -Or (-Not $User)) 
        { 
            # Line of log file not recognized. 
            Write-Host "Line skipped (not recognized): $Line" ` 
                -ForegroundColor red -BackgroundColor black 
            $Script:NumBadLines = $Script:NumBadLines + 1 
            If ($Script:NumBadLines -gt 5) 
            { 
                Write-Host "6 lines of the log file not recognized. Program aborted." ` 
                    -ForegroundColor red -BackgroundColor black 
                $Script:Stop = $True 
                Break 
            } 
        } 
        Else 
        { 
            # We know these fields are not Null, so we can Trim. 
            $Event = $Event.Trim() 
            $Date = $Date.Trim() 
            $Time = $Time.Trim() 
            $Computer = $Computer.Trim() 
            $User = $User.Trim() 
            $Session = "$Computer\$User" 
            # Check if this is a logon or logoff event. 
            If ($Event.ToLower() -eq "logon") 
            { 
                # Check if the last event for this session was a logon. 
                If ($LogonSessions.ContainsKey($Session)) 
                { 
                    # Logoff event missing for previous logon event. 
                    # Computer may have crashed or the user logged off when the log file 
                    # was unavailable or could not be reached. 
                    $PreviousTime = $LogonSessions[$Session] 
                    "$Session,$PreviousTime,(unknown),(unknown)" 
                    $Script:NumSessions = $Script:NumSessions + 1 
                    $Script:NumNoLogoffs = $Script:NumNoLogoffs + 1 
                    # Remove previous session from the hash table. 
                    # Otherwise, we will have a duplicate session. 
                    $LogonSessions.Remove($Session) 
                } 
                # Add this session to the hash table. 
                $LogonSessions.Add($Session, $CurrentTime) 
            } 
            If ($Event.ToLower() -eq "logoff") 
            { 
                # Check if the last event for this session was a logon. 
                If ($LogonSessions.ContainsKey($Session)) 
                { 
                    # Calculate timespan user was logged on. 
                    $PreviousTime = $LogonSessions[$Session] 
                    $TS = [Timespan]($CurrentTime - $PreviousTime) 
                    $Days = $TS.Days 
                    # Format hours, minutes, and seconds as two digit numbers with 
                    # no fractional seconds. 
                    $Hrs = $("0" + $TS.Hours).ToString() 
                    If ($Hrs.Length -eq 3{$Hrs = $Hrs.Substring(1)} 
                    $Mins = $("0" + $TS.Minutes).ToString() 
                    If ($Mins.Length -eq 3{$Mins = $Mins.Substring(1)} 
                    # Round the seconds plus milliseconds to the nearest second. 
                    # [Math]::Truncate and [Math]::Round are not supported on Windows RT 8.1. 
                    $Secs = "0" + ($TS.Seconds + ($TS.Milliseconds/1000) + 0.5).ToString() 
                    $Dot = $Secs.IndexOf(".") 
                    If ($Dot -gt 0{$Secs = $Secs.SubString(0, $Dot)} 
                    If ($Secs.Length -eq 3{$Secs = $Secs.SubString(1)} 
                    # The ":" character does not work in PowerShell V1, even if escaped. 
                    $Duration = $("$Days.$Hrs/$Mins/$Secs").Replace("/"":") 
                    "$Session,$PreviousTime,$CurrentTime,$Duration" 
                    $Script:NumSessions = $Script:NumSessions + 1 
                    # Remove this session from the hash table. 
                    $LogonSessions.Remove($Session) 
                } 
                Else 
                { 
                    # Previous logon event missing. 
                    "$Session,(unknown),$CurrentTime,(unknown)" 
                    $Script:NumSessions = $Script:NumSessions + 1 
                    $Script:NumNoLogons = $Script:NumNoLogons + 1 
                } 
            } 
            If (($Event.ToLower() -ne "logon") -and ($Event.ToLower() -ne "logoff")) 
            { 
                Write-Host "Event in line $Line of log file not recognized. Line Skipped" ` 
                    -ForegroundColor red -BackgroundColor black 
                $Script:NumBadLines = $Script:NumBadLines + 1 
                If ($Script:NumBadLines -gt 5) 
                { 
                    Write-Host "6 lines of the log file not recognized. Program aborted." ` 
                        -ForegroundColor red -BackgroundColor black 
                    $Script:Stop = $True 
                    Break 
                } 
            } 
        } 
    } 
} 
 
# Initialize counters. 
$NumLines = 0 
$NumSessions = 0 
$NumWarnings = 0 
$NumBadLines = 0 
$NumNoLogoffs = 0 
$NumNoLogons = 0 
$NumStillLoggedOn = 0 
 
# Check for parameter identifying the log file. 
If ($Args.Count -eq 1{ 
    $FileName = $Args[0} 
Else 
{ 
    # Prompt for the log file. 
    $FileName = Read-Host "Enter log file to be processed" 
} 
 
# Test for existence of the log file. 
If (-Not (Test-Path -Path $FileName)) 
{ 
    Write-Host "The log file $FileName not found. Program aborted." ` 
        -ForegroundColor red -BackgroundColor black 
    Break 
} 
 
# Check if the log file is empty or has one line. PowerShell Version 1 does not 
# support the -Header parameter, so the file must have a header line. This script 
# will add a header line if required. 
$LogFile = Import-Csv -Path $FileName 
If (-Not $LogFile) 
{ 
    # The log file is either empty or has one line. 
    # Import-Csv without the -Header parameter always assumes the first line is a header. 
    $Contents = Get-Content -Path $FileName 
    If (-Not $Contents) 
    { 
        # The log file is empty. 
        Write-Host "The log file $FileName is empty. Program aborted." ` 
            -ForegroundColor yellow -BackgroundColor black 
        Break 
    } 
    # The log file has one line. Check if the line is an event or a header line. 
    # At this point we assume the header fields are in the order specified. 
    If ($Contents.Replace(" """).ToLower() -Like "event,date,time,computer,user*") 
    { 
        # The log file has just a header line. 
        Write-Host "The log file $FileName has no events. Program aborted." ` 
            -ForegroundColor yellow -BackgroundColor black 
        Break 
    } 
    If (($Contents.ToLower() -Like "logon,*") -Or ($Contents.ToLower() -Like "logoff,*")) 
    { 
        # Log file appears to have one event, but no header line. 
        Write-Host "The log file $FileName has one event, but no header line." ` 
            -ForegroundColor yellow -BackgroundColor black 
        $NumWarnings = $NumWarnings + 1 
        # Insert a header line at the beginning of the log file. 
        Set-Content -Path $FileName -Value "Event,Date,Time,Computer,User" 
        Add-Content -Path $FileName -Value $Contents 
        Write-Host "A header line has been added at the beginning of the file." ` 
            -ForegroundColor green -BackgroundColor black 
        # Read the log file again, this time with the header line added. 
        # The script can continue. 
        $LogFile = Import-Csv -Path $FileName 
    } 
    Else 
    { 
        # The contents of the log file are not recognized. 
        Write-Host "The contents of log file $FileName are not recognized. Program aborted." ` 
            -ForegroundColor red -BackgroundColor black 
        Break 
    } 
} 
 
# Read only the first event of the log file to check the header line. 
# At this point we know the file has a header line and at least one event. 
# If the file has more than one event, $LogFile is an array. 
# If the file has just one event, it is not. In that case $NumberOfLines is Null. 
$NumberOfLines = $LogFile.Count 
If (-Not $NumberOfLines) 
{ 
    $NumberOfLines = 1 
    $Line = $LogFile 
} 
Else 
{ 
    $Line = $LogFile[0} 
$Event = $Line.Event 
$Date = $Line.Date 
$Time = $Line.Time 
$Computer = $Line.Computer 
$User = $Line.User 
# Check the header line. The script checked the header line when the log file had just 
# one line. Now the script must check log files with more than one line. 
If ((-Not $Event) -Or (-Not $Date) -Or (-Not $Time) -Or (-Not $Computer) -Or (-Not $User)) 
{ 
    # The header line is not recognized. Check if the first line is an event. 
    $Contents = Get-Content -Path $FileName -TotalCount 1 
    If (($Contents.ToLower() -Like "logon,*") -Or ($Contents.ToLower() -Like "logoff,*")) 
    { 
        # Log file appears to have events, but no header line. 
        Write-Host "The log file $FileName has events, but no header line." ` 
            -ForegroundColor yellow -BackgroundColor black 
        $NumWarnings = $NumWarnings + 1 
        # Insert a header line at the beginning of the log file. 
        $Contents = Get-Content -Path $FileName 
        Set-Content -Path $FileName -Value "Event,Date,Time,Computer,User" 
        Add-Content -Path $FileName -Value $Contents 
        Write-Host "A header line has been added at the beginning of the file." ` 
            -ForegroundColor green -BackgroundColor black 
        # Read the log file again, this time with the header line added. 
        # The script can continue. 
        $LogFile = Import-Csv -Path $FileName 
    } 
    Else 
    { 
        # The contents of the log file are not recognized. 
        $Ns = $Line | Get-Member -MemberType NoteProperty | Select Name 
        $Y = "Header: " 
        ForEach ($N In $Ns) 
        { 
            $Y = $Y + $N.Name + "," 
        } 
        # Strip off trailing comma. 
        $Y = $Y.Substring(0, $Y.Length - 1) 
        Write-Host "$Y not in correct format. Program aborted." ` 
            -ForegroundColor red -BackgroundColor black 
        Break 
    } 
} 
 
# Hash table of user sessions. The key will be the session, in the form ComputerName\UserName. 
# The value will be the logon datetime. Only logons are maintained in the hash table. When 
# the corresponding logoff event is detected in the log file, the session information is 
# output and the session is deleted from the hash table. 
$LogonSessions = @{} 
 
# Read each event in the log file. 
# At this point we know the log file has a header and at least one event. 
# The script must determine $NumberOfLines again, because a header line may have been added. 
$NumberOfLines = $LogFile.Count 
If (-Not $NumberOfLines) {$NumberOfLines = 1} 
$Initial = 0 
$Stop = $False 
 
# Call the function to read the LogFile and parse the lines. 
ReadLog $LogFile $Initial $NumberOfLines 
 
# If a line with a bad datetime was skipped, read the rest of the log file. 
If (($Skip -eq $True) -and ($Stop -eq $False)) 
{ 
    $Skip = $False 
    $Initial = $SkipLine + 1 
    # Continue with the next line in the log file. 
    ReadLog $LogFile $Initial $NumberOfLines 
} 
 
# If another line with a bad datetime was skipped, read the rest of the log file. 
If (($Skip -eq $True) -and ($Stop -eq $False)) 
{ 
    $Skip = $False 
    $Initial = $SkipLine + 1 
    # Continue with the next line in the log file. 
    ReadLog $LogFile $Initial $NumberOfLines 
} 
 
# If a third line with a bad datetime was skipped, read no more lines of the log file. 
If (($Skip -eq $True) -and ($Stop -eq $False)) 
{ 
    Write-Host "More than two lines of the log file have bad dates. No more lines processed." ` 
        -ForegroundColor red -BackgroundColor black 
    $NLines = '{0:n0}' -f $NumLines 
    Write-Host "ParseLogons.ps1" -ForegroundColor green -BackgroundColor black 
    Write-Host "Date: $(Get-Date)" -ForegroundColor green -BackgroundColor black 
    Write-Host "Log File: $FileName" -ForegroundColor green -BackgroundColor black 
    Write-Host "Program halted after reading $NLines lines of the log file." ` 
        -ForegroundColor green -BackgroundColor black 
    Break 
} 
 
If ($Stop -eq $True) 
{ 
    $NLines = '{0:n0}' -f $NumLines 
    Write-Host "ParseLogons.ps1" -ForegroundColor green -BackgroundColor black 
    Write-Host "Date: $(Get-Date)" -ForegroundColor green -BackgroundColor black 
    Write-Host "Log File: $FileName" -ForegroundColor green -BackgroundColor black 
    Write-Host "Program halted after reading $NLines lines of the log file." ` 
        -ForegroundColor green -BackgroundColor black 
    Break 
} 
 
# Loop through the sessions to find users still logged on. 
ForEach ($Entry In $LogonSessions.Keys) 
{ 
    $PreviousTime = $LogonSessions[$Entry] 
    "$Entry,$PreviousTime,(still logged on),(unknown)" 
    $NumSessions = $NumSessions + 1 
    $NumStillLoggedOn = $NumStillLoggedOn + 1 
} 
 
# Format the totals. 
$NLines = '{0:n0}' -f $NumLines 
$Max = $NLines.Length 
$NSessions = ('{0:n0}' -f $NumSessions).PadLeft($Max, " ") 
$NWarnings = ('{0:n0}' -f $NumWarnings).PadLeft($Max, " ") 
$NBadLines = ('{0:n0}' -f $NumBadLines).PadLeft($Max, " ") 
$NNoLogoffs = ('{0:n0}' -f $NumNoLogoffs).PadLeft($Max, " ") 
$NNoLogons = ('{0:n0}' -f $NumNoLogons).PadLeft($Max, " ") 
$NStillLoggedOn = ('{0:n0}' -f $NumStillLoggedOn).PadLeft($Max, " ") 
 
# Display totals. 
Write-Host "ParseLogons.ps1" -ForegroundColor green -BackgroundColor black 
Write-Host "Date: $(Get-Date)" -ForegroundColor green -BackgroundColor black 
Write-Host "Log File: $FileName" -ForegroundColor green -BackgroundColor black 
Write-Host "Totals:" -ForegroundColor green -BackgroundColor black 
Write-Host "  Lines read in the log file: $NLines" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Bad lines skipped:          $NBadLines" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Warnings:                   $NWarnings" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Sessions with no Logoff :   $NNoLogoffs" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Sessions with no logon:     $NNoLogons" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Sessions still logged on:   $NStillLoggedOn" ` 
    -ForegroundColor green -BackgroundColor black 
Write-Host "  Total Sessions:             $NSessions" ` 
    -ForegroundColor green -BackgroundColor black