Scheduled Email Reports of At Risk Users - Snowboundport37/champlain GitHub Wiki
Course: SEC-350
Author: Andrei Gorlitsky
Date: October 14, 2025
This project automates the process of identifying at-risk users (for example, users with repeated failed logon attempts) and sends a formatted email report daily at a configured time. All automation is handled by PowerShell scripts and Windows Task Scheduler.
| Script | Description |
|---|---|
Configuration.ps1 |
Manages and saves settings such as number of log days and execution time. |
Email.ps1 |
Builds and sends professional HTML emails using Gmail SMTP and an App Password. |
Scheduler.ps1 |
Creates a Windows Scheduled Task to run main.ps1 daily. |
main.ps1 |
Connects everything together, triggers emails, and manages automation. |
$ConfigPath = Join-Path $PSScriptRoot 'configuration.txt'
function Get-Configuration {
if (-not (Test-Path $ConfigPath)) {
Set-Content -Path $ConfigPath -Value @("30","1:12 PM")
}
$lines = Get-Content -Path $ConfigPath
[pscustomobject]@{
Days = [int]$lines[0]
ExecutionTime = $lines[1]
}
}
function Set-Configuration {
param(
[Parameter(Mandatory)][int]$Days,
[Parameter(Mandatory)][string]$ExecutionTime
)
Set-Content -Path $ConfigPath -Value @("$Days",$ExecutionTime)
}
function Show-Configuration {
$cfg = Get-Configuration
$cfg | Format-Table -AutoSize
}
function Change-Configuration {
$cfg = Get-Configuration
Write-Host "`nEnter new configuration values:"
$newDays = Read-Host "Days to evaluate logs [$($cfg.Days)]"
if (-not $newDays) { $newDays = $cfg.Days }
$newTime = Read-Host "Daily run time like 1:12 PM [$($cfg.ExecutionTime)]"
if (-not $newTime) { $newTime = $cfg.ExecutionTime }
Set-Configuration -Days $newDays -ExecutionTime $newTime
Write-Host "Configuration updated!" -ForegroundColor Green
}
function configurationMenu {
while ($true) {
Write-Host "`n===== Configuration Menu ====="
Write-Host "1. Show configuration"
Write-Host "2. Change configuration"
Write-Host "3. Exit"
$choice = Read-Host "Select option (1–3)"
switch ($choice) {
'1' { Show-Configuration }
'2' { Change-Configuration }
'3' { break }
default { Write-Host "Please choose 1, 2, or 3." -ForegroundColor Yellow }
}
}
}
if ($MyInvocation.InvocationName -ne '.') { configurationMenu }
function SendAlertEmail {
[CmdletBinding()]
param(
[Parameter(Mandatory)][string]$BodyText,
[string]$Title = "At Risk Users Report",
[string]$Subtitle = "Automated notification",
[string]$AccentColor = "#2563eb"
)
$From = "[email protected]"
$To = "[email protected]"
$Subject = $Title
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Password = "nlkaqezoardgbqpr" | ConvertTo-SecureString -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential ($From, $Password)
$now = (Get-Date).ToString("yyyy-MM-dd HH:mm")
$html = @"
<!DOCTYPE html>
<html lang='en'>
<head><meta charset='UTF-8'><title>$Title</title></head>
<body style='margin:0;padding:0;background:#f6f7fb;font-family:Segoe UI,Arial,sans-serif;color:#0f172a;'>
<table width='100%' cellspacing='0' cellpadding='0' style='padding:24px 0;background:#f6f7fb;'>
<tr><td align='center'>
<table width='600' style='background:white;border-radius:12px;box-shadow:0 10px 25px rgba(0,0,0,.1);overflow:hidden;'>
<tr><td style='background:$AccentColor;color:white;padding:20px 28px;'>
<div style='font-size:20px;font-weight:700;'>$Title</div>
<div style='opacity:.9;font-size:13px;'>$Subtitle</div>
</td></tr>
<tr><td style='padding:28px;font-size:14px;line-height:1.6;'>$BodyText
<div style='margin-top:16px;background:#f8fafc;border:1px solid #e5e7eb;border-radius:8px;padding:12px;color:#334155;font-size:13px;'>
<strong>Run time:</strong> $now<br/>
<strong>Sender:</strong> $From
</div>
</td></tr>
<tr><td style='padding:16px 28px;border-top:1px solid #e5e7eb;background:#f9fafb;font-size:12px;color:#64748b;'>
This message was sent automatically by the Scheduled Email Reports script.
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
"@
try {
Send-MailMessage -From $From -To $To -Subject $Subject -Body $html `
-BodyAsHtml -SmtpServer "smtp.gmail.com" -Port 587 -UseSsl -Credential $Credential
Write-Host "✅ Email sent to $To" -ForegroundColor Green
} catch {
Write-Host "❌ Failed to send email: $($_.Exception.Message)" -ForegroundColor Red
}
}
function ChooseTimeToRun([string]$TimeString) {
$formats = @('h:mm tt','hh:mm tt')
$culture = [System.Globalization.CultureInfo]::GetCultureInfo('en-US')
try {
$parsed = [datetime]::ParseExact($TimeString,$formats,$culture,[System.Globalization.DateTimeStyles]::None)
} catch {
throw "Invalid time format '$TimeString'. Use e.g. 1:12 PM"
}
$taskName = 'AtRiskEmailDaily'
if (Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false
}
$main = Join-Path $PSScriptRoot 'main.ps1'
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$main`""
$trigger = New-ScheduledTaskTrigger -Daily -At $parsed.TimeOfDay
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -RunOnlyIfNetworkAvailable -WakeToRun
$task = New-ScheduledTask -Action $action -Trigger $trigger `
-Principal $principal -Settings $settings
Register-ScheduledTask $taskName -InputObject $task | Out-Null
Write-Host "✅ Created/Updated schedule '$taskName' for: $TimeString." -ForegroundColor Green
}
. "$PSScriptRoot\Configuration.ps1"
. "$PSScriptRoot\Scheduler.ps1"
. "$PSScriptRoot\Email.ps1"
$configuration = Get-Configuration
$Failed = @(
[pscustomobject]@{ Name = "$env:COMPUTERNAME$env:USERNAME"; Count = (Get-Random -Min 0 -Max 5) },
[pscustomobject]@{ Name = "DESKTOP-TEST01\alex"; Count = (Get-Random -Min 0 -Max 5) }
)
$plainTable = ($Failed | Format-Table Name,Count -AutoSize | Out-String).Trim()
SendAlertEmail -BodyText $plainTable -Title "At Risk Users Report" -Subtitle "Automated notification"
ChooseTimeToRun $configuration.ExecutionTime
Write-Host "`nTask Summary:" -ForegroundColor Cyan
Get-ScheduledTask -TaskName 'AtRiskEmailDaily' | Select TaskName, State, LastRunTime, NextRunTime | Format-Table -AutoSize