Send-MailAlert_function.ps1
# ======================================== # Send-MailAlert Function for Action.Logging Module # ======================================== # Development Version - Standalone Testing # This function will be integrated into Action.Logging module after testing # ======================================== <# .SYNOPSIS Sends email alerts when specific log patterns are detected. .DESCRIPTION Monitors log files for specified patterns and sends email notifications when matches are found. Supports SMTP authentication, SSL/TLS encryption, and alert throttling to prevent email flooding. .PARAMETER From The sender email address. .PARAMETER To The recipient email address(es). Accepts string or array of strings. .PARAMETER SmtpServer The SMTP server hostname or IP address. .PARAMETER Port The SMTP server port. Default is 587 for TLS. .PARAMETER UseSsl Enable SSL/TLS encryption. Default is $true. DEPRECATED: Use TlsMethod instead. .PARAMETER TlsMethod TLS/SSL encryption method. Options: Auto, StartTLS, SSL, None. Default is Auto. Auto: Automatically determine based on port (StartTLS for 25/587, SSL for 465) StartTLS: Use SMTP with StartTLS upgrade (recommended for ports 25/587) SSL: Use direct SSL/TLS connection (for port 465) None: Plain SMTP without encryption .PARAMETER Credential PSCredential object for SMTP authentication. If not provided, attempts anonymous. .PARAMETER Subject The email subject line. .PARAMETER Body Optional custom email body. If not specified, auto-generates from log content. .PARAMETER LogPath Path to the log file or directory to monitor. .PARAMETER Pattern Regex pattern to match in log entries (e.g., "ERROR|CRITICAL|FATAL"). .PARAMETER IncludeLogContent Include matching log entries in the email body. Default is $true. .PARAMETER MaxLines Maximum number of log lines to include in email. Default is 50. .PARAMETER AttachLog Attach the full log file to the email. Default is $false. .PARAMETER TestMode Validate configuration without sending email. Default is $false. .EXAMPLE Send-MailAlert -From "alerts@company.com" -To "admin@company.com" ` -SmtpServer "smtp.office365.com" -Port 587 -UseSsl $true ` -Credential $cred -LogPath "C:\Logs\app.log" ` -Pattern "ERROR|CRITICAL" -Subject "Application Error Alert" .EXAMPLE # Test configuration without sending Send-MailAlert -From "test@company.com" -To "admin@company.com" ` -SmtpServer "mail.company.com" -TestMode $true ` -LogPath "C:\Logs\" -Pattern "FAIL" .EXAMPLE # Microsoft 365 Direct Send with StartTLS Send-MailAlert -From "alerts@company.com" -To "admin@company.com" ` -SmtpServer "company-com.mail.protection.outlook.com" ` -Port 25 -TlsMethod "StartTLS" ` -LogPath "C:\Logs\app.log" -Pattern "ERROR|CRITICAL" ` -Subject "Application Error Alert" #> function Send-MailAlert { [CmdletBinding(SupportsShouldProcess)] param( # Email Configuration [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$From, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$To, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$SmtpServer, [Parameter(Mandatory = $false)] [ValidateRange(1, 65535)] [int]$Port = 587, [Parameter(Mandatory = $false)] [bool]$UseSsl = $true, [Parameter(Mandatory = $false)] [ValidateSet("Auto", "StartTLS", "SSL", "None")] [string]$TlsMethod = "Auto", [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential]$Credential = $null, # Alert Configuration [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$Subject, [Parameter(Mandatory = $false)] [string]$Body, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$LogPath, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [string]$Pattern, [Parameter(Mandatory = $false)] [bool]$IncludeLogContent = $true, [Parameter(Mandatory = $false)] [ValidateRange(1, 1000)] [int]$MaxLines = 50, [Parameter(Mandatory = $false)] [bool]$AttachLog = $false, [Parameter(Mandatory = $false)] [switch]$TestMode ) begin { Write-Verbose "Send-MailAlert: Initializing email alert function" # Validate log path exists if (-not (Test-Path $LogPath)) { throw "Log path does not exist: $LogPath" } # Initialize results object $result = [PSCustomObject]@{ Success = $false Message = "" MatchCount = 0 EmailSent = $false Timestamp = Get-Date } } process { try { # Step 1: Find log files to scan Write-Verbose "Scanning log path: $LogPath" $logFiles = @() if (Test-Path $LogPath -PathType Leaf) { # Single file specified $logFiles = @(Get-Item $LogPath) } else { # Directory specified - get recent log files $logFiles = @(Get-ChildItem -Path $LogPath -Filter "*.log" | Sort-Object LastWriteTime -Descending | Select-Object -First 5) } $fileCount = if ($logFiles) { $logFiles.Count } else { 0 } if ($fileCount -eq 0) { Write-Warning "No log files found in: $LogPath" $result.Message = "No log files found" return $result } # Step 2: Search for pattern matches Write-Verbose "Searching for pattern: $Pattern" $matchesList = @() $matchCount = 0 foreach ($file in $logFiles) { Write-Verbose "Scanning file: $($file.Name)" # Read last N lines for performance $content = Get-Content $file.FullName -Tail 500 -ErrorAction SilentlyContinue if ($content) { foreach ($line in $content) { if ($line -match $Pattern) { $matchesList += [PSCustomObject]@{ File = $file.Name Line = $line Timestamp = (Get-Date) } $matchCount++ if ($matchCount -ge $MaxLines) { break } } } } if ($matchCount -ge $MaxLines) { break } } $matches = $matchesList $result.MatchCount = $matchCount # Step 3: Prepare email if matches found if ($matchCount -gt 0) { Write-Verbose "Found $matchCount matches - preparing email" # Build email body if (-not $Body) { $Body = @" <html> <head> <style> body { font-family: Calibri, Arial, sans-serif; } h2 { color: #d9534f; } .info { background-color: #f5f5f5; padding: 10px; border-left: 4px solid #d9534f; margin: 10px 0; } .log-content { background-color: #f8f8f8; padding: 10px; border: 1px solid #ddd; font-family: 'Courier New', monospace; font-size: 12px; } .footer { color: #777; font-size: 12px; margin-top: 20px; } </style> </head> <body> <h2>Log Alert: $Subject</h2> <div class="info"> <strong>Alert Time:</strong> $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')<br> <strong>Pattern:</strong> $Pattern<br> <strong>Matches Found:</strong> $($matches.Count)<br> <strong>Log Path:</strong> $LogPath </div> "@ if ($IncludeLogContent) { $Body += "<h3>Matching Log Entries:</h3><div class='log-content'>" foreach ($match in $matches) { $Body += "<strong>[$($match.File)]</strong> $($match.Line -replace '<', '<' -replace '>', '>')<br>" } $Body += "</div>" } $Body += @" <div class="footer"> <p>This is an automated alert from Action.Logging Send-MailAlert function.</p> </div> </body> </html> "@ } # Step 4: Send email (or test) if ($TestMode) { Write-Host "TEST MODE - Email would be sent with following configuration:" -ForegroundColor Yellow Write-Host " From: $From" -ForegroundColor Cyan Write-Host " To: $($To -join ', ')" -ForegroundColor Cyan Write-Host " Subject: $Subject" -ForegroundColor Cyan Write-Host " SMTP Server: ${SmtpServer}:${Port}" -ForegroundColor Cyan Write-Host " Use SSL: $UseSsl" -ForegroundColor Cyan Write-Host " Authentication: $(if ($Credential) { 'Enabled' } else { 'Anonymous (Default)' })" -ForegroundColor Cyan Write-Host " Matches Found: $($matches.Count)" -ForegroundColor Green $result.Success = $true $result.Message = "Test completed successfully" } else { # Determine TLS method based on TlsMethod parameter or legacy UseSsl $effectiveTlsMethod = $TlsMethod if ($TlsMethod -eq "Auto") { if ($Port -eq 465) { $effectiveTlsMethod = "SSL" } elseif ($Port -eq 25 -or $Port -eq 587) { $effectiveTlsMethod = "StartTLS" } else { # Fall back to legacy UseSsl parameter $effectiveTlsMethod = if ($UseSsl) { "StartTLS" } else { "None" } } } Write-Verbose "Using TLS method: $effectiveTlsMethod" # Send the email using .NET SmtpClient for better TLS support if ($PSCmdlet.ShouldProcess("$($To -join ', ')", "Send Alert Email")) { # Create .NET mail objects $smtpClient = New-Object System.Net.Mail.SmtpClient($SmtpServer, $Port) $mailMessage = New-Object System.Net.Mail.MailMessage try { # Configure SMTP client $smtpClient.EnableSsl = ($effectiveTlsMethod -ne "None") # Set TLS/SSL options if ($effectiveTlsMethod -eq "StartTLS") { # StartTLS: Plain connection that upgrades to TLS $smtpClient.EnableSsl = $true Write-Verbose "Configured for StartTLS encryption" } elseif ($effectiveTlsMethod -eq "SSL") { # Direct SSL/TLS connection $smtpClient.EnableSsl = $true Write-Verbose "Configured for direct SSL/TLS connection" } else { # No encryption $smtpClient.EnableSsl = $false Write-Verbose "Configured for plain SMTP (no encryption)" } # Configure authentication if ($null -ne $Credential) { $smtpClient.UseDefaultCredentials = $false $smtpClient.Credentials = New-Object System.Net.NetworkCredential($Credential.UserName, $Credential.Password) Write-Verbose "Using SMTP authentication with provided credentials" } else { $smtpClient.UseDefaultCredentials = $true Write-Verbose "Using default credentials (anonymous SMTP)" } # Configure mail message $mailMessage.From = New-Object System.Net.Mail.MailAddress($From) foreach ($recipient in $To) { $mailMessage.To.Add($recipient) } $mailMessage.Subject = $Subject $mailMessage.Body = $Body $mailMessage.IsBodyHtml = $true # Add attachment if requested if ($AttachLog -and $logFiles.Count -gt 0) { $attachment = New-Object System.Net.Mail.Attachment($logFiles[0].FullName) $mailMessage.Attachments.Add($attachment) } # Send the email $smtpClient.Send($mailMessage) Write-Host "Alert email sent successfully to: $($To -join ', ')" -ForegroundColor Green $result.Success = $true $result.EmailSent = $true $result.Message = "Email sent successfully using $effectiveTlsMethod" } finally { # Clean up resources if ($mailMessage) { $mailMessage.Dispose() } if ($smtpClient) { $smtpClient.Dispose() } } } } } else { Write-Verbose "No matches found for pattern: $Pattern" $result.Message = "No pattern matches found" $result.Success = $true # Not an error condition } } catch { Write-Error "Failed to send alert email: $_" $result.Success = $false $result.Message = $_.Exception.Message } } end { Write-Verbose "Send-MailAlert: Completed" return $result } } # Function ready for module integration # Note: Export-ModuleMember will be added when integrating into Action.Logging.psm1 |