Shore Up
A clerk each morning reading a tall stack of logbooks, circling the red entries, and sealing a one-page daily summary into an envelope.
WindowsSecurity

Email a Daily Windows Event Log Error Summary with PowerShell

Ketan Aagja10 min read
No ratings yet

Before you run this

This guide builds a small PowerShell script that reads the last 24 hours of Critical and Error events from the System and Application logs, writes them to an HTML file, and emails that file to you. It's a read-only report: it does not clear, modify, or delete any event log. The only things it creates on the system are the HTML report files in a folder you choose and — in the last section — a scheduled task.

Privileges. The Application log is readable by normal users, but reading the System log reliably requires membership in the local Event Log Readers group (or Administrator). So run the script, and register the scheduled task, under an account that has that right. You do not need Domain Admin.

Test it first. Read the script before you run it. Run it once by hand, on a lab machine or a single non-production server, and confirm the report file and the email look right before you schedule it. The email step sends to whatever address you configure — check you're not about to spam a distribution list.

One honest caveat. The script uses Send-MailMessage. Microsoft has marked that cmdlet obsolete — it still ships and works in Windows PowerShell 5.1, but they recommend against it for new code and offer no drop-in built-in replacement. For an internal report to your own relay it's fine and still the mainstream choice; if you need a fully supported path, look at a maintained mail module (for example, MailKit-based ones) — I won't walk through that here.

What I'm assuming

  • Windows Server 2019 or 2022, Windows PowerShell 5.1 (the default; powershell.exe, not pwsh).
  • You have an SMTP relay or server that will accept authenticated submission on port 587 with STARTTLS. Adjust the host and port to yours.
  • You're comfortable that "Critical" and "Error" (event levels 1 and 2) are the noise floor you want. Add level 3 if you want Warnings too.

The script

Save this as C:\Scripts\Send-EventSummary.ps1. Edit the settings block — the values in it are placeholders you must replace with your own domain, addresses, and relay.

#Requires -Version 5.1

# --- Settings you edit -------------------------------------------------
$Logs       = 'System','Application'                 # logs to scan
$Hours      = 24                                     # look-back window
$ReportDir  = 'C:\Reports\EventSummary'              # where reports are saved
$SmtpServer = 'smtp.example.com'                     # your relay
$SmtpPort   = 587
$MailFrom   = 'eventlog@example.com'
$MailTo     = 'admin@example.com'
$CredFile   = 'C:\Reports\EventSummary\smtp.cred'    # DPAPI-protected credential
# ----------------------------------------------------------------------

$StartTime    = (Get-Date).AddHours(-$Hours)
$ComputerName = $env:COMPUTERNAME
$stamp        = Get-Date -Format 'yyyy-MM-dd'

if (-not (Test-Path $ReportDir)) {
    New-Item -Path $ReportDir -ItemType Directory | Out-Null
}

# Collect Critical (Level 1) and Error (Level 2) events from each log.
# -ErrorAction SilentlyContinue swallows the "No events were found" error
# that Get-WinEvent throws when a log has nothing matching in the window.
$events = foreach ($log in $Logs) {
    Get-WinEvent -FilterHashtable @{
        LogName   = $log
        Level     = 1, 2
        StartTime = $StartTime
    } -ErrorAction SilentlyContinue
}

$count = ($events | Measure-Object).Count

