957 lines
31 KiB
PowerShell
957 lines
31 KiB
PowerShell
<#
|
|
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() |