Windows Event Log Project - Snowboundport37/champlain GitHub Wiki

Windows Event Log Project

This repository documents the complete implementation of the Functions and Event Logs assignment, including step-by-step progression, code files, and screenshots.

Project Overview

The assignment involves creating PowerShell functions to retrieve and process Windows Event Log data for logon/logoff events and system startup/shutdown events, with proper SID-to-username translation and modular function design.

Assignment Requirements

  1. Get login and logoff records from Windows Events
  2. Create custom objects with Time, Id, Event, and User properties
  3. Translate SID to username using System.Security.Principal.SecurityIdentifier
  4. Create functions with parameterized day ranges
  5. Add startup/shutdown event tracking
  6. Use dot-sourcing to load functions from separate files

Project Structure

├── Event-Logs.ps1    # Core functions for event log processing
├── Main.ps1          # Main script using dot-sourcing
└── README.md         # This documentation

Implementation Steps

Step 1: Get Logon/Logoff Records

Initial retrieval of raw 7001 (Logon) and 7002 (Logoff) events from the System event log.

Get-EventLog -LogName System -After (Get-Date).AddDays(-14) |
Where-Object { $_.EventID -in 7001,7002 }

Key Learning: Understanding Windows Event IDs and filtering event logs by date range.

Step 2: Create Custom Table Structure

Built a loop to process events and create custom PowerShell objects with standardized properties:

  • Time: TimeGenerated from original event
  • Id: InstanceId from original event
  • Event: "Logon" or "Logoff" based on InstanceId
  • User: Extracted from ReplacementStrings array
$events = Get-EventLog -LogName System -After (Get-Date).AddDays(-14) |
          Where-Object EventID -in 7001,7002

$rows = foreach ($e in $events) {
    $eventName = if ($e.InstanceId -eq 7001) { 'Logon' } 
                 elseif ($e.InstanceId -eq 7002) { 'Logoff' } 
                 else { 'Other' }
    
    $userRaw = if ($e.ReplacementStrings.Count -ge 2) { $e.ReplacementStrings[1] }
               elseif ($e.ReplacementStrings.Count -ge 1) { $e.ReplacementStrings[0] }
               else { '' }
    
    [pscustomobject]@{
        Time  = $e.TimeGenerated
        Id    = $e.InstanceId
        Event = $eventName
        User  = $userRaw
    }
}

Step 3: SID to Username Translation

Implemented SID (Security Identifier) to readable username conversion using .NET System.Security.Principal classes.

function Convert-SidToName {
    param([string]$SidOrName)
    try {
        $sid = New-Object System.Security.Principal.SecurityIdentifier($SidOrName)
        return ($sid.Translate([System.Security.Principal.NTAccount])).Value
    } catch {
        return $SidOrName
    }
}

Key Learning: Converting Windows SIDs to human-readable DOMAIN\Username format with proper error handling.

Step 4: Function Wrapper

Converted the script into a reusable function with parameterized day range:

function Get-LoginLogoffTable {
    param([int]$Days = 14)
    # Complete implementation with SID translation
    # Returns sorted table of logon/logoff events
}

Step 5: Startup/Shutdown Function

Added system event tracking for 6005 (Startup) and 6006 (Shutdown) events:

function Get-StartStopTable {
    param([int]$Days = 25)
    
    $events = Get-EventLog -LogName System -After (Get-Date).AddDays(-$Days) |
              Where-Object { $_.EventID -in 6005,6006 }
    
    $rows = foreach ($e in $events) {
        $eventName = if ($e.EventID -eq 6005) { 'Startup' } 
                     elseif ($e.EventID -eq 6006) { 'Shutdown' } 
                     else { 'Other' }
        
        [pscustomobject]@{
            Time  = $e.TimeGenerated
            Id    = $e.EventID   # Note: EventID, not InstanceId
            Event = $eventName
            User  = 'System'
        }
    }
    $rows | Sort-Object Time -Descending
}

Key Differences:

  • Uses EventID instead of InstanceId for system events
  • Sets User to constant "System" value
  • Tracks different event types (6005/6006 vs 7001/7002)

Step 6: Dot-Sourcing Implementation

Created modular design using dot-sourcing to load functions from separate files:

Main.ps1:

# Load functions using dot-sourcing
. (Join-Path $PSScriptRoot 'Event-Logs.ps1')

Clear-Host

# Configure day windows
$daysLogin  = 14
$daysSystem = 25

Write-Host "`n==== Logon and Logoff ====" -ForegroundColor Cyan
Get-LoginLogoffTable -Days $daysLogin | Format-Table -Auto

Write-Host "`n==== Shutdowns ====" -ForegroundColor Cyan
Get-StartStopTable -Days $daysSystem | Where-Object Event -eq 'Shutdown' | Format-Table -Auto