$style = @"
<style>
 body { font-family: Segoe UI, Arial, sans-serif; font-size: 13px; }
 table { border-collapse: collapse; }
 th, td { border: 1px solid #ccc; padding: 4px 8px; text-align: left; }
 th { background: #f0f0f0; }
</style>
"@

# Take only the first line of each Message so the table stays readable.
$body = $events |
    Sort-Object TimeCreated -Descending |
    Select-Object TimeCreated, LogName, LevelDisplayName, Id, ProviderName,
                  @{ Name = 'Message'; Expression = { ($_.Message -split "`r?`n")[0] } } |
    ConvertTo-Html -Head $style -PreContent @"
<h2>Event error summary for $ComputerName</h2>
<p>$count Critical/Error events in the last $Hours hours (since $StartTime).</p>
"@ | Out-String

$reportPath = Join-Path $ReportDir "EventSummary-$stamp.html"
$body | Out-File -FilePath $reportPath -Encoding UTF8

# Email the report. Credential is loaded from the DPAPI-protected file.
$cred = Import-Clixml -Path $CredFile

$mail = @{
    From        = $MailFrom
    To          = $MailTo
    Subject     = "[$ComputerName] $count event errors - $stamp"
    Body        = $body
    BodyAsHtml  = $true
    SmtpServer  = $SmtpServer
    Port        = $SmtpPort
    UseSsl      = $true            # STARTTLS on submission
    Credential  = $cred
    Attachments = $reportPath
}
Send-MailMessage @mail

Every property used — TimeCreated, LogName, LevelDisplayName, Id, ProviderName, Message — is a real property of the records Get-WinEvent returns. The Level values are the standard Windows severities: 1 = Critical, 2 = Error, 3 = Warning. For the exact FilterHashtable keys, see Get-WinEvent on Microsoft Learn.

Storing the SMTP credential

Never hard-code the relay password in the script. Export it once, logged in as the account that will run the scheduled task, to a file that only that user (on that machine) can decrypt — Export-Clixml protects the password with DPAPI:

# Run this once, interactively, as the task's service account:
Get-Credential | Export-Clixml -Path 'C:\Reports\EventSummary\smtp.cred'

The DPAPI tie is the gotcha to remember: a .cred file created by you cannot be read back by a different account or on a different machine. If the scheduled task runs as svc-eventlog, create the file while logged in as svc-eventlog.

Scheduling it daily

Register a task that runs the script every morning. Replace the account and time with yours:

$action  = New-ScheduledTaskAction -Execute 'powershell.exe' `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Send-EventSummary.ps1"'

$trigger = New-ScheduledTaskTrigger -Daily -At 7am

Register-ScheduledTask -TaskName 'Daily Event Error Summary' `
    -Action $action -Trigger $trigger `
    -User 'EXAMPLE\svc-eventlog' -Password '<the-account-password>' `
    -RunLevel Highest

-RunLevel Highest runs it elevated so the System log is readable. If you'd rather not put the password on the command line, create the task in the Task Scheduler GUI instead and let it prompt you — same result. These New-ScheduledTask* and Register-ScheduledTask cmdlets are documented under the ScheduledTasks module on Microsoft Learn.

Verify it worked

  1. Run it by hand first. From an elevated PowerShell prompt as the service account:

    & 'C:\Scripts\Send-EventSummary.ps1'
    

    Then confirm the report exists and the mail arrived:

    Get-ChildItem C:\Reports\EventSummary\
    

    Check the target inbox for the message and open the HTML attachment.

  2. Test the scheduled task without waiting for 7am:

    Start-ScheduledTask -TaskName 'Daily Event Error Summary'
    Get-ScheduledTaskInfo -TaskName 'Daily Event Error Summary'
    

    LastTaskResult of 0 means success. A non-zero value points to an exit error — usually the credential file (wrong account) or SMTP auth.

  3. Sanity-check the counts. Compare against what Event Viewer shows for the same window, or run the filter alone:

    Get-WinEvent -FilterHashtable @{ LogName='System'; Level=1,2; StartTime=(Get-Date).AddHours(-24) } |
        Measure-Object
    

Undo / rollback

Reading the logs changes nothing, so there's nothing to reverse there. To remove what the guide did add:

# Remove the scheduled task
Unregister-ScheduledTask -TaskName 'Daily Event Error Summary' -Confirm:$false

# Remove the stored credential and the reports (irreversible for those files)
Remove-Item 'C:\Reports\EventSummary\smtp.cred'
Remove-Item 'C:\Reports\EventSummary\*.html'

Deleting the report files and credential is permanent, but neither the event logs nor any system setting is touched — pull the task and the folder and the server is exactly as it was.

Written by
Ketan Aagja

Runs enterprise networks and security for a living, and writes Shore Up to turn two decades of hands-on Linux, Windows and mail-server work into guides you can actually use.

More about the author →

Was this article helpful?

Tap a star — no sign-in needed.

Be the first to rate this article.