Backup-AdHoc/send-mail.ps1
2025-11-19 11:27:59 +00:00

433 lines
14 KiB
PowerShell

param(
[Parameter(Mandatory = $true)]
[string]$Subject,
[Parameter(Mandatory = $true)]
[string]$Body
)
# Se Write-Log non esiste (esecuzione standalone), creo uno stub minimale
if (-not (Get-Command -Name Write-Log -ErrorAction SilentlyContinue)) {
function Write-Log {
param(
[string]$Level,
[string]$Message
)
$ts = Get-Date -Format 'dd/MM/yyyy HH:mm:ss'
Write-Host "[$ts] [$Level] $Message"
}
}
function Format-Bytes {
param(
[long]$Bytes
)
if ($Bytes -ge 1GB) {
return ("{0:N2} GB" -f ($Bytes / 1GB))
} elseif ($Bytes -ge 1MB) {
return ("{0:N2} MB" -f ($Bytes / 1MB))
} elseif ($Bytes -ge 1KB) {
return ("{0:N2} KB" -f ($Bytes / 1KB))
} else {
return ("{0} B" -f $Bytes)
}
}
function Format-Duration {
param(
[TimeSpan]$Span
)
if ($Span.TotalDays -ge 1) {
$days = [int]$Span.TotalDays
return ("{0}g {1:00}:{2:00}:{3:00}" -f $days, $Span.Hours, $Span.Minutes, $Span.Seconds)
} else {
return ("{0:00}:{1:00}:{2:00}" -f [int]$Span.TotalHours, $Span.Minutes, $Span.Seconds)
}
}
function Get-StatusInfo {
param(
[string]$Body
)
# Default: COMPLETATO (verde)
$statusText = 'COMPLETATO'
$color = '#4CAF50' # verde
$succ = '1'
$warn = '0'
$err = '0'
if ($Body -match 'ESITO:\s*FALLITO') {
$statusText = 'FALLITO'
$color = '#F44336' # rosso
$succ = '0'
$warn = '0'
$err = '1'
}
elseif ($Body -match 'ESITO:\s*COMPLETATO CON WARNING') {
$statusText = 'COMPLETATO CON WARNING'
$color = '#FFC107' # giallo
$succ = '0'
$warn = '1'
$err = '0'
}
[PSCustomObject]@{
Text = $statusText
Color = $color
Success = $succ
Warning = $warn
Error = $err
}
}
function Build-ReportHtml {
param(
[string]$Subject,
[string]$Body
)
# Cerca il template nella cartella di configurazione:
# 1) eventuale $script:InstallerConfDir / $InstallerConfDir
# 2) cartella bin\conf sotto la cartella dello script
# 3) percorso fisso C:\polo\scripts\bin\conf
$templatePath = $null
$candidateDirs = @()
if ($script:InstallerConfDir) { $candidateDirs += $script:InstallerConfDir }
if ($InstallerConfDir) { $candidateDirs += $InstallerConfDir }
if ($PSScriptRoot) {
$candidateDirs += (Join-Path $PSScriptRoot 'bin/conf')
}
elseif ($PSCommandPath) {
$candidateDirs += (Join-Path (Split-Path -Parent $PSCommandPath) 'bin/conf')
}
$candidateDirs += 'C:\polo\scripts\bin\conf'
foreach ($dir in $candidateDirs) {
if (-not $dir) { continue }
$path = Join-Path $dir 'report_template.html'
if (Test-Path -LiteralPath $path) {
$templatePath = $path
break
}
}
if (-not $templatePath) {
Write-Log WARN "[MAIL] Template HTML non trovato. Invio in testo semplice."
$encoded = $Body -replace '&','&amp;' -replace '<','&lt;' -replace '>','&gt;'
$encoded = $encoded -replace "`r`n","<br />" -replace "`n","<br />"
@"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>$Subject</title></head>
<body style="font-family: Tahoma, Arial, sans-serif; font-size: 12px;">
$encoded
</body>
</html>
"@
return
}
try {
# IMPORTANTE: leggi il template come UTF-8 per evitare i caratteri tipo – / Ã
$template = Get-Content -LiteralPath $templatePath -Raw -Encoding UTF8
} catch {
Write-Log WARN "[MAIL] Errore lettura template HTML: $_. Invio fallback."
$template = $null
}
if (-not $template) {
$encoded = $Body -replace '&','&amp;' -replace '<','&lt;' -replace '>','&gt;'
$encoded = $encoded -replace "`r`n","<br />" -replace "`n","<br />"
@"
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>$Subject</title></head>
<body style="font-family: Tahoma, Arial, sans-serif; font-size: 12px;">
$encoded
</body>
</html>
"@
return
}
$statusInfo = Get-StatusInfo -Body $Body
# Host / agente
$agent = if ($HostName) { $HostName } elseif ($env:COMPUTERNAME) { $env:COMPUTERNAME } else { 'Sconosciuto' }
# Versione script (se definita nel main, es: $ScriptVersion)
$scriptVersion = 'N/D'
try {
$sv = Get-Variable -Name ScriptVersion -ErrorAction SilentlyContinue
if ($sv) { $scriptVersion = $sv.Value }
} catch {}
# Parsing del body
$lines = $Body -split "(`r`n|`n)"
$startLine = $lines | Where-Object { $_ -match 'Start:' } | Select-Object -First 1
$endLine = $lines | Where-Object { $_ -match 'Fine:' } | Select-Object -First 1
$startTime = if ($startLine) { ($startLine -replace '.*Start:\s*','').Trim() } else { '' }
$endTime = if ($endLine) { ($endLine -replace '.*Fine:\s*','').Trim() } else { '' }
$backupDateTime = if ($endTime) { $endTime } elseif ($startTime) { $startTime } else { (Get-Date).ToString('dd/MM/yyyy HH:mm:ss') }
# Durata - provo sia con dd/MM/yyyy che dd-MM-yyyy
$durationStr = ''
try {
$culture = [System.Globalization.CultureInfo]::GetCultureInfo('it-IT')
$formats = @('dd/MM/yyyy HH:mm:ss','dd-MM-yyyy HH:mm:ss')
[datetime]$startDt = $null
[datetime]$endDt = $null
if ($startTime) {
foreach ($fmt in $formats) {
if ([datetime]::TryParseExact($startTime, $fmt, $culture, [System.Globalization.DateTimeStyles]::None, [ref]$startDt)) { break }
}
}
if ($endTime) {
foreach ($fmt in $formats) {
if ([datetime]::TryParseExact($endTime, $fmt, $culture, [System.Globalization.DateTimeStyles]::None, [ref]$endDt)) { break }
}
}
if ($startDt -and $endDt) {
$span = $endDt - $startDt
$durationStr = Format-Duration -Span $span
}
} catch {}
# --- Dimensioni archivi SQL / FILES, usando in modo sicuro la variabile $moved ---
$archives = @()
try {
$movedVar = Get-Variable -Name moved -ErrorAction Stop
$movedVal = $movedVar.Value
if ($movedVal) {
$archives = @($movedVal) | Where-Object { $_ -and (Test-Path -LiteralPath $_) }
}
} catch {
Write-Log INFO "[MAIL] Variabile 'moved' non disponibile, provo a recuperare i .7z dal log."
}
# Fallback: cerco *.7z dentro il testo del log
if (-not $archives -or $archives.Count -eq 0) {
$tokens = $Body -split '\s+'
foreach ($tok in $tokens) {
if ($tok -like '*.7z') {
$path = $tok.Trim("`";',.")
if ($path -and (Test-Path -LiteralPath $path)) {
if (-not ($archives -contains $path)) {
$archives += $path
}
}
}
}
}
[long]$totalBytes = 0
[long]$sqlBytes = 0
[long]$fileBytes = 0
foreach ($p in $archives) {
try {
$fi = Get-Item -LiteralPath $p -ErrorAction Stop
$size = [long]$fi.Length
$totalBytes += $size
if ($fi.Name -match '^SQL_') {
$sqlBytes += $size
} elseif ($fi.Name -match '^FILES_') {
$fileBytes += $size
}
} catch {
Write-Log WARN ("[MAIL] Impossibile leggere dimensione archivio {0}: {1}" -f $p, $_)
}
}
$sizeTotalStr = if ($totalBytes -gt 0) { Format-Bytes -Bytes $totalBytes } else { 'N/D' }
$sizeSqlStr = if ($sqlBytes -gt 0) { Format-Bytes -Bytes $sqlBytes } else { 'N/D' }
$sizeFilesStr = if ($fileBytes -gt 0) { Format-Bytes -Bytes $fileBytes } else { 'N/D' }
# Info disco (unità di BackupRoot o del primo archivio)
$diskSize = 'N/D'
$diskUsed = 'N/D'
$diskAvail = 'N/D'
$diskUsePct = 'N/D'
try {
$diskRoot = $null
$brVar = Get-Variable -Name BackupRoot -ErrorAction SilentlyContinue
if ($brVar -and $brVar.Value) {
$diskRoot = $brVar.Value
} elseif ($archives.Count -gt 0) {
$diskRoot = $archives[0]
}
if ($diskRoot) {
$driveRoot = [System.IO.Path]::GetPathRoot($diskRoot)
if ($driveRoot) {
$di = New-Object System.IO.DriveInfo($driveRoot)
$diskSize = Format-Bytes -Bytes $di.TotalSize
$diskAvail = Format-Bytes -Bytes $di.AvailableFreeSpace
$used = $di.TotalSize - $di.AvailableFreeSpace
$diskUsed = Format-Bytes -Bytes $used
if ($di.TotalSize -gt 0) {
$diskUsePct = ('{0:N1}%%' -f (($used / $di.TotalSize) * 100))
}
}
}
} catch {
Write-Log WARN "[MAIL] Impossibile calcolare spazio disco: $_"
}
# Info destinazione (rclone)
$target = ''
$fst = ''
$login = ''
$domain = ''
try {
$rrVar = Get-Variable -Name RcloneRemote -ErrorAction SilentlyContinue
$rpVar = Get-Variable -Name RcloneRemotePath -ErrorAction SilentlyContinue
$rrVal = if ($rrVar) { $rrVar.Value } else { $null }
$rpVal = if ($rpVar) { $rpVar.Value } else { $null }
if ($rrVal -or $rpVal) {
if ($rpVal) {
$target = "$rrVal`:$rpVal"
} else {
$target = "$rrVal`:"
}
$fst = 'rclone'
}
} catch {}
# Dettagli log in HTML
$details = $Body
$details = $details -replace '&','&amp;' -replace '<','&lt;' -replace '>','&gt;'
$details = $details -replace "`r`n","<br />" -replace "`n","<br />"
# Sostituzione placeholder nel template
$html = $template
# Stato e colori (verde / giallo / rosso)
$html = $html.Replace('XXXBGCOLORXXX', $statusInfo.Color)
$html = $html.Replace('XXXSTATXXX', $statusInfo.Text)
$html = $html.Replace('XXXSTATUSXXX', $statusInfo.Text)
# Info generali
$html = $html.Replace('XXXAGENTXXX', $agent)
$html = $html.Replace('XXXHOSTNAMEXXX', $agent)
$html = $html.Replace('XXXVERSIONXXX', $scriptVersion)
# Date / orari
$html = $html.Replace('XXXBACKUPDATETIMEXXX', $backupDateTime)
$html = $html.Replace('XXXSTARTXXX', $startTime)
$html = $html.Replace('XXXENDXXX', $endTime)
$html = $html.Replace('XXXDURATIONXXX', $durationStr)
# Contatori esito
$html = $html.Replace('XXXSUCCESSXXX', $statusInfo.Success)
$html = $html.Replace('XXXWARNINGXXX', $statusInfo.Warning)
$html = $html.Replace('XXXERRORXXX', $statusInfo.Error)
# Dimensioni:
# - Dimensione totale -> totale (SQL + FILES)
# - Dimensione backup -> solo FILES
# - Dati letti -> solo SQL
# - Dettagli / Dimensione -> totale
# - Dettagli / Letti -> solo SQL
# - Dettagli / Trasferiti -> solo FILES
$html = $html.Replace('XXXTOTALSIZEXXX', $sizeTotalStr)
$html = $html.Replace('XXXBACKUPSIZEXXX', $sizeFilesStr)
$html = $html.Replace('XXXDATAREADXXX', $sizeSqlStr)
$html = $html.Replace('XXXREADXXX', $sizeSqlStr)
$html = $html.Replace('XXXTRANSFERREDXXX', $sizeFilesStr)
# Destinazione backup
$html = $html.Replace('XXXTARGETXXX', $target)
$html = $html.Replace('XXXFSTXXX', $fst)
$html = $html.Replace('XXXLOGINXXX', $login)
$html = $html.Replace('XXXDOMAINXXX', $domain)
# Info disco
$html = $html.Replace('XXXDISKSIZEXXX', $diskSize)
$html = $html.Replace('XXXDISKUSEDXXX', $diskUsed)
$html = $html.Replace('XXXDISKAVAILXXX', $diskAvail)
$html = $html.Replace('XXXDISKUSEPXXX', $diskUsePct)
# Dettaglio log
$html = $html.Replace('XXXDETAILSXXX', $details)
return $html
}
function Send-ReportMail {
param(
[string]$Subject,
[string]$Body
)
$meVar = Get-Variable -Name MailEnabled -ErrorAction SilentlyContinue
if (-not $meVar -or -not $meVar.Value) {
Write-Log INFO "[MAIL] Invio mail disabilitato (MailEnabled=false o non definito)."
return
}
$htmlBody = Build-ReportHtml -Subject $Subject -Body $Body
try {
$shVar = Get-Variable -Name MailSmtpHost -ErrorAction Stop
$spVar = Get-Variable -Name MailSmtpPort -ErrorAction Stop
$maVar = Get-Variable -Name MailUseAuth -ErrorAction SilentlyContinue
$muVar = Get-Variable -Name MailUser -ErrorAction SilentlyContinue
$mpVar = Get-Variable -Name MailPassword -ErrorAction SilentlyContinue
$mfVar = Get-Variable -Name MailFrom -ErrorAction Stop
$mtVar = Get-Variable -Name MailTo -ErrorAction Stop
$MailSmtpHost = $shVar.Value
$MailSmtpPort = $spVar.Value
$MailUseAuth = if ($maVar) { $maVar.Value } else { $false }
$MailUser = if ($muVar) { $muVar.Value } else { $null }
$MailPassword = if ($mpVar) { $mpVar.Value } else { $null }
$MailFrom = $mfVar.Value
$MailTo = $mtVar.Value
$smtp = New-Object System.Net.Mail.SmtpClient($MailSmtpHost, $MailSmtpPort)
$smtp.EnableSsl = $true
$smtp.DeliveryMethod = [System.Net.Mail.SmtpDeliveryMethod]::Network
$smtp.UseDefaultCredentials = $false
if ($MailUseAuth -and $MailUser -and $MailPassword) {
$smtp.Credentials = New-Object System.Net.NetworkCredential($MailUser, $MailPassword)
}
$msg = New-Object System.Net.Mail.MailMessage
$msg.From = $MailFrom
foreach ($rcpt in @($MailTo)) {
if ($rcpt) { $msg.To.Add($rcpt) }
}
$msg.Subject = $Subject
$msg.Body = $htmlBody
$msg.IsBodyHtml = $true
$msg.BodyEncoding = [System.Text.Encoding]::UTF8
$msg.SubjectEncoding = [System.Text.Encoding]::UTF8
$smtp.Send($msg)
Write-Log INFO "[MAIL] Inviata correttamente (HTML con template)."
} catch {
Write-Log ERROR "[MAIL] Errore invio: $_"
}
}
Send-ReportMail -Subject $Subject -Body $Body