Guide
Generate and Email Reports from PowerShell
PowerShell's pipeline and object model make it a natural fit for report generation. This guide shows how to collect data from SQL, APIs, CSV files, and system metrics -- then format it as HTML tables or CSV attachments and email the results on a schedule. Works across all major email providers.
Written by Nick Barraclough Product Manager
Reviewed by Caleb Geene
What you need
The CLI must be installed and authenticated with an API key. If you haven't done that yet, follow the getting started guide first. PowerShell 5.1+ on Windows or PowerShell 7+ on macOS/Linux both work.
Send a CSV report as an attachment
The simplest reporting pattern: query data, export to CSV, attach it to an email. According to a 2024 Statista survey, 82% of businesses still use spreadsheets as their primary reporting tool. CSV is universal.
# sales-report.ps1 -- Query data and email a CSV attachment
# Step 1: Collect data (from SQL, API, CSV, or system metrics)
$data = Invoke-Sqlcmd -ServerInstance "db.company.local" -Database "Sales" -Query @"
SELECT Region, Product, SUM(Revenue) AS Revenue, COUNT(*) AS Orders
FROM Orders
WHERE OrderDate >= DATEADD(day, -7, GETDATE())
GROUP BY Region, Product
ORDER BY Revenue DESC
"@
# Step 2: Export to CSV
$csvPath = "$env:TEMP\sales-report-$(Get-Date -Format 'yyyy-MM-dd').csv"
$data | Export-Csv -Path $csvPath -NoTypeInformation
# Step 3: Build a text summary
$totalRevenue = ($data | Measure-Object -Property Revenue -Sum).Sum
$totalOrders = ($data | Measure-Object -Property Orders -Sum).Sum
$topRegion = $data | Sort-Object Revenue -Descending | Select-Object -First 1
$body = @"
Weekly Sales Report — $(Get-Date -Format 'yyyy-MM-dd')
Total revenue: $([math]::Round($totalRevenue, 2))
Total orders: $totalOrders
Top region: $($topRegion.Region) ($([math]::Round($topRegion.Revenue, 2)))
Full data attached as CSV.
"@
# Step 4: Email with attachment
nylas email send `
--to "finance@company.com" `
--subject "Sales Report — $(Get-Date -Format 'yyyy-MM-dd')" `
--body $body `
--attach $csvPath `
--yes
Remove-Item $csvPathSend an HTML table report
PowerShell's ConvertTo-Html cmdlet turns objects into HTML tables. Add inline CSS for email-safe styling (email clients strip external stylesheets).
# html-table-report.ps1 -- Styled HTML table in an email body
$services = Get-Service | Where-Object { $_.StartType -eq 'Automatic' } |
Select-Object Name, Status, DisplayName | Sort-Object Status
$tableRows = $services | ForEach-Object {
$color = if ($_.Status -eq 'Running') { '#4ade80' } else { '#ef4444' }
"<tr><td style='padding:6px 12px'>$($_.Name)</td>" +
"<td style='padding:6px 12px;color:$color'>$($_.Status)</td>" +
"<td style='padding:6px 12px'>$($_.DisplayName)</td></tr>"
}
$stoppedCount = ($services | Where-Object { $_.Status -ne 'Running' }).Count
$html = @"
<html><body style="font-family:system-ui;background:#0f172a;color:#e2e8f0;padding:24px">
<h1 style="color:#4ade80">Service Status — $env:COMPUTERNAME</h1>
<p>$($services.Count) auto-start services, $stoppedCount not running.</p>
<table style="border-collapse:collapse">
<tr style="background:#1e293b">
<th style="padding:8px 12px;text-align:left">Service</th>
<th style="padding:8px 12px;text-align:left">Status</th>
<th style="padding:8px 12px;text-align:left">Display Name</th>
</tr>
$($tableRows -join "`n")
</table>
<p style="color:#64748b;font-size:12px;margin-top:20px">
Generated $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
</p>
</body></html>
"@
nylas email send --to "ops@company.com" `
--subject "Service Report: $env:COMPUTERNAME ($stoppedCount stopped)" `
--body $html --yesAggregate data from multiple sources
Real reports pull from more than one place. This script combines system metrics, Event Log errors, and IIS traffic stats into a single weekly digest.
# weekly-digest.ps1 -- Multi-source aggregation report
$hostname = $env:COMPUTERNAME
$reportDate = Get-Date -Format 'yyyy-MM-dd'
# Source 1: System uptime and disk
$os = Get-CimInstance Win32_OperatingSystem
$uptime = (Get-Date) - $os.LastBootUpTime
$diskFree = Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'" |
ForEach-Object { [math]::Round($_.FreeSpace / 1GB, 1) }
# Source 2: Application errors (last 7 days)
$appErrors = Get-WinEvent -FilterHashtable @{
LogName = 'Application'; Level = 1, 2
StartTime = (Get-Date).AddDays(-7)
} -ErrorAction SilentlyContinue
$errorsBySource = $appErrors | Group-Object ProviderName |
Sort-Object Count -Descending | Select-Object -First 5
# Source 3: IIS traffic (if present)
$iisLogPath = "C:\inetpub\logs\LogFiles\W3SVC1"
$iisRequests = 0; $iis5xx = 0
if (Test-Path $iisLogPath) {
$recentLogs = Get-ChildItem $iisLogPath -Filter "*.log" |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) }
foreach ($log in $recentLogs) {
$lines = Get-Content $log.FullName | Where-Object { $_ -notmatch '^#' }
$iisRequests += $lines.Count
$iis5xx += ($lines | Where-Object { $_ -match ' 5\d\d ' }).Count
}
}
$body = @"
Weekly Infrastructure Digest — $reportDate
Server: $hostname
SYSTEM
Uptime: $($uptime.Days)d $($uptime.Hours)h
C: free: $diskFree GB
Memory: $([math]::Round(($os.TotalVisibleMemorySize - $os.FreePhysicalMemory)/1024)) MB used
APP ERRORS (7 days): $($appErrors.Count)
$($errorsBySource | ForEach-Object { " $($_.Name): $($_.Count)" } | Out-String)
WEB TRAFFIC (7 days)
Requests: $iisRequests
5xx errors: $iis5xx
"@
# Also attach a CSV backup of the data
$csvPath = "$env:TEMP\digest-$reportDate.csv"
[PSCustomObject]@{
Date=$reportDate; Server=$hostname; UptimeDays=$uptime.Days
DiskFreeGB=$diskFree; AppErrors=$appErrors.Count
WebRequests=$iisRequests; Web5xx=$iis5xx
} | Export-Csv $csvPath -NoTypeInformation
nylas email send --to "engineering@company.com" `
--subject "Weekly Digest: $hostname — $reportDate" `
--body $body --attach $csvPath --yes
Remove-Item $csvPathGenerate Excel files with ImportExcel
The ImportExcel module creates .xlsx files without needing Excel installed. It has over 14 million downloads on the PowerShell Gallery.
# Install ImportExcel (one time)
Install-Module ImportExcel -Scope CurrentUser -Force
# excel-report.ps1 -- Generate Excel and email it
$data = @(
[PSCustomObject]@{ Month="Jan"; Revenue=142000; Customers=89 }
[PSCustomObject]@{ Month="Feb"; Revenue=158000; Customers=97 }
[PSCustomObject]@{ Month="Mar"; Revenue=175000; Customers=112 }
)
$xlsxPath = "$env:TEMP\quarterly-report.xlsx"
# Export with chart
$chart = New-ExcelChartDefinition `
-XRange "Month" -YRange "Revenue" `
-ChartType ColumnClustered -Title "Quarterly Revenue"
$data | Export-Excel -Path $xlsxPath `
-TableName "Revenue" -AutoSize -FreezeTopRow -BoldTopRow `
-ExcelChartDefinition $chart
nylas email send --to "finance@company.com" `
--subject "Q1 Revenue Report" `
--body "Attached: Q1 revenue with chart. 3 months, $($data.Count) rows." `
--attach $xlsxPath --yes
Remove-Item $xlsxPathDatabase query report
Pull data from SQL Server, format as HTML, and email. This pattern works for daily signups, inventory counts, or any metric you track in a database.
# db-report.ps1 -- SQL Server query as HTML email
param(
[string]$Server = "db.company.local",
[string]$Database = "Production",
[string]$SendTo = "ops@company.com"
)
$signups = Invoke-Sqlcmd -ServerInstance $Server -Database $Database -Query @"
SELECT CAST(CreatedAt AS DATE) AS SignupDate,
COUNT(*) AS Total,
COUNT(CASE WHEN Plan='paid' THEN 1 END) AS Paid
FROM Users
WHERE CreatedAt >= DATEADD(day, -7, GETDATE())
GROUP BY CAST(CreatedAt AS DATE)
ORDER BY SignupDate DESC
"@
$rows = $signups | ForEach-Object {
$pct = if ($_.Total -gt 0) { [math]::Round(($_.Paid/$_.Total)*100,1) } else { 0 }
"<tr><td style='padding:6px 12px'>$($_.SignupDate.ToString('yyyy-MM-dd'))</td>" +
"<td style='padding:6px 12px;text-align:right'>$($_.Total)</td>" +
"<td style='padding:6px 12px;text-align:right'>$($_.Paid)</td>" +
"<td style='padding:6px 12px;text-align:right'>$pct%</td></tr>"
}
$totalSignups = ($signups | Measure-Object -Property Total -Sum).Sum
$totalPaid = ($signups | Measure-Object -Property Paid -Sum).Sum
$html = @"
<html><body style="font-family:system-ui;background:#0f172a;color:#e2e8f0;padding:24px">
<h1 style="color:#4ade80">Signup Report (7 days)</h1>
<p>$totalSignups signups, $totalPaid paid ($([math]::Round(($totalPaid/$totalSignups)*100,1))%)</p>
<table style="border-collapse:collapse">
<tr style="background:#1e293b">
<th style="padding:8px 12px;text-align:left">Date</th>
<th style="padding:8px 12px;text-align:right">Signups</th>
<th style="padding:8px 12px;text-align:right">Paid</th>
<th style="padding:8px 12px;text-align:right">Rate</th>
</tr>
$($rows -join "`n")
</table></body></html>
"@
nylas email send --to $SendTo `
--subject "Signups: $totalSignups total, $totalPaid paid (7 days)" `
--body $html --yesPull from REST APIs into a report
Many business metrics live in SaaS tools. Pull them via API and combine into a single email.
# api-metrics-report.ps1 -- Aggregate from multiple APIs
# GitHub repo stats
$gh = Invoke-RestMethod -Uri "https://api.github.com/repos/your-org/your-repo" `
-Headers @{ Authorization = "token $env:GITHUB_TOKEN" }
# Uptime monitoring
$uptime = Invoke-RestMethod -Uri "https://betteruptime.com/api/v2/monitors" `
-Headers @{ Authorization = "Bearer $env:UPTIME_TOKEN" }
$down = ($uptime.data | Where-Object { $_.attributes.status -ne 'up' }).Count
$body = @"
Engineering Metrics — $(Get-Date -Format 'yyyy-MM-dd')
GitHub ($($gh.full_name)):
Open issues: $($gh.open_issues_count)
Stars: $($gh.stargazers_count)
Uptime:
Monitors: $($uptime.data.Count)
Down: $down
"@
nylas email send --to "engineering@company.com" `
--subject "Engineering Metrics — $(Get-Date -Format 'yyyy-MM-dd')" `
--body $body --yesSchedule weekly reports
# Monday 8 AM weekly report
schtasks /create /tn "WeeklySalesReport" `
/tr "pwsh.exe -NoProfile -File C:\Reports\sales-report.ps1" `
/sc weekly /d MON /st 08:00 /ru "%USERNAME%"
# Weekday 7 AM daily digest
schtasks /create /tn "DailyDigest" `
/tr "pwsh.exe -NoProfile -File C:\Reports\weekly-digest.ps1" `
/sc weekly /d MON,TUE,WED,THU,FRI /st 07:00 /ru "%USERNAME%"
# Verify
schtasks /query /tn "WeeklySalesReport" /fo LISTFrequently asked questions
How do I send a CSV file as an email attachment from PowerShell?
Export your data with Export-Csv -Path report.csv, then attach it: nylas email send --attach report.csv --to recipient@company.com --subject "Report" --yes. The CLI sets the correct MIME type automatically.
Can I send HTML table reports from PowerShell?
Yes. Use ConvertTo-Html to turn PowerShell objects into HTML tables, add inline CSS (email clients strip external stylesheets), then pass the HTML to nylas email send --body $html. The CLI auto-detects HTML content.
How do I schedule weekly email reports on Windows?
Save your report script as a .ps1 file, then register with Task Scheduler: schtasks /create /tn ReportName /tr "pwsh -File script.ps1" /sc weekly /d MON /st 08:00. The task runs under your user account with access to Nylas CLI credentials.
Next steps
- Server monitoring alerts with PowerShell -- disk, service, and Event Log alerts
- Office 365 email from PowerShell -- manage O365 email without Microsoft Graph
- Send email from PowerShell -- the foundation guide for PowerShell email
- Full command reference -- every flag and subcommand