From de09a2e069911a77b5174c9c557d02bd904c2276 Mon Sep 17 00:00:00 2001 From: Mattia Tadini Date: Mon, 24 Nov 2025 07:52:56 +0000 Subject: [PATCH] Update send-mail.ps1 --- send-mail.ps1 | 265 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 181 insertions(+), 84 deletions(-) diff --git a/send-mail.ps1 b/send-mail.ps1 index a6c7783..64a8354 100644 --- a/send-mail.ps1 +++ b/send-mail.ps1 @@ -52,21 +52,21 @@ function Get-StatusInfo { # Default: COMPLETATO (verde) $statusText = 'COMPLETATO' - $color = '#4CAF50' # verde + $color = '#4CAF50' # verde $succ = '1' $warn = '0' $err = '0' if ($Body -match 'ESITO:\s*FALLITO') { $statusText = 'FALLITO' - $color = '#F44336' # rosso + $color = '#F44336' # rosso $succ = '0' $warn = '0' $err = '1' } elseif ($Body -match 'ESITO:\s*COMPLETATO CON WARNING') { $statusText = 'COMPLETATO CON WARNING' - $color = '#FFC107' # giallo + $color = '#FFC107' # giallo $succ = '0' $warn = '1' $err = '0' @@ -100,8 +100,7 @@ function Build-ReportHtml { if ($PSScriptRoot) { $candidateDirs += (Join-Path $PSScriptRoot 'bin/conf') - } - elseif ($PSCommandPath) { + } elseif ($PSCommandPath) { $candidateDirs += (Join-Path (Split-Path -Parent $PSCommandPath) 'bin/conf') } @@ -116,14 +115,19 @@ function Build-ReportHtml { } } + # Fallback: invio testo semplice if (-not $templatePath) { Write-Log WARN "[MAIL] Template HTML non trovato. Invio in testo semplice." $encoded = $Body -replace '&','&' -replace '<','<' -replace '>','>' $encoded = $encoded -replace "`r`n","
" -replace "`n","
" + @" -$Subject + + + $Subject + $encoded @@ -143,10 +147,14 @@ $encoded if (-not $template) { $encoded = $Body -replace '&','&' -replace '<','<' -replace '>','>' $encoded = $encoded -replace "`r`n","
" -replace "`n","
" + @" -$Subject + + + $Subject + $encoded @@ -158,54 +166,69 @@ $encoded $statusInfo = Get-StatusInfo -Body $Body # Host / agente - $agent = if ($HostName) { $HostName } elseif ($env:COMPUTERNAME) { $env:COMPUTERNAME } else { 'Sconosciuto' } + $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 {} + } catch { } - # Parsing del body - $lines = $Body -split "(`r`n|`n)" + # --- Estrazione Start / Fine dal body tramite regex --- + $startTime = '' + $endTime = '' - $startLine = $lines | Where-Object { $_ -match 'Start:' } | Select-Object -First 1 - $endLine = $lines | Where-Object { $_ -match 'Fine:' } | Select-Object -First 1 + $startMatch = [regex]::Match($Body, 'Start:\s*(\d{2}-\d{2}-\d{4}\s+\d{2}:\d{2}:\d{2})') + if ($startMatch.Success) { + $startTime = $startMatch.Groups[1].Value.Trim() + } - $startTime = if ($startLine) { ($startLine -replace '.*Start:\s*','').Trim() } else { '' } - $endTime = if ($endLine) { ($endLine -replace '.*Fine:\s*','').Trim() } else { '' } + $endMatch = [regex]::Match($Body, 'Fine:\s*(\d{2}-\d{2}-\d{4}\s+\d{2}:\d{2}:\d{2})') + if ($endMatch.Success) { + $endTime = $endMatch.Groups[1].Value.Trim() + } - $backupDateTime = if ($endTime) { $endTime } elseif ($startTime) { $startTime } else { (Get-Date).ToString('dd/MM/yyyy HH:mm:ss') } + # Se "Fine:" non è ancora presente nel log passato, uso l’istante attuale + if (-not $endTime) { + $endTime = (Get-Date).ToString('dd-MM-yyyy HH:mm:ss') + } - # Durata - provo sia con dd/MM/yyyy che dd-MM-yyyy + # Data/ora del report: uso l’ora di fine se disponibile, altrimenti quella di inizio + $backupDateTime = if ($endTime) { + $endTime + } elseif ($startTime) { + $startTime + } else { + (Get-Date).ToString('dd/MM/yyyy HH:mm:ss') + } + + # --- Calcolo durata: Fine - Inizio --- $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 + if ($startTime -and $endTime) { + $fmt = 'dd-MM-yyyy HH:mm:ss' + $culture = [System.Globalization.CultureInfo]::InvariantCulture + $startDt = [datetime]::ParseExact($startTime, $fmt, $culture) + $endDt = [datetime]::ParseExact($endTime, $fmt, $culture) + $span = $endDt - $startDt $durationStr = Format-Duration -Span $span } - } catch {} + } catch { + Write-Log WARN "[MAIL] Impossibile calcolare la durata: $_" + $durationStr = 'N/D' + } - # --- Dimensioni archivi SQL / FILES, usando in modo sicuro la variabile $moved --- + # --- Dimensioni archivi SQL / FILES --- $archives = @() + # 1) Prova con la variabile $moved (se il main script la imposta a percorsi) try { $movedVar = Get-Variable -Name moved -ErrorAction Stop $movedVal = $movedVar.Value @@ -216,7 +239,7 @@ $encoded Write-Log INFO "[MAIL] Variabile 'moved' non disponibile, provo a recuperare i .7z dal log." } - # Fallback: cerco *.7z dentro il testo del log + # 2) Fallback: cerco *.7z dentro il testo del log (percorso completo) if (-not $archives -or $archives.Count -eq 0) { $tokens = $Body -split '\s+' foreach ($tok in $tokens) { @@ -231,13 +254,58 @@ $encoded } } + # 3) Fallback avanzato: usa solo il NOME FILE e cerca in BackupRoot\Files / BackupRoot\Databases / BackupRoot\out + if (-not $archives -or $archives.Count -eq 0) { + try { + $brVar = Get-Variable -Name BackupRoot -ErrorAction SilentlyContinue + if ($brVar -and $brVar.Value) { + $br = $brVar.Value + + $dirs = @( + (Join-Path $br 'Files'), + (Join-Path $br 'Databases'), + (Join-Path $br 'out') + ) + + $tokens = $Body -split '\s+' + $names = @() + + foreach ($tok in $tokens) { + if ($tok -like '*.7z') { + $clean = $tok.Trim("`";',.") + if ($clean) { + $name = [System.IO.Path]::GetFileName($clean) + if ($name) { $names += $name } + } + } + } + + $names = $names | Select-Object -Unique + + foreach ($name in $names) { + foreach ($dir in $dirs) { + if (-not $dir) { continue } + $candidate = Join-Path $dir $name + if (Test-Path -LiteralPath $candidate) { + if (-not ($archives -contains $candidate)) { + $archives += $candidate + } + } + } + } + } + } catch { + Write-Log WARN "[MAIL] Errore nel tentativo di individuare gli archivi in Files/Databases/out: $_" + } + } + [long]$totalBytes = 0 [long]$sqlBytes = 0 [long]$fileBytes = 0 foreach ($p in $archives) { try { - $fi = Get-Item -LiteralPath $p -ErrorAction Stop + $fi = Get-Item -LiteralPath $p -ErrorAction Stop $size = [long]$fi.Length $totalBytes += $size @@ -252,17 +320,18 @@ $encoded } $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' } + $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' + $diskFs = 'N/D' + $diskRoot = $null try { - $diskRoot = $null $brVar = Get-Variable -Name BackupRoot -ErrorAction SilentlyContinue if ($brVar -and $brVar.Value) { $diskRoot = $brVar.Value @@ -273,7 +342,8 @@ $encoded if ($diskRoot) { $driveRoot = [System.IO.Path]::GetPathRoot($diskRoot) if ($driveRoot) { - $di = New-Object System.IO.DriveInfo($driveRoot) + $di = New-Object System.IO.DriveInfo($driveRoot) + $diskFs = $di.DriveFormat $diskSize = Format-Bytes -Bytes $di.TotalSize $diskAvail = Format-Bytes -Bytes $di.AvailableFreeSpace $used = $di.TotalSize - $di.AvailableFreeSpace @@ -287,27 +357,58 @@ $encoded Write-Log WARN "[MAIL] Impossibile calcolare spazio disco: $_" } - # Info destinazione (rclone) - $target = '' - $fst = '' - $login = '' - $domain = '' + # Info destinazione (rclone) + login/dominio + percorso / FS locale + $target = '' + $fst = '' + $localPathStr = if ($diskRoot) { $diskRoot } else { 'N/D' } + $localFsStr = $diskFs + + # Login/Dominio dell'utente Windows che esegue il backup + $login = if ($env:USERNAME) { $env:USERNAME } else { 'N/D' } + $domain = if ($env:USERDOMAIN) { $env:USERDOMAIN } + elseif ($env:COMPUTERNAME) { $env:COMPUTERNAME } + else { 'N/D' } 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 } + # 1) Preferisci RcloneRemoteDest (come da backup.conf) + $rdVar = Get-Variable -Name RcloneRemoteDest -ErrorAction SilentlyContinue + if ($rdVar -and $rdVar.Value) { + $target = $rdVar.Value + $fst = 'rclone' + } else { + # 2) Fallback: RcloneRemote/RcloneRemotePath se esistono + $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`:" + if ($rrVal -or $rpVal) { + if ($rpVal) { + $target = "$rrVal`:$rpVal" + } else { + $target = "$rrVal`:" + } + $fst = 'rclone' } - $fst = 'rclone' } - } catch {} + } catch { + Write-Log WARN "[MAIL] Impossibile leggere info rclone: $_" + } + + # Arricchisco i campi per la riga "Destinazione backup / File system / tipo" + if ($target -and $localPathStr -and $localFsStr) { + # Esempio: + # Destinazione backup: remote:bucket/path (locale: D:\Backup\out) + # File system / tipo: rclone / NTFS (D:) + $htmlTarget = "$target (locale: $localPathStr)" + if ($diskRoot) { + $driveRoot = [System.IO.Path]::GetPathRoot($diskRoot) + $fst = "$fst / $localFsStr ($driveRoot)" + } else { + $fst = "$fst / $localFsStr" + } + $target = $htmlTarget + } # Dettagli log in HTML $details = $Body @@ -323,9 +424,9 @@ $encoded $html = $html.Replace('XXXSTATUSXXX', $statusInfo.Text) # Info generali - $html = $html.Replace('XXXAGENTXXX', $agent) - $html = $html.Replace('XXXHOSTNAMEXXX', $agent) - $html = $html.Replace('XXXVERSIONXXX', $scriptVersion) + $html = $html.Replace('XXXAGENTXXX', $agent) + $html = $html.Replace('XXXHOSTNAMEXXX', $agent) + $html = $html.Replace('XXXVERSIONXXX', $scriptVersion) # Date / orari $html = $html.Replace('XXXBACKUPDATETIMEXXX', $backupDateTime) @@ -338,26 +439,22 @@ $encoded $html = $html.Replace('XXXWARNINGXXX', $statusInfo.Warning) $html = $html.Replace('XXXERRORXXX', $statusInfo.Error) - # Dimensioni: - # - Dimensione totale -> totale (SQL + FILES) - # - Dimensione SQL -> solo SQL - # - Dimensione Files -> solo Files - - $html = $html.Replace('XXXTOTALSIZEXXX', $sizeTotalStr) - $html = $html.Replace('XXXSQLSIZEXXX', $sizeSqlStr) + # Dimensioni + $html = $html.Replace('XXXTOTALSIZEXXX', $sizeTotalStr) + $html = $html.Replace('XXXSQLSIZEXXX', $sizeSqlStr) $html = $html.Replace('XXXFILESSIZEXXX', $sizeFilesStr) - # Destinazione backup + # Destinazione backup + login/dominio + spazio locale $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) + $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) @@ -382,11 +479,11 @@ function Send-ReportMail { 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 + $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 @@ -397,16 +494,16 @@ function Send-ReportMail { $MailTo = $mtVar.Value $smtp = New-Object System.Net.Mail.SmtpClient($MailSmtpHost, $MailSmtpPort) - $smtp.EnableSsl = $true - $smtp.DeliveryMethod = [System.Net.Mail.SmtpDeliveryMethod]::Network + $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 + $msg = New-Object System.Net.Mail.MailMessage + $msg.From = $MailFrom foreach ($rcpt in @($MailTo)) { if ($rcpt) { $msg.To.Add($rcpt) } @@ -425,4 +522,4 @@ function Send-ReportMail { } } -Send-ReportMail -Subject $Subject -Body $Body +Send-ReportMail -Subject $Subject -Body $Body \ No newline at end of file