How to Send Email with Log File Attachment in Batch Script
For a script that runs automatically on a server, a log file sitting on the drive is silent. If a critical backup fails or a system audit detects a security breach, you shouldn't have to manually check the folder, the script should notify you. By using PowerShell's email capabilities from a Batch script, you can send email notifications containing the results of your task, including log file attachments. This ensures that you stay informed about the health of your infrastructure without manual checking.
This guide will explain how to send emails from Batch using PowerShell.
Important Note: Send-MailMessage Deprecation
PowerShell's Send-MailMessage cmdlet is officially deprecated because it cannot guarantee secure connections to all modern SMTP servers. However, it remains functional in all current PowerShell versions and is still the most practical option for:
- Internal SMTP relays that don't require TLS
- Simple alerting where the SMTP server is trusted and on the local network
- Environments without access to the Microsoft Graph API or third-party modules
For production environments with Microsoft 365, consider migrating to the Microsoft Graph API or a dedicated alerting service. For internal relays and simple notifications, Send-MailMessage remains a reasonable choice.
Method 1: Email with Log File Attachment
This method sends an alert email with a log file attached, using proper error handling and validation.
Implementation
@echo off
setlocal
:: =============================================
:: Email Configuration
:: =============================================
set "SMTPServer=mail.internal.company.com"
set "SMTPPort=25"
set "From=alerts@company.com"
set "To=admin@company.com"
set "Subject=[%COMPUTERNAME%] Backup Report - %date%"
set "Attachment=%~dp0backup.log"
:: =============================================
:: Validation
:: =============================================
:: Verify the attachment exists
if not exist "%Attachment%" (
echo [ERROR] Attachment not found: %Attachment% >&2
echo [INFO] Sending notification without attachment.
set "Attachment="
)
echo [INFO] Sending email notification...
:: =============================================
:: Send Email
:: =============================================
if defined Attachment (
powershell -NoProfile -Command ^
"try {" ^
" Send-MailMessage" ^
" -From '%From%'" ^
" -To '%To%'" ^
" -Subject '%Subject%'" ^
" -Body 'The scheduled backup has completed. Please review the attached log for details.'" ^
" -SmtpServer '%SMTPServer%'" ^
" -Port %SMTPPort%" ^
" -Attachments '%Attachment%';" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"
) else (
powershell -NoProfile -Command ^
"try {" ^
" Send-MailMessage" ^
" -From '%From%'" ^
" -To '%To%'" ^
" -Subject '%Subject%'" ^
" -Body 'The scheduled backup has completed. Log file was not available for attachment.'" ^
" -SmtpServer '%SMTPServer%'" ^
" -Port %SMTPPort%;" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"
)
if errorlevel 1 (
echo [ERROR] Failed to send email. Check SMTP settings and network connectivity. >&2
:: Log the failure locally so it's not lost
echo [%date% %time%] Email send failed. SMTP: %SMTPServer%:%SMTPPort% >> "%~dp0email_failures.log"
endlocal
exit /b 1
)
echo [OK] Email sent successfully.
endlocal
exit /b 0
When to use -UseSsl:
Add -UseSsl to the Send-MailMessage parameters when:
- Connecting to an external SMTP server (Gmail, Office 365, etc.)
- Using port 587 (STARTTLS) or 465 (implicit SSL)
- Company policy requires encrypted email transmission
For internal relays on port 25 within a trusted network, SSL is typically not required.
Method 2: Authenticated Email (External SMTP)
When sending through Gmail, Office 365, or any SMTP server requiring authentication, you need credentials. This method uses a stored credential file rather than embedding passwords in the script.
Step 1: Store Credentials Securely (Run Once)
Run this command interactively to create an encrypted credential file. The file is encrypted with the current user's Windows identity and can only be decrypted by the same user on the same machine.
@echo off
echo [SETUP] Storing SMTP credentials for automated email.
echo You will be prompted for the username and password.
echo.
powershell -NoProfile -Command ^
"$cred = Get-Credential -Message 'Enter SMTP credentials (email and app password)';" ^
"$cred | Export-Clixml -Path '%~dp0smtp_credential.xml';" ^
"Write-Host 'Credentials saved to smtp_credential.xml'"
Step 2: Send Email Using Stored Credentials
@echo off
setlocal
set "SMTPServer=smtp.gmail.com"
set "SMTPPort=587"
set "To=admin@company.com"
set "Subject=[%COMPUTERNAME%] Alert - %date%"
set "Body=Automated alert from %COMPUTERNAME%. See attached log."
set "CredFile=%~dp0smtp_credential.xml"
set "Attachment=%~dp0report.log"
:: Verify credential file exists
if not exist "%CredFile%" (
echo [ERROR] Credential file not found: %CredFile% >&2
echo [INFO] Run the credential setup script first. >&2
endlocal
exit /b 1
)
echo [INFO] Sending authenticated email via %SMTPServer%...
powershell -NoProfile -Command ^
"try {" ^
" $cred = Import-Clixml -Path '%CredFile%';" ^
" $params = @{" ^
" From = $cred.UserName;" ^
" To = '%To%';" ^
" Subject = '%Subject%';" ^
" Body = '%Body%';" ^
" SmtpServer = '%SMTPServer%';" ^
" Port = %SMTPPort%;" ^
" UseSsl = $true;" ^
" Credential = $cred" ^
" };" ^
" if (Test-Path '%Attachment%') { $params.Attachments = '%Attachment%' };" ^
" Send-MailMessage @params;" ^
" exit 0" ^
"} catch {" ^
" Write-Error $_.Exception.Message;" ^
" exit 1" ^
"}"
if errorlevel 1 (
echo [ERROR] Failed to send email. >&2
echo [INFO] Common causes: expired app password, blocked port, invalid credentials. >&2
endlocal
exit /b 1
)
echo [OK] Email sent successfully.
endlocal
exit /b 0
Why Export-Clixml for credentials:
- The credential file is encrypted using the Windows Data Protection API (DPAPI), tied to the specific user account and machine.
- It cannot be decrypted by a different user or on a different computer.
- If the script runs as a scheduled task, the credential must be created by the same user account under which the task runs.
Gmail and Office 365 requirements:
- Gmail: Requires an "App Password" (generated in Google Account settings > Security > App Passwords). Regular account passwords will not work if 2FA is enabled.
- Office 365: Requires either an App Password or OAuth-based authentication (which
Send-MailMessagedoes not support, use the Microsoft Graph API for modern O365 integration). - Both: Use port 587 with
-UseSsl.
Method 3: Reusable Email Subroutine
For scripts that may need to send emails at multiple points (start notification, completion notification, error alert), a reusable subroutine keeps the code clean.
@echo off
setlocal
set "SMTPServer=mail.internal.company.com"
set "SMTPPort=25"
set "EmailFrom=scripts@company.com"
set "EmailTo=ops-team@company.com"
:: =============================================
:: Main Script Logic
:: =============================================
echo [INFO] Starting database maintenance...
:: Simulate a task that might fail
net session >nul 2>&1
if errorlevel 1 (
call :SendEmail "DB Maintenance FAILED on %COMPUTERNAME%" ^
"The database maintenance script could not obtain admin privileges." ""
endlocal
exit /b 1
)
:: ... (maintenance logic here) ...
:: Send success notification with log attached
call :SendEmail "DB Maintenance Complete on %COMPUTERNAME%" ^
"Database maintenance completed successfully. See attached log." ^
"%~dp0maintenance.log"
endlocal
exit /b 0
:: =============================================
:: Email Subroutine
:: Usage: call :SendEmail "Subject" "Body" "AttachmentPath"
:: AttachmentPath can be "" for no attachment
:: =============================================
:SendEmail
setlocal
set "Sub=%~1"
set "Bod=%~2"
set "Att=%~3"
if "%Att%"=="" (
set "AttParam="
) else (
if exist "%Att%" (
set "AttParam=-Attachments '%Att%'"
) else (
set "AttParam="
)
)
powershell -NoProfile -Command ^
"try {" ^
" Send-MailMessage -From '%EmailFrom%' -To '%EmailTo%'" ^
" -Subject '%Sub%' -Body '%Bod%'" ^
" -SmtpServer '%SMTPServer%' -Port %SMTPPort%" ^
" %AttParam%;" ^
" exit 0" ^
"} catch { exit 1 }" >nul 2>&1
if errorlevel 1 (
echo [WARNING] Email notification failed. Continuing script execution. >&2
echo [%date% %time%] Email failed: %Sub% >> "%~dp0email_failures.log"
)
endlocal
exit /b 0
Why the subroutine doesn't abort on failure:
Email delivery is a notification mechanism, not core business logic. If the SMTP server is temporarily down, the script should still complete its primary task (backup, maintenance, audit). The email failure is logged locally so it's not lost, but it doesn't prevent the main work from finishing.
How to Avoid Common Errors
Wrong Way: Embedding Passwords in the Script
:: DANGEROUS: password visible to anyone who reads the file
set "Password=MySecretP@ss!"
powershell -Command "... -Credential (New-Object PSCredential('%User%', (ConvertTo-SecureString '%Password%' -AsPlainText -Force)))"
If the script is shared, backed up, or stored in version control, the password is exposed.
Correct Way: Use Export-Clixml (Method 2) for encrypted credential storage, or use an internal SMTP relay that accepts connections from known server IPs without authentication.
Problem: Firewall Blocks SMTP Ports
Many ISPs, cloud providers (AWS, Azure, GCP), and corporate firewalls block port 25 outbound to prevent spam.
Solution: Use port 587 (STARTTLS) with -UseSsl. If that's also blocked, set up an internal SMTP relay that accepts connections on the local network and relays through an authorized route.
Problem: Attachment Too Large
SMTP servers typically reject messages with attachments over 10–25 MB. A large log file will cause the send to fail.
Solution: Check the file size before attaching and compress if necessary:
:: Check if log file exceeds 5 MB before attaching
for %%f in ("%Attachment%") do (
if %%~zf gtr 5242880 (
echo [INFO] Log file exceeds 5 MB. Compressing...
tar -acf "%Attachment%.zip" "%Attachment%" 2>nul
set "Attachment=%Attachment%.zip"
)
)
Problem: Special Characters in Subject or Body
If the email subject or body contains ' (single quotes), &, |, or %, the Batch-to-PowerShell parameter passing can break.
Solution: Keep email subjects and bodies simple, use alphanumeric text and basic punctuation. For dynamic content that might contain special characters, write the body to a temp file and use -BodyAsHtml or read it with Get-Content:
-Body (Get-Content 'body.txt' -Raw)
Problem: Send-MailMessage Deprecation Warnings
PowerShell 7+ displays a deprecation warning when Send-MailMessage is used. This warning goes to stderr and may appear in your Batch script's error output.
Solution: Add -WarningAction SilentlyContinue to suppress the warning. The cmdlet remains functional despite the deprecation notice.
Best Practices and Rules
1. Only Email on Failures or Daily Summaries
If your script runs every 5 minutes, sending a "Success" email each time will generate 288 emails per day, which will be ignored or flagged as spam. Send emails only for failures, threshold violations, or once-daily summary reports.
2. Include the Computer Name in the Subject
If you manage multiple servers, include %COMPUTERNAME% in the subject line so you can identify the source without opening the email:
[SERVER-DB01] Backup Failed - 2024-05-10
3. Log Email Failures Locally
If the email itself fails (SMTP down, credentials expired, network issue), the failure notification is lost. Always log email send failures to a local file as a fallback record.
4. Use an Internal SMTP Relay When Possible
An internal relay (like IIS SMTP, hMailServer, or Postfix on a Linux VM) that accepts unauthenticated connections from known server IPs is the simplest and most reliable approach. It eliminates credential management, app passwords, and TLS configuration issues.
5. Compress Large Attachments
Check the attachment size before sending. Compress files over 5 MB with tar -acf to avoid SMTP size limits and reduce transmission time.
6. Test with a Manual Send First
Before integrating email into an automated script, test the PowerShell Send-MailMessage command interactively to verify SMTP settings, credentials, and network connectivity. Debug email delivery issues separately from your main script logic.
Conclusions
Automating email notifications turns your Batch scripts from passive files into active monitors. By bridging to PowerShell's SMTP capabilities, with proper credential storage, error handling, and size management, you ensure that critical system status information reaches you wherever you are. Whether using an internal relay for simplicity or authenticated external SMTP for cloud environments, email notification is a foundational capability for professional system administration.