<# 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 [System.Windows.Forms.Application]::EnableVisualStyles() $ErrorActionPreference = 'Stop' # ------------------------------ GLOBALI PER RUN DOPO CHIUSURA ---------------- $global:RunBackupOnClose = $false $global:AfterInstallScriptPath = $null $global:AfterInstallConfPath = $null # ------------------------------ 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 ($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 } } } 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 try { Invoke-WebRequest -Uri $Url -OutFile $Dest -ErrorAction Stop 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 } } } # 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 ) Write-Log "Estrazione ZIP: $ZipPath -> $DestDir" $zip = $null try { $zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath) foreach ($entry in $zip.Entries) { $targetPath = Join-Path $DestDir $entry.FullName $targetDir = Split-Path $targetPath -Parent if ([string]::IsNullOrEmpty($entry.Name)) { Ensure-Dir $targetPath } else { Ensure-Dir $targetDir [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $targetPath, $true) } } } finally { if ($zip) { $zip.Dispose() } } } 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', [string]$Day = 'MON', [switch]$Quiet ) $argList = @( '-NoProfile', '-ExecutionPolicy','Bypass', '-File', ('"{0}"' -f $ScriptPath), '-Config', ('"{0}"' -f $ConfigPath) ) $argString = [string]::Join(' ', $argList) $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $argString $trigger = if ($Schedule -eq 'WEEKLY') { New-ScheduledTaskTrigger -Weekly -DaysOfWeek $Day -At $Time } else { 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 } } # 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) } } $global:ConfUi = @{ 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} } } # 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) { $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 $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 $text = $enc.GetString($bytes,2,$bytes.Length-2) } else { $utf8Strict = New-Object System.Text.UTF8Encoding($false,$true) try { $text = $utf8Strict.GetString($bytes) } catch { $text = [System.Text.Encoding]::Default.GetString($bytes) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } } 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) $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('#')) { 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 } } # 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) } } 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' $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 $form.Text = 'Installer AdHoc Backup' $form.StartPosition = 'CenterScreen' $form.MaximizeBox = $true $form.FormBorderStyle = 'FixedDialog' $form.Font = New-Object System.Drawing.Font('Segoe UI', 9) Set-Sz $form 900 950 # 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) $txtInstall = New-Object System.Windows.Forms.TextBox 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) # 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) $txtScriptUrl = New-Object System.Windows.Forms.TextBox Set-Loc $txtScriptUrl 10 92 Set-Sz $txtScriptUrl 860 24 $txtScriptUrl.Text = $defaultScriptUrl $form.Controls.Add($txtScriptUrl) # 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 (Utilità di pianificazione)' Set-Loc $grpSched 10 360 Set-Sz $grpSched 860 140 $form.Controls.Add($grpSched) $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) $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-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 562 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) $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.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 } $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" } } catch { Write-Log "Errore ricarica backup.conf: $($_.Exception.Message)" 'ERR' } }) $btnSaveConf.Add_Click({ try { $layout = Ensure-ToolsLayout -Root $txtInstall.Text $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' } }) # 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 [void]$global:ChildPids.Add($p.Id) Write-Log "Avviato rclone config (PID=$($p.Id))" } 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 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 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 } }) $btnInstall.Add_Click({ try { $btnInstall.Enabled = $false $root = $txtInstall.Text Ensure-Dir $root $layout = Ensure-ToolsLayout -Root $root # 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' } $sevenExe = Ensure-SevenZipExe -SevenZipDir $layout.SevenZip 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." } # 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 = '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 } # 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 } }) $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 Stop if ($proc -and -not $proc.HasExited) { try { $proc.Kill() } catch {} } } catch { } } }) # ------------------------------ AVVIO ----------------------------------------- $global:LogFile = Join-Path $env:TEMP "install-adhoc-backup_gui_$(Get-Date -Format 'yyyyMMdd_HHmmss').log" 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' } Enable-TextRendering $form [void]$form.ShowDialog()