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 with Gmail, Outlook, Exchange, Yahoo, iCloud, and IMAP.
By Nick Barraclough
Setup
Install Nylas CLI and authenticate once. See the PowerShell email guide for full install options.
irm https://cli.nylas.com/install.ps1 | iex
nylas auth config # paste your API keySend 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