diff --git a/install-AdHoc-Backup-GUI.ps1 b/install-AdHoc-Backup-GUI.ps1 index e4b4ce6..af302ef 100644 --- a/install-AdHoc-Backup-GUI.ps1 +++ b/install-AdHoc-Backup-GUI.ps1 @@ -1,14 +1,6 @@ #Requires -Version 5.1 <# - Installer AdHoc Backup (GUI) - - Crea cartella di installazione - - Scarica script di backup e file di config - - Opzionale: crea attività pianificata (giornaliera o settimanale) - - Opzionale: prepara RClone (download exe, rclone.conf, remote) - Correzioni/novità: - * URL script corretto (AdHoc-Backup.ps1) - * Frequenza schedulazione Giornaliera/Settimanale (+ giorno) - * Sezione RClone (auto-download, rclone.conf, remote) +Installer AdHoc Backup (GUI) - v1.3 #> Add-Type -AssemblyName System.Windows.Forms @@ -17,451 +9,139 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem $ErrorActionPreference = 'Stop' +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 } + function New-LogTimestamp { (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') } - -$global:LogFile = $null +$global:LogFile = $null $global:LogTextBox = $null +function Write-Log { param([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 } -function Write-Log { - param( - [Parameter(Mandatory=$true)][string]$Message, - [ValidateSet('INFO','ERR','WARN')] [string]$Level = 'INFO' - ) - $line = "[{0}][{1}] {2}" -f (New-LogTimestamp), $Level, $Message - if ($global:LogTextBox -ne $null) { - 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 -} +function Ensure-Dir { param([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([string]$Url,[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 } } } +function Expand-Zip { param([string]$ZipPath,[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([string]$TaskName,[string]$ScriptPath,[string]$ConfigPath,[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";$cmd+=$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 } } -function Ensure-Dir { - param([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 Ensure-ToolsLayout { param([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 } } +function Ensure-RcloneExe { param([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 } -function Start-Download { - param([Parameter(Mandatory)][string]$Url, - [Parameter(Mandatory)][string]$Dest) +$global:ChildPids = New-Object System.Collections.ArrayList +function Track-Child($p){ if($p -and $p.Id){ [void]$global:ChildPids.Add($p.Id) } } - Write-Log "Scarico: $Url" - Write-Log "Destinazione: $Dest" +$global:ConfUi = @{ Map=$null; Order=@(); Controls=@{}; SourceLines=@(); 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 Parse-BackupConf { param([string]$Path) $order=New-Object System.Collections.ArrayList; $map=[ordered]@{}; $lines=Get-Content -LiteralPath $Path -ErrorAction Stop; foreach($line in $lines){ $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}) }; $global:ConfUi.Map=$map; $global:ConfUi.Order=$order; $global:ConfUi.SourceLines=$lines } +function Save-BackupConf { param([string]$Path) $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) } }; 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])") } }; Set-Content -LiteralPath $Path -Value $out -Encoding UTF8 } - [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls +$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' - 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 { - $wc = New-Object System.Net.WebClient - $wc.DownloadFile($Url, $Dest) - Write-Log "Download completato con WebClient (fallback)." - return $true - } catch { - Write-Log "Download fallito: $($_.Exception.Message)" 'ERR' - return $false - } - } -} +$form = New-Object System.Windows.Forms.Form +$form.Text = 'Installer AdHoc Backup' +$form.StartPosition = 'CenterScreen' +$form.MaximizeBox = $true +$form.FormBorderStyle = 'FixedDialog' +Set-Sz $form 900 860 -function Expand-Zip { - param([Parameter(Mandatory)] [string] $ZipPath, - [Parameter(Mandatory)] [string] $DestDir) - Write-Log "Estrazione ZIP: $ZipPath -> $DestDir" - [System.IO.Compression.ZipFile]::ExtractToDirectory($ZipPath, $DestDir, $true) -} +$lblInstall = New-Object System.Windows.Forms.Label; $lblInstall.Text = 'Cartella di installazione'; Set-Loc $lblInstall 10 12; Set-Sz $lblInstall 180 20; $form.Controls.Add($lblInstall) +$txtInstall = New-Object System.Windows.Forms.TextBox; Set-Loc $txtInstall 10 32; Set-Sz $txtInstall 760 22; $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 26; $form.Controls.Add($btnBrowse) -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' } +$lblScriptUrl = New-Object System.Windows.Forms.Label; $lblScriptUrl.Text = 'Script URL'; Set-Loc $lblScriptUrl 10 64; Set-Sz $lblScriptUrl 150 20; $form.Controls.Add($lblScriptUrl) +$txtScriptUrl = New-Object System.Windows.Forms.TextBox; Set-Loc $txtScriptUrl 10 84; Set-Sz $txtScriptUrl 860 22; $txtScriptUrl.Text = $defaultScriptUrl; $form.Controls.Add($txtScriptUrl) - $tr = 'powershell.exe ' + ($psArgs -join ' ') - $st = $Time.ToString('HH:mm') +$lblConfUrl = New-Object System.Windows.Forms.Label; $lblConfUrl.Text = 'Config URL'; Set-Loc $lblConfUrl 10 116; Set-Sz $lblConfUrl 150 20; $form.Controls.Add($lblConfUrl) +$txtConfUrl = New-Object System.Windows.Forms.TextBox; Set-Loc $txtConfUrl 10 136; Set-Sz $txtConfUrl 860 22; $txtConfUrl.Text = $defaultConfUrl; $form.Controls.Add($txtConfUrl) - $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 - } -} - -$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' - -$form = New-Object System.Windows.Forms.Form -$form.Text = 'Installer AdHoc Backup' -$form.StartPosition = 'CenterScreen' -$form.Size = New-Object System.Drawing.Size(900, 820) -$form.MaximizeBox = $true -$form.FormBorderStyle = 'FixedDialog' - -# --- UI --- -$lblInstall = New-Object System.Windows.Forms.Label -$lblInstall.Text = 'Cartella di installazione' -$lblInstall.Location = New-Object System.Drawing.Point(10, 12); $lblInstall.Size = New-Object System.Drawing.Size(180, 20) -$form.Controls.Add($lblInstall) - -$txtInstall = New-Object System.Windows.Forms.TextBox -$txtInstall.Location = New-Object System.Drawing.Point(10, 32); $txtInstall.Size = New-Object System.Drawing.Size(760, 22) -$txtInstall.Text = $defaultInstall -$form.Controls.Add($txtInstall) - -$btnBrowse = New-Object System.Windows.Forms.Button -$btnBrowse.Text = 'Scegli...'; $btnBrowse.Location = New-Object System.Drawing.Point(780, 30); $btnBrowse.Size = New-Object System.Drawing.Size(90, 26) -$form.Controls.Add($btnBrowse) - -$lblScriptUrl = New-Object System.Windows.Forms.Label -$lblScriptUrl.Text = 'Script URL' -$lblScriptUrl.Location = New-Object System.Drawing.Point(10, 64); $lblScriptUrl.Size = New-Object System.Drawing.Size(150, 20) -$form.Controls.Add($lblScriptUrl) - -$txtScriptUrl = New-Object System.Windows.Forms.TextBox -$txtScriptUrl.Location = New-Object System.Drawing.Point(10, 84); $txtScriptUrl.Size = New-Object System.Drawing.Size(860, 22) -$txtScriptUrl.Text = $defaultScriptUrl -$form.Controls.Add($txtScriptUrl) - -$lblConfUrl = New-Object System.Windows.Forms.Label -$lblConfUrl.Text = 'Config URL' -$lblConfUrl.Location = New-Object System.Drawing.Point(10, 116); $lblConfUrl.Size = New-Object System.Drawing.Size(150, 20) -$form.Controls.Add($lblConfUrl) - -$txtConfUrl = New-Object System.Windows.Forms.TextBox -$txtConfUrl.Location = New-Object System.Drawing.Point(10, 136); $txtConfUrl.Size = New-Object System.Drawing.Size(860, 22) -$txtConfUrl.Text = $defaultConfUrl -$form.Controls.Add($txtConfUrl) - -$grpOptions = New-Object System.Windows.Forms.GroupBox -$grpOptions.Text = 'Opzioni'; $grpOptions.Location = New-Object System.Drawing.Point(10, 170); $grpOptions.Size = New-Object System.Drawing.Size(340, 120) -$form.Controls.Add($grpOptions) - -$chkForceConf = New-Object System.Windows.Forms.CheckBox -$chkForceConf.Text = 'Sovrascrivi config esistente (ForceConfig)' -$chkForceConf.Location = New-Object System.Drawing.Point(10, 20); $chkForceConf.Size = New-Object System.Drawing.Size(300, 22) -$grpOptions.Controls.Add($chkForceConf) - -$chkNoRun = New-Object System.Windows.Forms.CheckBox -$chkNoRun.Text = 'Non avviare il backup ora (NoRun)' -$chkNoRun.Location = New-Object System.Drawing.Point(10, 45); $chkNoRun.Size = New-Object System.Drawing.Size(280, 22) -$grpOptions.Controls.Add($chkNoRun) - -$chkQuiet = New-Object System.Windows.Forms.CheckBox -$chkQuiet.Text = 'Modalità silenziosa (Quiet)' -$chkQuiet.Location = New-Object System.Drawing.Point(10, 70); $chkQuiet.Size = New-Object System.Drawing.Size(220, 22) -$grpOptions.Controls.Add($chkQuiet) - -$grpSched = New-Object System.Windows.Forms.GroupBox -$grpSched.Text = 'Schedulazione'; $grpSched.Location = New-Object System.Drawing.Point(360, 170); $grpSched.Size = New-Object System.Drawing.Size(510, 160) -$form.Controls.Add($grpSched) - -$chkSchedule = New-Object System.Windows.Forms.CheckBox -$chkSchedule.Text = 'Crea attività pianificata' -$chkSchedule.Location = New-Object System.Drawing.Point(10, 20); $chkSchedule.Size = New-Object System.Drawing.Size(200, 22) -$chkSchedule.Checked = $true -$grpSched.Controls.Add($chkSchedule) - -$lblFreq = New-Object System.Windows.Forms.Label -$lblFreq.Text = 'Frequenza:'; $lblFreq.Location = New-Object System.Drawing.Point(20, 50); $lblFreq.Size = New-Object System.Drawing.Size(80, 22) -$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 -$cmbFreq.Location = New-Object System.Drawing.Point(100, 48); $cmbFreq.Size = New-Object System.Drawing.Size(120, 22) -$grpSched.Controls.Add($cmbFreq) - -$lblDOW = New-Object System.Windows.Forms.Label -$lblDOW.Text = 'Giorno:'; $lblDOW.Location = New-Object System.Drawing.Point(240, 50); $lblDOW.Size = New-Object System.Drawing.Size(60, 22) -$grpSched.Controls.Add($lblDOW) - -$cmbDOW = New-Object System.Windows.Forms.ComboBox -$cmbDOW.DropDownStyle = 'DropDownList' -[void]$cmbDOW.Items.Add('Lunedì (MON)'); [void]$cmbDOW.Items.Add('Martedì (TUE)'); [void]$cmbDOW.Items.Add('Mercoledì (WED)') -[void]$cmbDOW.Items.Add('Giovedì (THU)'); [void]$cmbDOW.Items.Add('Venerdì (FRI)'); [void]$cmbDOW.Items.Add('Sabato (SAT)'); [void]$cmbDOW.Items.Add('Domenica (SUN)') -$cmbDOW.SelectedIndex = 0; $cmbDOW.Location = New-Object System.Drawing.Point(300, 48); $cmbDOW.Size = New-Object System.Drawing.Size(180, 22) -$cmbDOW.Enabled = $false -$grpSched.Controls.Add($cmbDOW) +$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 22; $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 280 22; $grpOptions.Controls.Add($chkNoRun) +$chkQuiet = New-Object System.Windows.Forms.CheckBox; $chkQuiet.Text='Modalità silenziosa (Quiet)'; Set-Loc $chkQuiet 10 70; Set-Sz $chkQuiet 220 22; $grpOptions.Controls.Add($chkQuiet) +$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 200 22; $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 22; $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 22; $grpSched.Controls.Add($cmbFreq) +$lblDOW = New-Object System.Windows.Forms.Label; $lblDOW.Text='Giorno:'; Set-Loc $lblDOW 240 50; Set-Sz $lblDOW 60 22; $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 22; $cmbDOW.Enabled=$false; $grpSched.Controls.Add($cmbDOW) $cmbFreq.Add_SelectedIndexChanged({ $cmbDOW.Enabled = ($cmbFreq.SelectedIndex -eq 1) }) -$lblTime = New-Object System.Windows.Forms.Label -$lblTime.Text = 'Ora:'; $lblTime.Location = New-Object System.Drawing.Point(20, 85); $lblTime.Size = New-Object System.Drawing.Size(40, 22) -$grpSched.Controls.Add($lblTime) +$lblTime = New-Object System.Windows.Forms.Label; $lblTime.Text='Ora:'; Set-Loc $lblTime 20 85; Set-Sz $lblTime 40 22; $grpSched.Controls.Add($lblTime) +$timePicker = New-Object System.Windows.Forms.DateTimePicker; $timePicker.Format=[System.Windows.Forms.DateTimePickerFormat]::Time; $timePicker.ShowUpDown=$true; $timePicker.Value=(Get-Date -Hour 23 -Minute 0 -Second 0); Set-Loc $timePicker 60 82; Set-Sz $timePicker 100 22; $grpSched.Controls.Add($timePicker) -$timePicker = New-Object System.Windows.Forms.DateTimePicker -$timePicker.Format = [System.Windows.Forms.DateTimePickerFormat]::Time -$timePicker.ShowUpDown = $true -$timePicker.Value = (Get-Date "23:00") -$timePicker.Location = New-Object System.Drawing.Point(60, 82); $timePicker.Size = New-Object System.Drawing.Size(100, 22) -$grpSched.Controls.Add($timePicker) +$lblTask = New-Object System.Windows.Forms.Label; $lblTask.Text='Nome task:'; Set-Loc $lblTask 180 85; Set-Sz $lblTask 80 22; $grpSched.Controls.Add($lblTask) +$txtTask = New-Object System.Windows.Forms.TextBox; Set-Loc $txtTask 260 82; Set-Sz $txtTask 230 22; $txtTask.Text='Backup_AdHoc'; $grpSched.Controls.Add($txtTask) -$lblTask = New-Object System.Windows.Forms.Label -$lblTask.Text = 'Nome task:'; $lblTask.Location = New-Object System.Drawing.Point(180, 85); $lblTask.Size = New-Object System.Drawing.Size(80, 22) -$grpSched.Controls.Add($lblTask) +$lblExtra = New-Object System.Windows.Forms.Label; $lblExtra.Text='Argomenti extra per AdHoc (es. -WhatIf)'; Set-Loc $lblExtra 10 340; Set-Sz $lblExtra 380 20; $form.Controls.Add($lblExtra) +$txtExtra = New-Object System.Windows.Forms.TextBox; Set-Loc $txtExtra 10 362; Set-Sz $txtExtra 860 22; $form.Controls.Add($txtExtra) -$txtTask = New-Object System.Windows.Forms.TextBox -$txtTask.Location = New-Object System.Drawing.Point(260, 82); $txtTask.Size = New-Object System.Drawing.Size(230, 22) -$txtTask.Text = 'Backup_AdHoc' -$grpSched.Controls.Add($txtTask) +$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) +$cfgPanel = New-Object System.Windows.Forms.Panel; Set-Loc $cfgPanel 10 10; Set-Sz $cfgPanel 830 150; $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 120 26; $tabConfig.Controls.Add($btnReloadConf) +$btnSaveConf = New-Object System.Windows.Forms.Button; $btnSaveConf.Text='Salva in bin\\conf'; Set-Loc $btnSaveConf 140 170; Set-Sz $btnSaveConf 230 26; $tabConfig.Controls.Add($btnSaveConf) +$btnRcloneConfig = New-Object System.Windows.Forms.Button; $btnRcloneConfig.Text='Configura rclone (console)'; Set-Loc $btnRcloneConfig 380 170; Set-Sz $btnRcloneConfig 170 26; $tabConfig.Controls.Add($btnRcloneConfig) -$lblExtra = New-Object System.Windows.Forms.Label -$lblExtra.Text = 'Argomenti extra per AdHoc (es. -WhatIf)' -$lblExtra.Location = New-Object System.Drawing.Point(10, 340); $lblExtra.Size = New-Object System.Drawing.Size(380, 20) -$form.Controls.Add($lblExtra) +function Build-ConfigUI { $cfgPanel.Controls.Clear(); $global:ConfUi.Controls=@{}; $xLabel=10; $xCtrl=260; $y=10; $h=24; foreach($k in $global:ConfUi.Map.Keys){ $v=$global:ConfUi.Map[$k]; $lbl=New-Object System.Windows.Forms.Label; $lbl.Text=$k; 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.Checked=(('1','true','yes','y') -contains ($v.ToString().ToLower())); Set-Loc $chk $xCtrl ($y+2); Set-Sz $chk 20 $h; $chk.Add_CheckedChanged({ $global:ConfUi.Map[$k] = if($chk.Checked){'true'} else {'false'} }); $cfgPanel.Controls.Add($chk); $global:ConfUi.Controls[$k]=$chk } else { $tb=New-Object System.Windows.Forms.TextBox; $tb.Text=$v; Set-Loc $tb $xCtrl $y; Set-Sz $tb 540 $h; $tb.Add_TextChanged({ $global:ConfUi.Map[$k] = $tb.Text }); $cfgPanel.Controls.Add($tb); $global:ConfUi.Controls[$k]=$tb }; $y+=28 } } -$txtExtra = New-Object System.Windows.Forms.TextBox -$txtExtra.Location = New-Object System.Drawing.Point(10, 362); $txtExtra.Size = New-Object System.Drawing.Size(860, 22) -$form.Controls.Add($txtExtra) +$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' } }) +$btnRcloneConfig.Add_Click({ try { $layout=Ensure-ToolsLayout -Root $txtInstall.Text; $rcloneExe=Ensure-RcloneExe -RcloneDir $layout.RClone; $rcloneConf=Join-Path $layout.Conf 'rclone.conf'; $cmd='cmd.exe'; $args="/k `"$rcloneExe`" config --config `"$rcloneConf`""; $p=Start-Process -FilePath $cmd -ArgumentList $args -PassThru; Track-Child $p; Write-Log "Avviato rclone config (PID=$($p.Id))" } catch { Write-Log "Errore avvio rclone config: $($_.Exception.Message)" 'ERR' } }) -$grpRclone = New-Object System.Windows.Forms.GroupBox -$grpRclone.Text = 'RClone (opzionale)'; $grpRclone.Location = New-Object System.Drawing.Point(10, 392); $grpRclone.Size = New-Object System.Drawing.Size(860, 230) -$form.Controls.Add($grpRclone) +$btnInstall = New-Object System.Windows.Forms.Button; $btnInstall.Text='Installa'; Set-Loc $btnInstall 10 652; Set-Sz $btnInstall 120 30; $form.Controls.Add($btnInstall) +$btnClose = New-Object System.Windows.Forms.Button; $btnClose.Text='Chiudi'; Set-Loc $btnClose 150 652; Set-Sz $btnClose 120 30; $form.Controls.Add($btnClose) -$chkRclone = New-Object System.Windows.Forms.CheckBox -$chkRclone.Text = 'Configura RClone'; $chkRclone.Location = New-Object System.Drawing.Point(10, 20); $chkRclone.Size = New-Object System.Drawing.Size(200, 22) -$grpRclone.Controls.Add($chkRclone) - -$chkRcloneAuto = New-Object System.Windows.Forms.CheckBox -$chkRcloneAuto.Text = 'Scarica/aggiorna automaticamente rclone.exe' -$chkRcloneAuto.Location = New-Object System.Drawing.Point(200, 20); $chkRcloneAuto.Size = New-Object System.Drawing.Size(300, 22); $chkRcloneAuto.Checked = $true -$grpRclone.Controls.Add($chkRcloneAuto) - -$lblRRemote = New-Object System.Windows.Forms.Label -$lblRRemote.Text = 'Remote di destinazione (es. "mioRemote:Backup/AdHoc")' -$lblRRemote.Location = New-Object System.Drawing.Point(10, 50); $lblRRemote.Size = New-Object System.Drawing.Size(360, 20) -$grpRclone.Controls.Add($lblRRemote) - -$txtRRemote = New-Object System.Windows.Forms.TextBox -$txtRRemote.Location = New-Object System.Drawing.Point(10, 70); $txtRRemote.Size = New-Object System.Drawing.Size(830, 22) -$grpRclone.Controls.Add($txtRRemote) - -$chkRcloneOverwriteConf = New-Object System.Windows.Forms.CheckBox -$chkRcloneOverwriteConf.Text = 'Sovrascrivi rclone.conf (sotto)' -$chkRcloneOverwriteConf.Location = New-Object System.Drawing.Point(10, 100); $chkRcloneOverwriteConf.Size = New-Object System.Drawing.Size(220, 22) -$chkRcloneOverwriteConf.Checked = $false -$grpRclone.Controls.Add($chkRcloneOverwriteConf) - -$lblRConf = New-Object System.Windows.Forms.Label -$lblRConf.Text = 'Contenuto rclone.conf (incolla qui il tuo, oppure usa lo scheletro come base)' -$lblRConf.Location = New-Object System.Drawing.Point(10, 122); $lblRConf.Size = New-Object System.Drawing.Size(560, 20) -$grpRclone.Controls.Add($lblRConf) - -$txtRConf = New-Object System.Windows.Forms.TextBox -$txtRConf.Multiline = $true; $txtRConf.ScrollBars = 'Vertical' -$txtRConf.Location = New-Object System.Drawing.Point(10, 144); $txtRConf.Size = New-Object System.Drawing.Size(830, 70) -$txtRConf.Font = New-Object System.Drawing.Font('Consolas', 9) -$txtRConf.Text = @" -# Esempio per S3 generico: -#[backupremote] -#type = s3 -#provider = Other -#endpoint = https://s3.example.com -#access_key_id = YOUR_ACCESS_KEY -#secret_access_key = YOUR_SECRET -#region = us-east-1 -"@ -$grpRclone.Controls.Add($txtRConf) - -$lblStatus = New-Object System.Windows.Forms.Label -$lblStatus.Text = 'Pronto.'; $lblStatus.Location = New-Object System.Drawing.Point(10, 634); $lblStatus.Size = New-Object System.Drawing.Size(300, 22) -$form.Controls.Add($lblStatus) - -$txtLog = New-Object System.Windows.Forms.TextBox -$txtLog.Multiline = $true; $txtLog.ScrollBars = 'Vertical' -$txtLog.Location = New-Object System.Drawing.Point(10, 658); $txtLog.Size = New-Object System.Drawing.Size(860, 90) -$txtLog.Font = New-Object System.Drawing.Font('Consolas', 9) -$form.Controls.Add($txtLog) - -$global:LogTextBox = $txtLog - -$btnInstall = New-Object System.Windows.Forms.Button -$btnInstall.Text = 'Installa'; $btnInstall.Size = New-Object System.Drawing.Size(100, 30) -$btnInstall.Location = New-Object System.Drawing.Point(770, 758) -$btnInstall.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right -$form.Controls.Add($btnInstall) - -$btnClose = New-Object System.Windows.Forms.Button -$btnClose.Text = 'Chiudi'; $btnClose.Size = New-Object System.Drawing.Size(100, 30) -$btnClose.Location = New-Object System.Drawing.Point(660, 758) -$btnClose.Anchor = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right -$form.Controls.Add($btnClose) - -$form.Add_Shown({ - try { - $x = [int]$form.ClientSize.Width - [int]$btnInstall.Width - 20 - $y = [int]$form.ClientSize.Height - [int]$btnInstall.Height - 20 - $btnInstall.Location = New-Object System.Drawing.Point -ArgumentList $x, $y - $x2 = [int]$form.ClientSize.Width - [int]$btnClose.Width - 130 - $y2 = [int]$form.ClientSize.Height - [int]$btnClose.Height - 20 - $btnClose.Location = New-Object System.Drawing.Point -ArgumentList $x2, $y2 - } catch { - Write-Log "Posizionamento pulsanti fallito: $($_.Exception.Message)" 'ERR' - } -}) - -$btnBrowse.Add_Click({ - $dlg = New-Object System.Windows.Forms.FolderBrowserDialog - $dlg.Description = 'Scegli la cartella di installazione' - $dlg.SelectedPath = $txtInstall.Text - if ($dlg.ShowDialog() -eq 'OK') { $txtInstall.Text = $dlg.SelectedPath } -}) - -$btnClose.Add_Click({ $form.Close() }) - -function Get-SchTasksDayCode($comboIndex) { - switch ($comboIndex) { - 0 { 'MON' } 1 { 'TUE' } 2 { 'WED' } 3 { 'THU' } 4 { 'FRI' } 5 { 'SAT' } 6 { 'SUN' } default { 'MON' } - } -} +$lblLog = New-Object System.Windows.Forms.Label; $lblLog.Text='Log'; Set-Loc $lblLog 10 692; Set-Sz $lblLog 50 20; $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 +$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 { - $lblStatus.Text = 'In esecuzione...' - $installPath = $txtInstall.Text.Trim() - $scriptUrl = $txtScriptUrl.Text.Trim() - $confUrl = $txtConfUrl.Text.Trim() - $extraArgs = $txtExtra.Text.Trim() - - if (-not $installPath) { throw 'Cartella di installazione non valida.' } - Ensure-Dir -Path $installPath - - $global:LogFile = Join-Path $installPath ("install_log_{0}.txt" -f (Get-Date -Format 'yyyyMMdd_HHmmss')) - Write-Log "Percorso predefinito: $installPath" - Write-Log "Log su file: $global:LogFile" - - $scriptLocal = Join-Path $installPath 'AdHoc_Backup.ps1' # locale; remoto è AdHoc-Backup.ps1 - $confLocal = Join-Path $installPath 'backup.conf' - - Write-Log "Scarico Script di backup" - if (-not (Start-Download -Url $scriptUrl -Dest $scriptLocal)) { throw "Scaricamento script non riuscito." } - - if ((Test-Path -LiteralPath $confLocal) -and (-not $chkForceConf.Checked)) { - Write-Log "Config già presente e ForceConfig NON attivo: non sovrascrivo." - } else { - Write-Log "Scarico file di config" - if (-not (Start-Download -Url $confUrl -Dest $confLocal)) { throw "Scaricamento config non riuscito." } + $btnInstall.Enabled = $false + $root = $txtInstall.Text; Ensure-Dir $root + $layout = Ensure-ToolsLayout -Root $root + $scriptPath = Join-Path $root 'AdHoc-Backup.ps1' + if(-not (Start-Download -Url $txtScriptUrl.Text -Dest $scriptPath)){ throw "Impossibile scaricare lo script principale." } + $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' } } + 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) } - - if ($chkRclone.Checked) { - $rcloneDir = Join-Path $installPath 'RClone' - $rcloneExe = Join-Path $rcloneDir 'rclone.exe' - $rcloneConf = Join-Path $rcloneDir 'rclone.conf' - $remoteTxt = Join-Path $rcloneDir 'rclone-remote.txt' - - Ensure-Dir -Path $rcloneDir - - if ($chkRcloneAuto.Checked -and (-not (Test-Path -LiteralPath $rcloneExe))) { - try { - $zipUrl = 'https://downloads.rclone.org/rclone-current-windows-amd64.zip' - $tmpZip = Join-Path ([IO.Path]::GetTempPath()) ('rclone_' + [Guid]::NewGuid().ToString() + '.zip') - if (-not (Start-Download -Url $zipUrl -Dest $tmpZip)) { throw "Download rclone fallito." } - - $tmpExtract = Join-Path ([IO.Path]::GetTempPath()) ('rclone_extract_' + [Guid]::NewGuid().ToString()) - Ensure-Dir -Path $tmpExtract - Expand-Zip -ZipPath $tmpZip -DestDir $tmpExtract - - $exe = Get-ChildItem -Path $tmpExtract -Recurse -Filter 'rclone.exe' | Select-Object -First 1 - if (-not $exe) { throw "rclone.exe non trovato nello ZIP." } - Copy-Item -Path $exe.FullName -Destination $rcloneExe -Force - Write-Log "rclone.exe installato in $rcloneExe" - Remove-Item $tmpZip -Force -ErrorAction SilentlyContinue - Remove-Item $tmpExtract -Recurse -Force -ErrorAction SilentlyContinue - } catch { - Write-Log "Installazione automatica rclone fallita: $($_.Exception.Message)" 'ERR' - } - } else { - Write-Log "Auto-download rclone disattivato o rclone già presente." - } - - if ($txtRConf.Text.Trim().Length -gt 0 -and ($chkRcloneOverwriteConf.Checked -or -not (Test-Path -LiteralPath $rcloneConf))) { - Set-Content -Path $rcloneConf -Value $txtRConf.Text -Encoding UTF8 - Write-Log "rclone.conf scritto in $rcloneConf" - } elseif (Test-Path -LiteralPath $rcloneConf) { - Write-Log "rclone.conf già presente e non sovrascritto." - } else { - Write-Log "Nessun contenuto rclone.conf fornito; salto creazione." - } - - if ($txtRRemote.Text.Trim().Length -gt 0) { - Set-Content -Path $remoteTxt -Value $txtRRemote.Text.Trim() -Encoding UTF8 - Write-Log "Remote salvato in $remoteTxt" - } + 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; Track-Child $p; Write-Log "Esecuzione avviata (PID=$($p.Id))." } + Write-Log "Installazione completata." + } catch { Write-Log "Errore durante l'installazione: $($_.Exception.Message)" 'ERR' } + finally { $btnInstall.Enabled = $true } +}) +$btnClose.Add_Click({ $form.Close() }) - if ($chkSchedule.Checked) { - $schedule = if ($cmbFreq.SelectedIndex -eq 0) { 'DAILY' } else { 'WEEKLY' } - $dayCode = Get-SchTasksDayCode $cmbDOW.SelectedIndex - - $ok = New-DailyOrWeeklyTask -TaskName $txtTask.Text.Trim() -ScriptPath $scriptLocal -ConfigPath $confLocal -Time $timePicker.Value -Schedule $schedule -Day $dayCode -Quiet:($chkQuiet.Checked) - if (-not $ok) { throw "Impostazione attività pianificata fallita." } - } - - if (-not $chkNoRun.Checked) { - $args = @('-NoProfile','-ExecutionPolicy','Bypass','-File', $scriptLocal, '-Config', $confLocal) - if ($chkQuiet.Checked) { $args += '-Quiet' } - if ($extraArgs) { $args += $extraArgs } - Write-Log "Eseguo ora: powershell.exe $($args -join ' ')" - try { - $psi = New-Object System.Diagnostics.ProcessStartInfo - $psi.FileName = 'powershell.exe' - $psi.Arguments = $args -join ' ' - $psi.UseShellExecute = $false - $psi.CreateNoWindow = $true - $p = [System.Diagnostics.Process]::Start($psi) - Write-Log "Processo avviato, PID=$($p.Id)" - } catch { - Write-Log "Avvio immediato fallito: $($_.Exception.Message)" 'ERR' +# FIX: non usare $pid (conflitto con $PID automatico) +$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 } } - } else { - Write-Log "Avvio immediato disattivato (NoRun)." - } - - $lblStatus.Text = 'Completato.' - } catch { - $lblStatus.Text = 'Errore.' - Write-Log $_.Exception.Message 'ERR' + } catch {} } }) -Write-Log "Percorso predefinito: $defaultInstall" +$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' } [void]$form.ShowDialog()