diff --git a/install-AdHoc-Backup-GUI.ps1 b/install-AdHoc-Backup-GUI.ps1 index e46a6bf..7fc44dc 100644 --- a/install-AdHoc-Backup-GUI.ps1 +++ b/install-AdHoc-Backup-GUI.ps1 @@ -1,73 +1,193 @@ -#Requires -Version 5.1 <# -Installer AdHoc Backup (GUI) - v1.3.8 -- FIX definitivo accenti in GUI su PowerShell 5.1: questo file è salvato in UTF-8 **con BOM** -- Mantiene: font Segoe UI, UseCompatibleTextRendering, auto-detect encoding per backup.conf, salvataggio UTF-8 no BOM -- Solo 7zr.exe, install di Rclone & 7zr su "Installa", layout bin/conf, schedulazione, chiusura processi figli +Installer AdHoc Backup (GUI) - v1.4.3 +- Download automatico: + - AdHoc-Backup.ps1 + - send-mail.ps1 + - backup.conf + - report_template.html +- Pulsante "Test mail" che usa i parametri correnti di backup.conf +- Fix: + - niente #Requires in testa + - Enable-TextRendering robusta + - Parse-BackupConf che preserva TUTTA la struttura del file + (commenti, TAB, spazi, ordine righe, ecc.) e modifica solo i valori + - ScheduledTaskAction con argomenti stringa + - URL configurabili per send-mail.ps1 e report_template.html + - Checkbox "Eseguire il backup subito dopo l'installazione": + * se spuntata → il backup parte alla chiusura della GUI + * se non spuntata → nessun backup automatico #> Add-Type -AssemblyName System.Windows.Forms Add-Type -AssemblyName System.Drawing Add-Type -AssemblyName System.IO.Compression.FileSystem -# Abilita stile moderno WinForms [System.Windows.Forms.Application]::EnableVisualStyles() $ErrorActionPreference = 'Stop' -# ------------------------------ HELPERS GRAFICI ------------------------------- -function New-Point([object]$x,[object]$y) { try { return New-Object System.Drawing.Point ([int]$x), ([int]$y) } catch { return [System.Drawing.Point]::new([int]$x, [int]$y) } } -function New-Size([object]$w,[object]$h) { try { return New-Object System.Drawing.Size ([int]$w), ([int]$h) } catch { return [System.Drawing.Size]::new([int]$w, [int]$h) } } -function Set-Loc($ctrl,[object]$x,[object]$y){ $ctrl.Location = New-Point $x $y } -function Set-Sz($ctrl,[object]$w,[object]$h){ $ctrl.Size = New-Size $w $h } +# ------------------------------ GLOBALI PER RUN DOPO CHIUSURA ---------------- +$global:RunBackupOnClose = $false +$global:AfterInstallScriptPath = $null +$global:AfterInstallConfPath = $null -# Abilita rendering compatibile (GDI+) su etichette/pulsanti/checkbox ecc. -function Enable-TextRendering($container){ - foreach($c in $container.Controls){ - if ($c -and ($c.PSObject.Properties['UseCompatibleTextRendering'])){ - $c.UseCompatibleTextRendering = $true +# ------------------------------ FUNZIONI DI UTILITÀ --------------------------- +function Ensure-Dir { + param([Parameter(Mandatory)][string]$Path) + if (-not (Test-Path -LiteralPath $Path)) { + New-Item -ItemType Directory -Path $Path -Force | Out-Null + } +} + +# Log su TextBox e file (nessun ValidateSet, accetta qualsiasi "Level") +$global:LogTextBox = $null +$global:LogFile = $null +function Write-Log { + param( + [string]$Message, + [string]$Level = 'INFO' + ) + + if ([string]::IsNullOrWhiteSpace($Level)) { + $Level = 'INFO' + } + + $ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' + $line = "[{0}] [{1}] {2}" -f $ts,$Level,$Message + + if ($global:LogTextBox) { + if ($global:LogTextBox.InvokeRequired) { + $null = $global:LogTextBox.Invoke( + [Action]{ $global:LogTextBox.AppendText($line + [Environment]::NewLine) } + ) + } else { + $global:LogTextBox.AppendText($line + [Environment]::NewLine) } - if ($c.Controls -and $c.Controls.Count -gt 0){ + } + if ($global:LogFile) { + Add-Content -LiteralPath $global:LogFile -Value $line + } +} + +function Set-Loc($control, [int]$x, [int]$y) { + $control.Location = New-Object System.Drawing.Point($x,$y) +} +function Set-Sz($control, [int]$w, [int]$h) { + $control.Size = New-Object System.Drawing.Size($w,$h) +} + +# Rendering compatibile per controlli testuali (evita errori se la proprietà non esiste) +function Enable-TextRendering($container) { + if (-not $container) { return } + + try { + if ($container.PSObject -and $container.PSObject.Properties['UseCompatibleTextRendering']) { + $container.UseCompatibleTextRendering = $true + } + } catch { + # ignorato + } + + if ($container.Controls -and $container.Controls.Count -gt 0) { + foreach ($c in $container.Controls) { Enable-TextRendering $c } } } -# ------------------------------ LOG ------------------------------------------- -function New-LogTimestamp { (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') } -$global:LogFile = $null -$global:LogTextBox = $null -function Write-Log { - param([Parameter(Mandatory)][string]$Message,[ValidateSet('INFO','ERR','WARN')] [string]$Level = 'INFO') - $line = "[{0}][{1}] {2}" -f (New-LogTimestamp), $Level, $Message - if ($global:LogTextBox) { try { $global:LogTextBox.AppendText("$line`r`n") } catch {} } - if ($global:LogFile) { try { Add-Content -Path $global:LogFile -Value $line -Encoding UTF8 } catch {} } - Write-Host $line -} - -# ------------------------------ UTILS ----------------------------------------- -function Ensure-Dir { param([Parameter(Mandatory)][string]$Path) - if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path | Out-Null; Write-Log "Cartella creata: $Path" } - else { Write-Log "Cartella già presente: $Path" } -} - function Start-Download { - param([Parameter(Mandatory)][string]$Url,[Parameter(Mandatory)][string]$Dest) - Write-Log "Scarico: $Url"; Write-Log "Destinazione: $Dest" - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls + param( + [Parameter(Mandatory)][string]$Url, + [Parameter(Mandatory)][string]$Dest + ) + Write-Log "Scarico: $Url" + Write-Log "Destinazione: $Dest" + [Net.ServicePointManager]::SecurityProtocol = + [Net.SecurityProtocolType]::Tls12 -bor + [Net.SecurityProtocolType]::Tls11 -bor + [Net.SecurityProtocolType]::Tls + try { Invoke-WebRequest -Uri $Url -OutFile $Dest -ErrorAction Stop - Write-Log "Download completato."; return $true + Write-Log "Download completato." + return $true } catch { Write-Log "Invoke-WebRequest fallito: $($_.Exception.Message)" 'WARN' - try { (New-Object System.Net.WebClient).DownloadFile($Url, $Dest); Write-Log "Download completato con WebClient (fallback)."; return $true } - catch { Write-Log "Download fallito: $($_.Exception.Message)" 'ERR'; return $false } + try { + (New-Object System.Net.WebClient).DownloadFile($Url,$Dest) + Write-Log "Download completato con WebClient (fallback)." + return $true + } catch { + Write-Log "Download fallito: $($_.Exception.Message)" 'ERR' + return $false + } } } +# Layout standard: root\bin\conf, root\bin\rclone, root\bin\7zip +function Ensure-ToolsLayout { + param([Parameter(Mandatory)][string]$Root) + $root = (Resolve-Path -LiteralPath $Root).ProviderPath + $bin = Join-Path $root 'bin' + $conf = Join-Path $bin 'conf' + $rc = Join-Path $bin 'rclone' + $sz = Join-Path $bin '7zip' + Ensure-Dir $root + Ensure-Dir $bin + Ensure-Dir $conf + Ensure-Dir $rc + Ensure-Dir $sz + [pscustomobject]@{ + Root = $root + Bin = $bin + Conf = $conf + RClone = $rc + SevenZip = $sz + } +} + +# Autoinstall Rclone portabile in $RcloneDir +function Ensure-RcloneExe { + param([Parameter(Mandatory)][string]$RcloneDir) + Ensure-Dir $RcloneDir + $exe = Join-Path $RcloneDir 'rclone.exe' + if (-not (Test-Path -LiteralPath $exe)) { + $zip = Join-Path $RcloneDir 'rclone-current-windows-amd64.zip' + if (Start-Download -Url 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' -Dest $zip) { + Expand-Zip -ZipPath $zip -DestDir $RcloneDir + $found = Get-ChildItem -LiteralPath $RcloneDir -Recurse -Filter 'rclone.exe' | Select-Object -First 1 + if ($found) { + Copy-Item -LiteralPath $found.FullName -Destination $exe -Force + } + Get-ChildItem -LiteralPath $RcloneDir -Directory | + Where-Object { $_.Name -notlike 'rclone*' } | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue + } + } + return $exe +} + +# Solo 7zr.exe +function Ensure-SevenZipExe { + param([Parameter(Mandatory)][string]$SevenZipDir) + Ensure-Dir $SevenZipDir + $exe = Join-Path $SevenZipDir '7zr.exe' + if (-not (Test-Path -LiteralPath $exe)) { + $zipUrl = 'https://www.7-zip.org/a/7zr.exe' + if (Start-Download -Url $zipUrl -Dest $exe) { + Write-Log "7zr.exe scaricato in $exe" + } + } + return $exe +} + # Estrattore ZIP robusto (overwrite = true) function Expand-Zip { - param([Parameter(Mandatory)][string]$ZipPath,[Parameter(Mandatory)][string]$DestDir) + param( + [Parameter(Mandatory)][string]$ZipPath, + [Parameter(Mandatory)][string]$DestDir + ) Write-Log "Estrazione ZIP: $ZipPath -> $DestDir" $zip = $null try { @@ -88,95 +208,89 @@ function Expand-Zip { } function New-DailyOrWeeklyTask { - param([Parameter(Mandatory)][string]$TaskName, - [Parameter(Mandatory)][string]$ScriptPath, - [Parameter(Mandatory)][string]$ConfigPath, - [Parameter(Mandatory)][DateTime]$Time, - [ValidateSet('DAILY','WEEKLY')] [string]$Schedule = 'DAILY', - [ValidateSet('MON','TUE','WED','THU','FRI','SAT','SUN')] [string]$Day = 'MON', - [switch]$Quiet) - $psArgs = @('-NoProfile','-ExecutionPolicy','Bypass','-File',('"{0}"' -f $ScriptPath),'-Config',('"{0}"' -f $ConfigPath)) - if ($Quiet) { $psArgs += '-Quiet' } - $tr = 'powershell.exe ' + ($psArgs -join ' ') - $st = $Time.ToString('HH:mm') - $cmd = @('/Create','/SC',$Schedule,'/TN',('"{0}"' -f $TaskName),'/TR',('"{0}"' -f $tr),'/ST',$st,'/RL','HIGHEST','/F') - if ($Schedule -eq 'WEEKLY') { $cmd += @('/D',$Day) } - Write-Log "Creo/aggiorno attività pianificata: $TaskName ($Schedule) alle $st" - try { schtasks.exe @cmd | Out-Null; Write-Log "Attività pianificata impostata."; return $true } - catch { Write-Log "Creazione attività fallita: $($_.Exception.Message)" 'ERR'; return $false } -} + param( + [Parameter(Mandatory)][string]$TaskName, + [Parameter(Mandatory)][string]$ScriptPath, + [Parameter(Mandatory)][string]$ConfigPath, + [Parameter(Mandatory)][DateTime]$Time, + [ValidateSet('DAILY','WEEKLY')] [string]$Schedule = 'DAILY', + [string]$Day = 'MON', + [switch]$Quiet + ) -# ------------------------------ LAYOUT BIN/CONF ------------------------------- -function Ensure-ToolsLayout { - param([Parameter(Mandatory)][string]$Root) - $binRoot = Join-Path $Root 'bin' - $confRoot = Join-Path $binRoot 'conf' - $rcloneDir= Join-Path $binRoot 'RClone' - $sevenDir = Join-Path $binRoot '7Zip' - Ensure-Dir $binRoot - Ensure-Dir $confRoot - Ensure-Dir $rcloneDir - Ensure-Dir $sevenDir - return @{ Bin=$binRoot; Conf=$confRoot; RClone=$rcloneDir; SevenZip=$sevenDir } -} + $argList = @( + '-NoProfile', + '-ExecutionPolicy','Bypass', + '-File', ('"{0}"' -f $ScriptPath), + '-Config', ('"{0}"' -f $ConfigPath) + ) + $argString = [string]::Join(' ', $argList) -function Ensure-RcloneExe { - param([Parameter(Mandatory)][string]$RcloneDir) - $exe = Join-Path $RcloneDir 'rclone.exe' - if (-not (Test-Path -LiteralPath $exe)) { - $zip = Join-Path $RcloneDir 'rclone-current-windows-amd64.zip' - if (Start-Download -Url 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' -Dest $zip) { - Expand-Zip -ZipPath $zip -DestDir $RcloneDir - $found = Get-ChildItem -LiteralPath $RcloneDir -Recurse -Filter 'rclone.exe' | Select-Object -First 1 - if ($found) { Copy-Item -LiteralPath $found.FullName -Destination $exe -Force } - Get-ChildItem -LiteralPath $RcloneDir -Directory | Where-Object { $_.Name -like 'rclone-*' } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue - Remove-Item -LiteralPath $zip -Force -ErrorAction SilentlyContinue - } - } - return $exe -} + $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $argString -# === SOLO 7zr.exe ============================================================= -function Ensure-SevenZipExe { - param([Parameter(Mandatory)][string]$SevenZipDir) - $exe = Join-Path $SevenZipDir '7zr.exe' - if (-not (Test-Path -LiteralPath $exe)) { - $url7zr = 'https://www.7-zip.org/a/7zr.exe' - if (Start-Download -Url $url7zr -Dest $exe) { - Write-Log "Installato 7-Zip minimal: $exe" + $trigger = + if ($Schedule -eq 'WEEKLY') { + New-ScheduledTaskTrigger -Weekly -DaysOfWeek $Day -At $Time } else { - Write-Log "Impossibile installare 7-Zip (7zr.exe)." 'ERR' + New-ScheduledTaskTrigger -Daily -At $Time } + + $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries + $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -RunLevel Highest + $task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings + + if ($Quiet) { + Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force | Out-Null + } else { + Register-ScheduledTask -TaskName $TaskName -InputObject $task -Force } - return $exe } -# ------------------------------ CONF PARSER ----------------------------------- +# Traccia processi figli $global:ChildPids = New-Object System.Collections.ArrayList -function Track-Child($p){ if ($p -and $p.Id) { [void]$global:ChildPids.Add($p.Id) } } +function Track-Child($p) { + if ($p -and $p.Id) { [void]$global:ChildPids.Add($p.Id) } +} $global:ConfUi = @{ - Map = [ordered]@{} - Order = @() - Controls = @{} - SourceLines = @() - TempPath = Join-Path $env:TEMP 'adhoc_backup_conf_preview.conf' + Map = [ordered]@{} + Order = @() + Controls = @{} + SourceLines = @() + LineMeta = @{} # per chiave: LineIndex, Prefix, PrefixWS, SuffixWS, Comment + LineIndexToKey = @{} # LineIndex -> Key + TempPath = Join-Path $env:TEMP 'adhoc_backup_conf_preview.conf' } -function Is-BoolString([string]$s){ if ($null -eq $s) { return $false } - switch ($s.Trim().ToLowerInvariant()) { 'true' {1};'false' {1};'1' {1};'0' {1};'yes' {1};'no' {1};'y' {1};'n' {1}; default {0} } } +function Is-BoolString([string]$s){ + if ($null -eq $s) { return $false } + switch ($s.Trim().ToLowerInvariant()) { + 'true' {1} + 'false' {1} + '1' {1} + '0' {1} + 'yes' {1} + 'no' {1} + 'y' {1} + 'n' {1} + default {0} + } +} -# Auto-detect encoding (UTF-8/UTF-16/ANSI). Preferisce UTF-8, ma cade su ANSI se necessario. +# Auto-detect encoding function Read-FileAuto { param([Parameter(Mandatory)][string]$Path) $bytes = [System.IO.File]::ReadAllBytes($Path) - if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { + if ($bytes.Length -ge 3 -and + $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { $text = [System.Text.Encoding]::UTF8.GetString($bytes,3,$bytes.Length-3) - } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { - $enc = [System.Text.Encoding]::Unicode + } elseif ($bytes.Length -ge 2 -and + $bytes[0] -eq 0xFF -and $bytes[1] -eq 0xFE) { + $enc = [System.Text.Encoding]::Unicode $text = $enc.GetString($bytes,2,$bytes.Length-2) - } elseif ($bytes.Length -ge 2 -and $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { - $enc = [System.Text.Encoding]::BigEndianUnicode + } elseif ($bytes.Length -ge 2 -and + $bytes[0] -eq 0xFE -and $bytes[1] -eq 0xFF) { + $enc = [System.Text.Encoding]::BigEndianUnicode $text = $enc.GetString($bytes,2,$bytes.Length-2) } else { $utf8Strict = New-Object System.Text.UTF8Encoding($false,$true) @@ -185,55 +299,129 @@ function Read-FileAuto { } catch { $text = [System.Text.Encoding]::Default.GetString($bytes) } - if ($text -match ([char]0xFFFD)) { # carattere di sostituzione presente => probabile encoding errato - $alt = [System.Text.Encoding]::Default.GetString($bytes) - $count1 = ($text.ToCharArray() | Where-Object { $_ -eq [char]0xFFFD }).Count - $count2 = ($alt.ToCharArray() | Where-Object { $_ -eq [char]0xFFFD }).Count - if ($count2 -lt $count1) { $text = $alt } + if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { + $text = $text.Substring(1) } } - return $text -split "`r?`n" + return $text } +# Salva in UTF-8 senza BOM +function Write-FileUtf8NoBom { + param( + [Parameter(Mandatory)][string]$Path, + [Parameter(Mandatory)][string]$Content + ) + $utf8NoBom = New-Object System.Text.UTF8Encoding($false) + [System.IO.File]::WriteAllText($Path,$Content,$utf8NoBom) +} + +# backup.conf parsing: +# - mantiene SourceLines intatte +# - per ogni chiave tiene traccia di come ricostruire la riga +# (Prefix, WS prima/ dopo valore, Comment) function Parse-BackupConf { param([Parameter(Mandatory)][string]$Path) - $order = New-Object System.Collections.ArrayList - $map = [ordered]@{} - $lines = Read-FileAuto -Path $Path - foreach ($line in $lines) { + $global:ConfUi.Map.Clear() + $global:ConfUi.Order = @() + $global:ConfUi.SourceLines = @() + $global:ConfUi.LineMeta = @{} + $global:ConfUi.LineIndexToKey = @{} + + $text = Read-FileAuto -Path $Path + $lines = $text -split "(`r`n|`n)" + + for ($i = 0; $i -lt $lines.Count; $i++) { + $line = $lines[$i] + $global:ConfUi.SourceLines += $line $trim = $line.Trim() - if ($trim -eq '' -or $trim.StartsWith('#') -or $trim.StartsWith(';')) { [void]$order.Add(@{type='comment';text=$line}); continue } - $idx = $trim.IndexOf('='); if ($idx -lt 1) { [void]$order.Add(@{type='raw';text=$line}); continue } - $k = $trim.Substring(0,$idx).Trim(); $v = $trim.Substring($idx+1).Trim() - $v = ($v -replace '(\s+)[#;].*$', '').Trim() - if (($v.StartsWith('"') -and $v.EndsWith('"')) -or ($v.StartsWith("'") -and $v.EndsWith("'"))) { $v = $v.Substring(1,$v.Length-2) } - $map[$k] = $v; [void]$order.Add(@{type='kv';key=$k}) + if ($trim -eq '' -or $trim.StartsWith('#')) { continue } + + $eqIndex = $line.IndexOf('=') + if ($eqIndex -lt 0) { continue } + + $keyPart = $line.Substring(0,$eqIndex) + $key = $keyPart.Trim() + if ([string]::IsNullOrWhiteSpace($key)) { continue } + + $rest = $line.Substring($eqIndex + 1) + + $commentIdx = $rest.IndexOf('#') + if ($commentIdx -ge 0) { + $valArea = $rest.Substring(0,$commentIdx) + $comment = $rest.Substring($commentIdx) + } else { + $valArea = $rest + $comment = '' + } + + $valTrim = $valArea.Trim() + + # Calcolo spazi/tab prima e dopo il valore all'interno di valArea + $prefixWS = '' + $suffixWS = '' + if ($valTrim -ne '') { + $firstIdx = $valArea.IndexOf($valTrim) + if ($firstIdx -ge 0) { + $prefixWS = $valArea.Substring(0,$firstIdx) + $suffixWS = $valArea.Substring($firstIdx + $valTrim.Length) + } + } + + if (-not $global:ConfUi.Map.Contains($key)) { + $global:ConfUi.Map[$key] = $valTrim + $global:ConfUi.Order += $key + } else { + $global:ConfUi.Map[$key] = $valTrim + } + + $meta = [pscustomobject]@{ + LineIndex = $i + Prefix = $line.Substring(0,$eqIndex + 1) + PrefixWS = $prefixWS + SuffixWS = $suffixWS + Comment = $comment + } + + $global:ConfUi.LineMeta[$key] = $meta + $global:ConfUi.LineIndexToKey[$i] = $key } - $global:ConfUi.Map=$map; $global:ConfUi.Order=$order; $global:ConfUi.SourceLines=$lines } -function Save-BackupConf { param([Parameter(Mandatory)][string]$Path) - if (-not $global:ConfUi.Map) { throw "Mappa configurazione non inizializzata." } - $out = New-Object System.Collections.Generic.List[string] - foreach ($entry in $global:ConfUi.Order){ - if ($entry.type -eq 'kv'){ - $k=$entry.key; $v=$global:ConfUi.Map[$k] - if (Is-BoolString $v) { $v = ([string]$v).Trim().ToLowerInvariant(); if ($v -in @('1','yes','y')){$v='true'} elseif ($v -in @('0','no','n')){$v='false'} } - $out.Add("$k=$v") - } else { $out.Add($entry.text) } +# Salva modificando SOLO le righe che contengono chiavi note, +# lasciando tutto il resto del file identico all'originale +function Save-BackupConf { + param([Parameter(Mandatory)][string]$Path) + + $sb = New-Object System.Text.StringBuilder + + for ($i = 0; $i -lt $global:ConfUi.SourceLines.Count; $i++) { + $line = $global:ConfUi.SourceLines[$i] + + if ($global:ConfUi.LineIndexToKey.ContainsKey($i)) { + $key = $global:ConfUi.LineIndexToKey[$i] + if ($global:ConfUi.Map.Contains($key) -and $global:ConfUi.LineMeta.Contains($key)) { + $val = $global:ConfUi.Map[$key] + $m = $global:ConfUi.LineMeta[$key] + $newLine = '{0}{1}{2}{3}{4}' -f $m.Prefix,$m.PrefixWS,$val,$m.SuffixWS,$m.Comment + [void]$sb.AppendLine($newLine) + } else { + [void]$sb.AppendLine($line) + } + } else { + [void]$sb.AppendLine($line) + } } - foreach ($k in $global:ConfUi.Map.Keys){ - if (-not ($global:ConfUi.Order | Where-Object { $_.type -eq 'kv' -and $_.key -eq $k })) { $out.Add("$k=$($global:ConfUi.Map[$k])") } - } - # UTF-8 no BOM - $utf8NoBom = New-Object System.Text.UTF8Encoding($false) - [System.IO.File]::WriteAllLines($Path, $out, $utf8NoBom) + + Write-FileUtf8NoBom -Path $Path -Content $sb.ToString() } # ------------------------------ DEFAULTS -------------------------------------- -$defaultInstall = 'C:\polo\scripts' -$defaultScriptUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/AdHoc-Backup.ps1' -$defaultConfUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/backup.conf' +$defaultInstall = 'C:\polo\scripts' +$defaultScriptUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/AdHoc-Backup.ps1' +$defaultConfUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/backup.conf' +$defaultMailScriptUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/send-mail.ps1' +$defaultReportTemplateUrl = 'https://gitea.poloinformatico.it/Mattia/Backup-AdHoc/raw/branch/main/report_template.html' # ------------------------------ UI -------------------------------------------- $form = New-Object System.Windows.Forms.Form @@ -241,103 +429,255 @@ $form.Text = 'Installer AdHoc Backup' $form.StartPosition = 'CenterScreen' $form.MaximizeBox = $true $form.FormBorderStyle = 'FixedDialog' -# Font globale moderno con pieno supporto accenti $form.Font = New-Object System.Drawing.Font('Segoe UI', 9) -Set-Sz $form 900 860 +Set-Sz $form 900 950 -# Install path +# Cartella installazione $lblInstall = New-Object System.Windows.Forms.Label -$lblInstall.Text = 'Cartella di installazione'; Set-Loc $lblInstall 10 12; Set-Sz $lblInstall 200 20; $form.Controls.Add($lblInstall) +$lblInstall.Text = 'Cartella di installazione' +Set-Loc $lblInstall 10 12 +Set-Sz $lblInstall 200 20 +$form.Controls.Add($lblInstall) $txtInstall = New-Object System.Windows.Forms.TextBox -Set-Loc $txtInstall 10 32; Set-Sz $txtInstall 760 24; $txtInstall.Text = $defaultInstall; $form.Controls.Add($txtInstall) +Set-Loc $txtInstall 10 32 +Set-Sz $txtInstall 760 24 +$txtInstall.Text = $defaultInstall +$form.Controls.Add($txtInstall) $btnBrowse = New-Object System.Windows.Forms.Button -$btnBrowse.Text = 'Scegli...'; Set-Loc $btnBrowse 780 30; Set-Sz $btnBrowse 90 28; $form.Controls.Add($btnBrowse) +$btnBrowse.Text = 'Scegli...' +Set-Loc $btnBrowse 780 30 +Set-Sz $btnBrowse 90 28 +$form.Controls.Add($btnBrowse) -# URLs -$lblScriptUrl = New-Object System.Windows.Forms.Label; $lblScriptUrl.Text = 'Script URL'; Set-Loc $lblScriptUrl 10 64; Set-Sz $lblScriptUrl 150 22; $form.Controls.Add($lblScriptUrl) -$txtScriptUrl = New-Object System.Windows.Forms.TextBox; Set-Loc $txtScriptUrl 10 84; Set-Sz $txtScriptUrl 860 24; $txtScriptUrl.Text = $defaultScriptUrl; $form.Controls.Add($txtScriptUrl) +# URL AdHoc-Backup.ps1 +$lblScriptUrl = New-Object System.Windows.Forms.Label +$lblScriptUrl.Text='URL script AdHoc-Backup.ps1' +Set-Loc $lblScriptUrl 10 70 +Set-Sz $lblScriptUrl 220 22 +$form.Controls.Add($lblScriptUrl) -$lblConfUrl = New-Object System.Windows.Forms.Label; $lblConfUrl.Text = 'Config URL'; Set-Loc $lblConfUrl 10 116; Set-Sz $lblConfUrl 150 22; $form.Controls.Add($lblConfUrl) -$txtConfUrl = New-Object System.Windows.Forms.TextBox; Set-Loc $txtConfUrl 10 136; Set-Sz $txtConfUrl 860 24; $txtConfUrl.Text = $defaultConfUrl; $form.Controls.Add($txtConfUrl) +$txtScriptUrl = New-Object System.Windows.Forms.TextBox +Set-Loc $txtScriptUrl 10 92 +Set-Sz $txtScriptUrl 860 24 +$txtScriptUrl.Text = $defaultScriptUrl +$form.Controls.Add($txtScriptUrl) -# Opzioni -$grpOptions = New-Object System.Windows.Forms.GroupBox; $grpOptions.Text = 'Opzioni'; Set-Loc $grpOptions 10 170; Set-Sz $grpOptions 340 120; $form.Controls.Add($grpOptions) -$chkForceConf = New-Object System.Windows.Forms.CheckBox; $chkForceConf.Text='Sovrascrivi config esistente (ForceConfig)'; Set-Loc $chkForceConf 10 20; Set-Sz $chkForceConf 300 24; $grpOptions.Controls.Add($chkForceConf) -$chkNoRun = New-Object System.Windows.Forms.CheckBox; $chkNoRun.Text='Non avviare il backup ora (NoRun)'; Set-Loc $chkNoRun 10 45; Set-Sz $chkNoRun 300 24; $grpOptions.Controls.Add($chkNoRun) -$chkQuiet = New-Object System.Windows.Forms.CheckBox; $chkQuiet.Text='Modalità silenziosa (Quiet)'; Set-Loc $chkQuiet 10 70; Set-Sz $chkQuiet 300 24; $grpOptions.Controls.Add($chkQuiet) +# URL backup.conf +$lblConfUrl = New-Object System.Windows.Forms.Label +$lblConfUrl.Text='URL backup.conf' +Set-Loc $lblConfUrl 10 120 +Set-Sz $lblConfUrl 220 22 +$form.Controls.Add($lblConfUrl) + +$txtConfUrl = New-Object System.Windows.Forms.TextBox +Set-Loc $txtConfUrl 10 142 +Set-Sz $txtConfUrl 860 24 +$txtConfUrl.Text = $defaultConfUrl +$form.Controls.Add($txtConfUrl) + +# URL send-mail.ps1 +$lblMailUrl = New-Object System.Windows.Forms.Label +$lblMailUrl.Text='URL send-mail.ps1' +Set-Loc $lblMailUrl 10 170 +Set-Sz $lblMailUrl 220 22 +$form.Controls.Add($lblMailUrl) + +$txtMailUrl = New-Object System.Windows.Forms.TextBox +Set-Loc $txtMailUrl 10 192 +Set-Sz $txtMailUrl 860 24 +$txtMailUrl.Text = $defaultMailScriptUrl +$form.Controls.Add($txtMailUrl) + +# URL report_template.html +$lblTemplateUrl = New-Object System.Windows.Forms.Label +$lblTemplateUrl.Text='URL report_template.html' +Set-Loc $lblTemplateUrl 10 220 +Set-Sz $lblTemplateUrl 220 22 +$form.Controls.Add($lblTemplateUrl) + +$txtTemplateUrl = New-Object System.Windows.Forms.TextBox +Set-Loc $txtTemplateUrl 10 242 +Set-Sz $txtTemplateUrl 860 24 +$txtTemplateUrl.Text = $defaultReportTemplateUrl +$form.Controls.Add($txtTemplateUrl) + +# Opzioni installazione +$grpOptions = New-Object System.Windows.Forms.GroupBox +$grpOptions.Text='Opzioni' +Set-Loc $grpOptions 10 280 +Set-Sz $grpOptions 860 70 +$form.Controls.Add($grpOptions) + +$chkQuiet = New-Object System.Windows.Forms.CheckBox +$chkQuiet.Text='Crea attività pianificata senza output (quiet)' +Set-Loc $chkQuiet 20 30 +Set-Sz $chkQuiet 340 22 +$grpOptions.Controls.Add($chkQuiet) + +# NUOVA semantica: se SPUNTATA → esegue il backup ALLA CHIUSURA +$chkRunAfter = New-Object System.Windows.Forms.CheckBox +$chkRunAfter.Text="Eseguire il backup subito dopo l'installazione (alla chiusura)" +Set-Loc $chkRunAfter 380 30 +Set-Sz $chkRunAfter 460 22 +$grpOptions.Controls.Add($chkRunAfter) # Schedulazione -$grpSched = New-Object System.Windows.Forms.GroupBox; $grpSched.Text='Schedulazione'; Set-Loc $grpSched 360 170; Set-Sz $grpSched 510 160; $form.Controls.Add($grpSched) -$chkSchedule = New-Object System.Windows.Forms.CheckBox; $chkSchedule.Text='Crea attività pianificata'; Set-Loc $chkSchedule 10 20; Set-Sz $chkSchedule 220 24; $chkSchedule.Checked=$true; $grpSched.Controls.Add($chkSchedule) -$lblFreq = New-Object System.Windows.Forms.Label; $lblFreq.Text='Frequenza:'; Set-Loc $lblFreq 20 50; Set-Sz $lblFreq 80 24; $grpSched.Controls.Add($lblFreq) -$cmbFreq = New-Object System.Windows.Forms.ComboBox; $cmbFreq.DropDownStyle='DropDownList'; [void]$cmbFreq.Items.Add('Giornaliera'); [void]$cmbFreq.Items.Add('Settimanale'); $cmbFreq.SelectedIndex=0; Set-Loc $cmbFreq 100 48; Set-Sz $cmbFreq 120 24; $grpSched.Controls.Add($cmbFreq) -$lblDOW = New-Object System.Windows.Forms.Label; $lblDOW.Text='Giorno:'; Set-Loc $lblDOW 240 50; Set-Sz $lblDOW 60 24; $grpSched.Controls.Add($lblDOW) -$cmbDOW = New-Object System.Windows.Forms.ComboBox; $cmbDOW.DropDownStyle='DropDownList' -'Lunedì (MON)','Martedì (TUE)','Mercoledì (WED)','Giovedì (THU)','Venerdì (FRI)','Sabato (SAT)','Domenica (SUN)' | ForEach-Object { [void]$cmbDOW.Items.Add($_) } -$cmbDOW.SelectedIndex=0; Set-Loc $cmbDOW 300 48; Set-Sz $cmbDOW 180 24; $cmbDOW.Enabled=$false; $grpSched.Controls.Add($cmbDOW) -$cmbFreq.Add_SelectedIndexChanged({ $cmbDOW.Enabled = ($cmbFreq.SelectedIndex -eq 1) }) +$grpSched = New-Object System.Windows.Forms.GroupBox +$grpSched.Text='Schedulazione (Utilità di pianificazione)' +Set-Loc $grpSched 10 360 +Set-Sz $grpSched 860 140 +$form.Controls.Add($grpSched) -$lblTime = New-Object System.Windows.Forms.Label; $lblTime.Text='Ora:'; Set-Loc $lblTime 20 85; Set-Sz $lblTime 40 24; $grpSched.Controls.Add($lblTime) -$timePicker = New-Object System.Windows.Forms.DateTimePicker; $timePicker.Format=[System.Windows.Forms.DateTimePickerFormat]::Time; $timePicker.ShowUpDown=$true -$tpDefault = Get-Date; $tpDefault = Get-Date -Hour 23 -Minute 0 -Second 0 -$timePicker.Value = $tpDefault; Set-Loc $timePicker 60 82; Set-Sz $timePicker 100 24; $grpSched.Controls.Add($timePicker) +$chkSchedule = New-Object System.Windows.Forms.CheckBox +$chkSchedule.Text='Crea attività pianificata' +Set-Loc $chkSchedule 20 20 +Set-Sz $chkSchedule 200 24 +$chkSchedule.Checked = $true +$grpSched.Controls.Add($chkSchedule) -$lblTask = New-Object System.Windows.Forms.Label; $lblTask.Text='Nome task:'; Set-Loc $lblTask 180 85; Set-Sz $lblTask 80 24; $grpSched.Controls.Add($lblTask) -$txtTask = New-Object System.Windows.Forms.TextBox; Set-Loc $txtTask 260 82; Set-Sz $txtTask 230 24; $txtTask.Text='Backup_AdHoc'; $grpSched.Controls.Add($txtTask) +$lblFreq = New-Object System.Windows.Forms.Label +$lblFreq.Text='Frequenza:' +Set-Loc $lblFreq 20 50 +Set-Sz $lblFreq 80 24 +$grpSched.Controls.Add($lblFreq) + +$cmbFreq = New-Object System.Windows.Forms.ComboBox +$cmbFreq.DropDownStyle='DropDownList' +'Giornaliero','Settimanale' | ForEach-Object { [void]$cmbFreq.Items.Add($_) } +$cmbFreq.SelectedIndex = 0 +Set-Loc $cmbFreq 100 48 +Set-Sz $cmbFreq 120 24 +$grpSched.Controls.Add($cmbFreq) + +$lblDOW = New-Object System.Windows.Forms.Label +$lblDOW.Text='Giorno:' +Set-Loc $lblDOW 240 50 +Set-Sz $lblDOW 60 24 +$grpSched.Controls.Add($lblDOW) + +$cmbDOW = New-Object System.Windows.Forms.ComboBox +$cmbDOW.DropDownStyle='DropDownList' +'Lunedì (MON)','Martedì (TUE)','Mercoledì (WED)','Giovedì (THU)','Venerdì (FRI)','Sabato (SAT)','Domenica (SUN)' | + ForEach-Object { [void]$cmbDOW.Items.Add($_) } +$cmbDOW.SelectedIndex = 0 +$cmbDOW.Enabled = $false +Set-Loc $cmbDOW 300 48 +Set-Sz $cmbDOW 180 24 +$grpSched.Controls.Add($cmbDOW) + +$cmbFreq.Add_SelectedIndexChanged({ + $cmbDOW.Enabled = ($cmbFreq.SelectedIndex -eq 1) +}) + +$lblTime = New-Object System.Windows.Forms.Label +$lblTime.Text='Ora:' +Set-Loc $lblTime 20 85 +Set-Sz $lblTime 40 24 +$grpSched.Controls.Add($lblTime) + +$timePicker = New-Object System.Windows.Forms.DateTimePicker +$timePicker.Format = [System.Windows.Forms.DateTimePickerFormat]::Time +$timePicker.ShowUpDown = $true +$tpDefault = Get-Date -Hour 23 -Minute 0 -Second 0 +$timePicker.Value = $tpDefault +Set-Loc $timePicker 60 82 +Set-Sz $timePicker 100 24 +$grpSched.Controls.Add($timePicker) + +$lblTask = New-Object System.Windows.Forms.Label +$lblTask.Text='Nome attività:' +Set-Loc $lblTask 180 85 +Set-Sz $lblTask 90 24 +$grpSched.Controls.Add($lblTask) + +$txtTask = New-Object System.Windows.Forms.TextBox +$txtTask.Text='Backup_AdHoc' +Set-Loc $txtTask 270 82 +Set-Sz $txtTask 210 24 +$grpSched.Controls.Add($txtTask) # Argomenti extra -$lblExtra = New-Object System.Windows.Forms.Label; $lblExtra.Text='Argomenti extra per AdHoc (es. -WhatIf)'; Set-Loc $lblExtra 10 340; Set-Sz $lblExtra 420 22; $form.Controls.Add($lblExtra) -$txtExtra = New-Object System.Windows.Forms.TextBox; Set-Loc $txtExtra 10 362; Set-Sz $txtExtra 860 24; $form.Controls.Add($txtExtra) +$lblExtra = New-Object System.Windows.Forms.Label +$lblExtra.Text='Argomenti extra per AdHoc-Backup.ps1 (es. -Quiet, parametri addizionali):' +Set-Loc $lblExtra 10 510 +Set-Sz $lblExtra 480 22 +$form.Controls.Add($lblExtra) + +$txtExtra = New-Object System.Windows.Forms.TextBox +Set-Loc $txtExtra 10 532 +Set-Sz $txtExtra 860 24 +$form.Controls.Add($txtExtra) # ------------------------------ TAB: CONFIGURAZIONE --------------------------- -$tabs = New-Object System.Windows.Forms.TabControl; Set-Loc $tabs 10 392; Set-Sz $tabs 860 230; $form.Controls.Add($tabs) -$tabConfig = New-Object System.Windows.Forms.TabPage; $tabConfig.Text='Configurazione (backup.conf)'; [void]$tabs.TabPages.Add($tabConfig) +$tabs = New-Object System.Windows.Forms.TabControl +Set-Loc $tabs 10 562 +Set-Sz $tabs 860 230 +$form.Controls.Add($tabs) -$cfgPanel = New-Object System.Windows.Forms.Panel; Set-Loc $cfgPanel 10 10; Set-Sz $cfgPanel 830 150; $cfgPanel.AutoScroll=$true; $tabConfig.Controls.Add($cfgPanel) +$tabConfig = New-Object System.Windows.Forms.TabPage +$tabConfig.Text='Configurazione (backup.conf)' +[void]$tabs.TabPages.Add($tabConfig) -$btnReloadConf = New-Object System.Windows.Forms.Button; $btnReloadConf.Text='Ricarica da URL'; Set-Loc $btnReloadConf 10 170; Set-Sz $btnReloadConf 140 28; $tabConfig.Controls.Add($btnReloadConf) -$btnSaveConf = New-Object System.Windows.Forms.Button; $btnSaveConf.Text='Salva in bin\\conf'; Set-Loc $btnSaveConf 160 170; Set-Sz $btnSaveConf 230 28; $tabConfig.Controls.Add($btnSaveConf) -$btnRcloneConfig = New-Object System.Windows.Forms.Button; $btnRcloneConfig.Text='Configura rclone (console)'; Set-Loc $btnRcloneConfig 400 170; Set-Sz $btnRcloneConfig 200 28; $tabConfig.Controls.Add($btnRcloneConfig) +$cfgPanel = New-Object System.Windows.Forms.Panel +Set-Loc $cfgPanel 0 0 +Set-Sz $cfgPanel 850 160 +$cfgPanel.AutoScroll = $true +$tabConfig.Controls.Add($cfgPanel) + +$btnReloadConf = New-Object System.Windows.Forms.Button +$btnReloadConf.Text='Ricarica da URL' +Set-Loc $btnReloadConf 10 170 +Set-Sz $btnReloadConf 140 28 +$tabConfig.Controls.Add($btnReloadConf) + +$btnSaveConf = New-Object System.Windows.Forms.Button +$btnSaveConf.Text='Salva in bin\conf\backup.conf' +Set-Loc $btnSaveConf 160 170 +Set-Sz $btnSaveConf 250 28 +$tabConfig.Controls.Add($btnSaveConf) + +$btnRcloneConfig = New-Object System.Windows.Forms.Button +$btnRcloneConfig.Text='rclone config (console separata)' +Set-Loc $btnRcloneConfig 420 170 +Set-Sz $btnRcloneConfig 250 28 +$tabConfig.Controls.Add($btnRcloneConfig) function Build-ConfigUI { - $cfgPanel.Controls.Clear(); $global:ConfUi.Controls = @{} - $xLabel=10; $xCtrl=260; $y=10; $h=24 - foreach ($k in $global:ConfUi.Map.Keys) { - $key = [string]$k - $v = $global:ConfUi.Map[$key] - $lbl = New-Object System.Windows.Forms.Label; $lbl.Text=$key; $lbl.Tag=$key; Set-Loc $lbl $xLabel ($y+4); Set-Sz $lbl 240 $h; $cfgPanel.Controls.Add($lbl) - if (Is-BoolString $v) { - $chk = New-Object System.Windows.Forms.CheckBox - $chk.Tag = $key - $chk.Checked = (('1','true','yes','y') -contains ($v.ToString().ToLower())) - Set-Loc $chk $xCtrl ($y+2); Set-Sz $chk 20 $h - $chk.add_CheckedChanged({ param($sender,$args) - try { - $k2 = [string]$sender.Tag - if (-not [string]::IsNullOrEmpty($k2)) { - $global:ConfUi.Map[$k2] = if ($sender.Checked) { 'true' } else { 'false' } - } - } catch { Write-Log "Update booleano fallito: $($_.Exception.Message)" 'WARN' } - }) - $cfgPanel.Controls.Add($chk); $global:ConfUi.Controls[$key]=$chk - } else { - $tb = New-Object System.Windows.Forms.TextBox - $tb.Tag = $key - $tb.Text=$v; Set-Loc $tb $xCtrl $y; Set-Sz $tb 540 $h - $tb.add_TextChanged({ param($sender,$args) - try { - $k2 = [string]$sender.Tag - if (-not [string]::IsNullOrEmpty($k2)) { - $global:ConfUi.Map[$k2] = $sender.Text - } - } catch { Write-Log "Update testo fallito: $($_.Exception.Message)" 'WARN' } - }) - $cfgPanel.Controls.Add($tb); $global:ConfUi.Controls[$key]=$tb + $cfgPanel.Controls.Clear() + $global:ConfUi.Controls.Clear() + $y = 10 + foreach ($key in $global:ConfUi.Order) { + $val = $global:ConfUi.Map[$key] + + $lbl = New-Object System.Windows.Forms.Label + $lbl.Text = $key + Set-Loc $lbl 10 $y + Set-Sz $lbl 220 20 + $cfgPanel.Controls.Add($lbl) + + $tb = New-Object System.Windows.Forms.TextBox + $tb.Text = $val + Set-Loc $tb 240 $y + Set-Sz $tb 560 20 + + $global:ConfUi.Controls[$key] = $tb + + $tb.Add_TextChanged({ + $k = $this.Tag + $global:ConfUi.Map[$k] = $this.Text + }) + $tb.Tag = $key + + if ($key -like '*Password*') { + $tb.UseSystemPasswordChar = $true } + + $cfgPanel.Controls.Add($tb) $y += 28 } + Enable-TextRendering $cfgPanel } @@ -345,9 +685,13 @@ $btnReloadConf.Add_Click({ try { $temp = $global:ConfUi.TempPath if (Start-Download -Url $txtConfUrl.Text -Dest $temp) { - Parse-BackupConf -Path $temp; Build-ConfigUI; Write-Log "backup.conf scaricato e caricato in editor: $temp" + Parse-BackupConf -Path $temp + Build-ConfigUI + Write-Log "backup.conf scaricato e caricato in editor: $temp" } - } catch { Write-Log "Errore ricarica backup.conf: $($_.Exception.Message)" 'ERR' } + } catch { + Write-Log "Errore ricarica backup.conf: $($_.Exception.Message)" 'ERR' + } }) $btnSaveConf.Add_Click({ @@ -356,88 +700,238 @@ $btnSaveConf.Add_Click({ $dest = Join-Path $layout.Conf 'backup.conf' Save-BackupConf -Path $dest Write-Log "backup.conf salvato in: $dest" - } catch { Write-Log "Errore salvataggio backup.conf: $($_.Exception.Message)" 'ERR' } + } catch { + Write-Log "Errore salvataggio backup.conf: $($_.Exception.Message)" 'ERR' + } }) -# Avvio rclone config in console dedicata +# Avvio rclone config $btnRcloneConfig.Add_Click({ try { $layout = Ensure-ToolsLayout -Root $txtInstall.Text $rcloneExe = Ensure-RcloneExe -RcloneDir $layout.RClone $rcloneConf = Join-Path $layout.Conf 'rclone.conf' - $p = Start-Process -FilePath $rcloneExe -ArgumentList @('config','--config', $rcloneConf) -WorkingDirectory $layout.RClone -PassThru + $p = Start-Process -FilePath $rcloneExe -ArgumentList @('config','--config',$rcloneConf) -WorkingDirectory $layout.RClone -PassThru [void]$global:ChildPids.Add($p.Id) Write-Log "Avviato rclone config (PID=$($p.Id))" - } catch { Write-Log "Errore avvio rclone config: $($_.Exception.Message)" 'ERR' } + } catch { + Write-Log "Errore avvio rclone config: $($_.Exception.Message)" 'ERR' + } }) # ------------------------------ BOTTONI AZIONE -------------------------------- -$btnInstall = New-Object System.Windows.Forms.Button; $btnInstall.Text='Installa'; Set-Loc $btnInstall 10 652; Set-Sz $btnInstall 120 32; $form.Controls.Add($btnInstall) -$btnClose = New-Object System.Windows.Forms.Button; $btnClose.Text='Chiudi'; Set-Loc $btnClose 150 652; Set-Sz $btnClose 120 32; $form.Controls.Add($btnClose) +$btnInstall = New-Object System.Windows.Forms.Button +$btnInstall.Text = 'Installa' +Set-Loc $btnInstall 10 804 +Set-Sz $btnInstall 120 32 +$form.Controls.Add($btnInstall) + +$btnTestMail = New-Object System.Windows.Forms.Button +$btnTestMail.Text = 'Test mail' +Set-Loc $btnTestMail 150 804 +Set-Sz $btnTestMail 120 32 +$form.Controls.Add($btnTestMail) + +$btnClose = New-Object System.Windows.Forms.Button +$btnClose.Text = 'Chiudi' +Set-Loc $btnClose 290 804 +Set-Sz $btnClose 120 32 +$form.Controls.Add($btnClose) # LOG -$lblLog = New-Object System.Windows.Forms.Label; $lblLog.Text='Log'; Set-Loc $lblLog 10 692; Set-Sz $lblLog 50 22; $form.Controls.Add($lblLog) -$txtLog = New-Object System.Windows.Forms.TextBox; Set-Loc $txtLog 10 712; Set-Sz $txtLog 860 100; $txtLog.Multiline=$true; $txtLog.ScrollBars='Vertical'; $txtLog.ReadOnly=$true; $form.Controls.Add($txtLog); $global:LogTextBox = $txtLog +$lblLog = New-Object System.Windows.Forms.Label +$lblLog.Text='Log:' +Set-Loc $lblLog 10 836 +Set-Sz $lblLog 50 22 +$form.Controls.Add($lblLog) + +$txtLog = New-Object System.Windows.Forms.TextBox +Set-Loc $txtLog 10 858 +Set-Sz $txtLog 860 70 +$txtLog.Multiline = $true +$txtLog.ScrollBars = 'Vertical' +$txtLog.ReadOnly = $true +$form.Controls.Add($txtLog) +$global:LogTextBox = $txtLog # ------------------------------ HANDLERS -------------------------------------- -$btnBrowse.Add_Click({ $dlg = New-Object System.Windows.Forms.FolderBrowserDialog; $dlg.Description='Seleziona cartella di installazione'; if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ $txtInstall.Text=$dlg.SelectedPath } }) +$btnBrowse.Add_Click({ + $dlg = New-Object System.Windows.Forms.FolderBrowserDialog + $dlg.Description = 'Seleziona cartella di installazione' + if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK){ + $txtInstall.Text = $dlg.SelectedPath + } +}) $btnInstall.Add_Click({ try { $btnInstall.Enabled = $false - $root = $txtInstall.Text; Ensure-Dir $root + $root = $txtInstall.Text + Ensure-Dir $root $layout = Ensure-ToolsLayout -Root $root - # Scarica/installa Rclone e SOLO 7zr.exe + # Scarica/installa Rclone e 7zr $rcloneExe = Ensure-RcloneExe -RcloneDir $layout.RClone - if (-not (Test-Path -LiteralPath $rcloneExe)) { Write-Log "ATTENZIONE: rclone.exe non installato." 'WARN' } + if (-not (Test-Path -LiteralPath $rcloneExe)) { + Write-Log "ATTENZIONE: rclone.exe non installato." 'WARN' + } $sevenExe = Ensure-SevenZipExe -SevenZipDir $layout.SevenZip - if (-not (Test-Path -LiteralPath $sevenExe)) { Write-Log "ATTENZIONE: 7-Zip (7zr.exe) non installato." 'WARN' } + if (-not (Test-Path -LiteralPath $sevenExe)) { + Write-Log "ATTENZIONE: 7-Zip (7zr.exe) non installato." 'WARN' + } # Script principale $scriptPath = Join-Path $root 'AdHoc-Backup.ps1' - if (-not (Start-Download -Url $txtScriptUrl.Text -Dest $scriptPath)) { throw "Impossibile scaricare lo script principale." } + if (-not (Start-Download -Url $txtScriptUrl.Text -Dest $scriptPath)) { + throw "Impossibile scaricare lo script principale." + } - # backup.conf -> bin\conf\backup.conf - $confPath = Join-Path $layout.Conf 'backup.conf' - if ($global:ConfUi.Map) { Save-BackupConf -Path $confPath } - else { - if (-not (Start-Download -Url $txtConfUrl.Text -Dest $confPath)) { Write-Log "Attenzione: impossibile ottenere backup.conf; procedo senza." 'WARN' } + # Script invio mail + $sendMailPath = Join-Path $root 'send-mail.ps1' + if (-not (Start-Download -Url $txtMailUrl.Text -Dest $sendMailPath)) { + Write-Log "ATTENZIONE: impossibile scaricare send-mail.ps1 (invio mail avanzato)." 'WARN' + } + + # backup.conf e report_template.html + $confPath = Join-Path $layout.Conf 'backup.conf' + $reportTemplatePath = Join-Path $layout.Conf 'report_template.html' + + if ($global:ConfUi.Map -and $global:ConfUi.Map.Count -gt 0) { + Save-BackupConf -Path $confPath + } else { + if (-not (Start-Download -Url $txtConfUrl.Text -Dest $confPath)) { + Write-Log "ATTENZIONE: impossibile ottenere backup.conf; procedo senza." 'WARN' + } + } + + if (-not (Test-Path -LiteralPath $reportTemplatePath)) { + if (Start-Download -Url $txtTemplateUrl.Text -Dest $reportTemplatePath) { + Write-Log "report_template.html scaricato in: $reportTemplatePath" + } else { + Write-Log "ATTENZIONE: impossibile ottenere report_template.html; invio mail HTML non disponibile." 'WARN' + } } # Schedulazione if ($chkSchedule.Checked) { $schedule = if ($cmbFreq.SelectedIndex -eq 1) { 'WEEKLY' } else { 'DAILY' } - $dowmap = @('MON','TUE','WED','THU','FRI','SAT','SUN'); $day=$dowmap[0]; if ($schedule -eq 'WEEKLY'){ $day=$dowmap[$cmbDOW.SelectedIndex] } - [void](New-DailyOrWeeklyTask -TaskName $txtTask.Text -ScriptPath $scriptPath -ConfigPath $confPath -Time $timePicker.Value -Schedule $schedule -Day $day -Quiet:$chkQuiet.Checked) + $dowmap = @('MON','TUE','WED','THU','FRI','SAT','SUN') + $day = 'MON' + if ($schedule -eq 'WEEKLY') { + $day = $dowmap[$cmbDOW.SelectedIndex] + } + New-DailyOrWeeklyTask ` + -TaskName $txtTask.Text ` + -ScriptPath $scriptPath ` + -ConfigPath $confPath ` + -Time $timePicker.Value ` + -Schedule $schedule ` + -Day $day ` + -Quiet: $chkQuiet.Checked | Out-Null } - # Avvio immediato - if (-not $chkNoRun.Checked) { - $args=@('-NoProfile','-ExecutionPolicy','Bypass','-File',"`"$scriptPath`"","-Config", "`"$confPath`"") - if ($chkQuiet.Checked){ $args += '-Quiet' } - if ($txtExtra.Text -and $txtExtra.Text.Trim() -ne ''){ $args += $txtExtra.Text.Trim().Split(' ') } - $p=Start-Process -FilePath 'powershell.exe' -ArgumentList $args -PassThru; [void]$global:ChildPids.Add($p.Id); Write-Log "Esecuzione avviata (PID=$($p.Id))." + # Flag per esecuzione alla chiusura GUI + if ($chkRunAfter.Checked) { + $global:RunBackupOnClose = $true + $global:AfterInstallScriptPath = $scriptPath + $global:AfterInstallConfPath = $confPath + Write-Log "Alla chiusura della finestra verrà eseguito il backup." + } else { + $global:RunBackupOnClose = $false + $global:AfterInstallScriptPath = $null + $global:AfterInstallConfPath = $null } Write-Log "Installazione completata." - } catch { Write-Log "Errore durante l'installazione: $($_.Exception.Message)" 'ERR' } - finally { $btnInstall.Enabled = $true } + } catch { + Write-Log "Errore durante l'installazione: $($_.Exception.Message)" 'ERR' + } finally { + $btnInstall.Enabled = $true + } }) -$btnClose.Add_Click({ $form.Close() }) +$btnTestMail.Add_Click({ + try { + if (-not $global:ConfUi.Map -or $global:ConfUi.Map.Count -eq 0) { + Write-Log "Configurazione non caricata: impossibile effettuare il test di invio mail." 'ERR' + return + } + + $root = $txtInstall.Text + if ([string]::IsNullOrWhiteSpace($root)) { + Write-Log "Cartella di installazione non impostata; impossibile effettuare il test di invio mail." 'ERR' + return + } + + $layout = Ensure-ToolsLayout -Root $root + + # Percorso send-mail.ps1 + $sendMailPath = Join-Path $root 'send-mail.ps1' + if (-not (Test-Path -LiteralPath $sendMailPath)) { + Write-Log "send-mail.ps1 non trovato; provo a scaricarlo..." 'WARN' + if (-not (Start-Download -Url $txtMailUrl.Text -Dest $sendMailPath)) { + Write-Log "Impossibile scaricare send-mail.ps1; test mail annullato." 'ERR' + return + } + } + + # Esporta i parametri di configurazione come variabili + foreach ($k in $global:ConfUi.Map.Keys) { + $v = $global:ConfUi.Map[$k] + if ($null -ne $v -and $v -ne '') { + Set-Variable -Name $k -Value $v -Scope Script + } + } + + # Cartella di configurazione per il template HTML + $script:InstallerConfDir = $layout.Conf + $global:InstallerConfDir = $layout.Conf + + Write-Log "Invio mail di test in corso..." + . $sendMailPath -Subject 'Test AdHoc Backup' -Body ("Mail di test inviata dall'installer AdHoc Backup.`nData: {0}" -f (Get-Date -Format 'dd/MM/yyyy HH:mm:ss')) + Write-Log "Richiesta invio mail di test completata (controllare eventuali errori sopra)." 'INFO' + } catch { + Write-Log ("Errore durante il test di invio mail: {0}" -f $_.Exception.Message) 'ERR' + } +}) + +$btnClose.Add_Click({ + $form.Close() +}) + +# Avvio backup alla chiusura GUI (se richiesto) +$form.Add_FormClosing({ + try { + if ($global:RunBackupOnClose -and + $global:AfterInstallScriptPath -and + $global:AfterInstallConfPath) { + + Write-Log "Avvio backup alla chiusura della GUI..." + $args = @( + '-NoProfile','-ExecutionPolicy','Bypass', + '-File', ('"{0}"' -f $global:AfterInstallScriptPath), + '-Config', ('"{0}"' -f $global:AfterInstallConfPath) + ) + $argString = [string]::Join(' ',$args) + Start-Process -FilePath 'powershell.exe' -ArgumentList $argString | Out-Null + } + } catch { + Write-Log ("Errore avvio backup alla chiusura: {0}" -f $_.Exception.Message) 'ERR' + } +}) # Chiudi eventuali processi figli avviati dalla GUI $form.Add_FormClosed({ foreach ($cpid in $global:ChildPids) { try { - $proc = Get-Process -Id $cpid -ErrorAction SilentlyContinue - if ($proc) { - $null = $proc.CloseMainWindow() - Start-Sleep -Milliseconds 200 - if (-not $proc.HasExited) { Stop-Process -Id $cpid -Force -ErrorAction SilentlyContinue } + $proc = Get-Process -Id $cpid -ErrorAction Stop + if ($proc -and -not $proc.HasExited) { + try { + $proc.Kill() + } catch {} } - } catch {} + } catch { + } } }) @@ -447,11 +941,17 @@ Write-Log "Log file: $global:LogFile" try { $temp = $global:ConfUi.TempPath - if (Start-Download -Url $defaultConfUrl -Dest $temp) { Parse-BackupConf -Path $temp; Build-ConfigUI; Write-Log "backup.conf iniziale caricato da: $defaultConfUrl" } - else { Write-Log "Non sono riuscito a scaricare il backup.conf iniziale." 'WARN' } -} catch { Write-Log "Errore iniziale confer: $($_.Exception.Message)" 'ERR' } + if (Start-Download -Url $defaultConfUrl -Dest $temp) { + Parse-BackupConf -Path $temp + Build-ConfigUI + Write-Log "backup.conf iniziale caricato da: $defaultConfUrl" + } else { + Write-Log "Non sono riuscito a scaricare il backup.conf iniziale." 'WARN' + } +} catch { + Write-Log "Errore iniziale confer: $($_.Exception.Message)" 'ERR' +} -# Applica rendering compatibile a tutta la finestra Enable-TextRendering $form -[void]$form.ShowDialog() +[void]$form.ShowDialog() \ No newline at end of file