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 '<', '&lt;' -replace '>', '&gt;')<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