Write-Host "`n==== Startups ====" -ForegroundColor Cyan
Get-StartStopTable -Days $daysSystem | Where-Object Event -eq 'Startup'  | Format-Table -Auto

📸 SCREENSHOT NEEDED: Take a final screenshot showing the complete Main.ps1 execution with all three sections displayed:

  1. Logon and Logoff table with translated usernames
  2. Shutdowns table filtered to show only shutdown events
  3. Startups table filtered to show only startup events

This screenshot should demonstrate the final working solution with proper formatting, colored headers, and all data correctly processed.

Complete Code Files

Event-Logs.ps1

# Helper: SID to DOMAIN\User
function Convert-SidToName {
    param([string]$SidOrName)
    try {
        $sid = New-Object System.Security.Principal.SecurityIdentifier($SidOrName)
        return ($sid.Translate([System.Security.Principal.NTAccount])).Value
    } catch {
        return $SidOrName
    }
}

# Logon and Logoff Events (7001, 7002)
function Get-LoginLogoffTable {
    param([int]$Days = 14)
    
    $events = Get-EventLog -LogName System -After (Get-Date).AddDays(-$Days) |
              Where-Object { $_.EventID -in 7001,7002 }
    
    $rows = foreach ($e in $events) {
        $eventName = if ($e.InstanceId -eq 7001) { 'Logon' } 
                     elseif ($e.InstanceId -eq 7002) { 'Logoff' } 
                     else { 'Other' }
        
        $userRaw = if ($e.ReplacementStrings.Count -ge 2) { $e.ReplacementStrings[1] }
                   elseif ($e.ReplacementStrings.Count -ge 1) { $e.ReplacementStrings[0] }
                   else { '' }
        
        [pscustomobject]@{
            Time  = $e.TimeGenerated
            Id    = $e.InstanceId
            Event = $eventName
            User  = Convert-SidToName $userRaw
        }
    }
    $rows | Sort-Object Time -Descending
}

# Startup and Shutdown Events (6005, 6006)
function Get-StartStopTable {
    param([int]$Days = 25)
    
    $events = Get-EventLog -LogName System -After (Get-Date).AddDays(-$Days) |
              Where-Object { $_.EventID -in 6005,6006 }
    
    $rows = foreach ($e in $events) {
        $eventName = if ($e.EventID -eq 6005) { 'Startup' } 
                     elseif ($e.EventID -eq 6006) { 'Shutdown' } 
                     else { 'Other' }
        
        [pscustomobject]@{
            Time  = $e.TimeGenerated
            Id    = $e.EventID   # Note: EventID for system events
            Event = $eventName
            User  = 'System'
        }
    }
    $rows | Sort-Object Time -Descending
}

Main.ps1

# Load functions using dot-sourcing
. (Join-Path $PSScriptRoot 'Event-Logs.ps1')

Clear-Host

# Adjust day windows if needed
$daysLogin  = 14
$daysSystem = 25

Write-Host "`n==== Logon and Logoff ====" -ForegroundColor Cyan
Get-LoginLogoffTable -Days $daysLogin | Format-Table -Auto

Write-Host "`n==== Shutdowns ====" -ForegroundColor Cyan
Get-StartStopTable -Days $daysSystem | Where-Object Event -eq 'Shutdown' | Format-Table -Auto

Write-Host "`n==== Startups ====" -ForegroundColor Cyan
Get-StartStopTable -Days $daysSystem | Where-Object Event -eq 'Startup'  | Format-Table -Auto

Usage Instructions

  1. Clone or download both script files to the same directory
  2. Run Main.ps1 to execute the complete demonstration
  3. Adjust day ranges by modifying $daysLogin and $daysSystem variables
  4. Run individual functions by dot-sourcing Event-Logs.ps1 and calling functions directly
# Example of running individual functions
. .\Event-Logs.ps1
Get-LoginLogoffTable -Days 7
Get-StartStopTable -Days 30 | Where-Object Event -eq 'Startup'

Final Results

Key Technical Concepts

Event Log Types

  • 7001/7002: User logon/logoff events (uses InstanceId)
  • 6005/6006: System startup/shutdown events (uses EventId)

SID Translation

  • Converts Windows Security Identifiers to readable DOMAIN\Username format
  • Handles errors gracefully by returning original value if translation fails

PowerShell Techniques

  • Dot-sourcing: Loading functions from external files with . script.ps1
  • Custom objects: Creating structured data with [pscustomobject]@{}
  • Pipeline filtering: Using Where-Object for efficient data filtering
  • Error handling: Try-catch blocks for robust SID translation

Assignment Fulfillment

Login/Logoff logs obtained - Events 7001/7002 retrieved and processed
Shutdown/Startup logs obtained - Events 6006/6005 retrieved and processed
Functions and dot-sourcing working - Modular design with proper function loading
SID translation successful - User identifiers converted to readable usernames