Update install-AdHoc-Backup.ps1

This commit is contained in:
Mattia Tadini 2025-11-10 11:02:45 +00:00
parent b2b25a5d64
commit bb1284cb64

View File

@ -1,458 +1,346 @@
<# =====================================================================
install-AdHoc-Backup.ps1
- Menù interattivo:
1) Installa lo script e l'ambiente
2) Crea pianificazione (Scheduled Task)
3) Modifica configurazione (backup.conf) con guida in italiano
<#
install-AdHoc-Backup.ps1
Menù di installazione / pianificazione / configurazione per AdHoc-Backup.ps1
#>
Requisiti:
- Eseguire come Amministratore per creare la pianificazione con SYSTEM.
- Tenere nella stessa cartella:
- AdHoc-Backup.ps1
- backup.conf
# ==========================
# CONFIGURAZIONE INIZIALE
# ==========================
# Cartella dove installeremo script e config
$InstallRoot = "C:\polo\script"
# Nome dello script di backup vero e proprio
$BackupScriptName = "AdHoc-Backup.ps1"
# Nome del file di configurazione
$ConfigFileName = "backup.conf"
Note:
- Rispettate le chiavi del tuo backup.conf e il suo "manuale" (liste con |,
commenti con #, ecc.). Alcune spiegazioni sono riportate nei prompt.
- Rclone: viene posizionato in $BackupRoot\RClone\rclone.exe. Se non presente,
lo script prova a copiarlo da installazioni esistenti o può scaricarlo.
===================================================================== #>
# (OPZIONALE) Base URL da cui scaricare i file
# Metti l'URL del tuo webserver / share http se li distribuisci così
$DownloadBaseUrl = "" # es. "https://intranet/polo/scripts"
[CmdletBinding()]
param(
[string]$ConfigPath, # Facoltativo: percorso a backup.conf (default: .\backup.conf)
[string]$BackupRootOverride # Facoltativo: forza BackupRoot (altrimenti legge da backup.conf)
# Elenco dei file da scaricare in modalità "installa lo script"
$FilesToDownload = @(
$BackupScriptName,
$ConfigFileName
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
# ==========================
# FUNZIONI DI SUPPORTO
# ==========================
function Write-Title($text) {
Write-Host ""
Write-Host "=== $text ===" -ForegroundColor Cyan
function Ensure-Folder {
param([string]$Path)
if (-not (Test-Path $Path)) {
Write-Host "Creo la cartella $Path ..." -ForegroundColor Yellow
New-Item -ItemType Directory -Path $Path -Force | Out-Null
}
}
function Pause-Enter($msg="Premi INVIO per continuare...") {
Write-Host ""
Read-Host $msg | Out-Null
function Download-File {
param(
[string]$Url,
[string]$Destination
)
if ([string]::IsNullOrWhiteSpace($Url)) {
Write-Warning "URL non impostato per $Destination, salto il download."
return
}
try {
Write-Host "Scarico $Url -> $Destination"
Invoke-WebRequest -Uri $Url -OutFile $Destination -UseBasicParsing
}
catch {
Write-Warning "Impossibile scaricare $Url. Errore: $($_.Exception.Message)"
}
}
function Read-Default($prompt, $default) {
if ([string]::IsNullOrEmpty($default)) {
return Read-Host "$prompt"
function Read-YesNo {
param(
[string]$Message,
[bool]$Default = $true
)
if ($Default) {
$msg = "$Message [S/n]"
} else {
$ans = Read-Host "$prompt [$default]"
if ([string]::IsNullOrEmpty($ans)) { return $default }
return $ans
$msg = "$Message [s/N]"
}
$ans = Read-Host $msg
if ([string]::IsNullOrWhiteSpace($ans)) {
return $Default
}
$ans = $ans.Trim().ToLower()
return $ans.StartsWith("s")
}
function Read-YesNo($prompt, [bool]$default=$true) {
$defTxt = if ($default) { "S/n" } else { "s/N" }
while ($true) {
$ans = Read-Host "$prompt ($defTxt)"
if ([string]::IsNullOrWhiteSpace($ans)) { return $default }
switch ($ans.ToLower()) {
's' { return $true }
'si' { return $true }
'y' { return $true }
'n' { return $false }
'no' { return $false }
default { Write-Host "Inserisci 's' o 'n'." -ForegroundColor Yellow }
function Get-ConfigValue {
param(
[string]$Path,
[string]$Key
)
if (-not (Test-Path $Path)) { return "" }
$line = Get-Content $Path | Where-Object { $_ -match "^\s*$([regex]::Escape($Key))\s*=" } | Select-Object -First 1
if ($null -eq $line) { return "" }
return ($line -split "=",2)[1].Trim()
}
function Set-ConfigValue {
param(
[string]$Path,
[string]$Key,
[string]$Value
)
$Value = $Value.Trim()
if (-not (Test-Path $Path)) {
"$Key=$Value" | Set-Content -Path $Path -Encoding UTF8
return
}
$content = Get-Content $Path
$found = $false
for ($i = 0; $i -lt $content.Count; $i++) {
if ($content[$i] -match "^\s*$([regex]::Escape($Key))\s*=") {
$content[$i] = "$Key=$Value"
$found = $true
break
}
}
if (-not $found) {
$content += "$Key=$Value"
}
$content | Set-Content -Path $Path -Encoding UTF8
}
function Test-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
return $p.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
function Ensure-DefaultConfig {
param([string]$ConfigPath)
if (Test-Path $ConfigPath) { return }
Write-Host "File di configurazione non trovato, ne creo uno di base..." -ForegroundColor Yellow
@"
# backup.conf generato automaticamente
# Modificalo con l'opzione 3 del menù.
BackupRoot=C:\Backups_AdHoc
LocalRetentionDaysFiles=2
LocalRetentionDaysDb=2
RemoteRetentionDays=15
KeepLocalArchives=true
EnableFileBackup=true
EnableRcloneUpload=true
ArchiveSources=C:\Zucchetti\ahr90|C:\Zucchetti\NetSetup
EnableSqlBackup=true
SqlInstance=localhost\SQLEXPRESS
SqlUseWindowsAuth=true
SqlUser=sa
SqlPassword=
DbInclude=
DbExclude=master|model|msdb|tempdb
SqlCompressStage=true
SqlDropBakAfterZip=true
SevenZipCompressionLevel=1
RcloneRemoteDest=dropbox:/Backups_AdHoc/%COMPUTERNAME%
RcloneBwl=
RcloneExtraArgs=
MailEnabled=true
MailSmtpHost=relay.poloinformatico.it
MailSmtpPort=587
MailUseAuth=true
MailUser=
MailPassword=
MailFrom=backup@localhost
MailTo=it@poloinformatico.it
MailSubjectPref=[BACKUP ADHOC]
"@ | Set-Content -Path $ConfigPath -Encoding UTF8
}
# ---------- Config I/O (preserva commenti e ordine) ----------
class ConfigDoc {
[System.Collections.Generic.List[string]]$Lines
[hashtable]$Map
[hashtable]$Index
ConfigDoc() {
$this.Lines = [System.Collections.Generic.List[string]]::new()
$this.Map = @{}
$this.Index = @{}
}
}
# ==========================
# FUNZIONE 1: INSTALLA
# ==========================
function Install-AdHocBackup {
Write-Host "== INSTALLAZIONE SCRIPT BACKUP ==" -ForegroundColor Cyan
Ensure-Folder -Path $InstallRoot
function Load-Config([string]$path) {
if (-not (Test-Path -LiteralPath $path)) {
throw "File di configurazione non trovato: $path"
}
$doc = [ConfigDoc]::new()
$raw = Get-Content -LiteralPath $path -Raw -Encoding UTF8 -ErrorAction Stop
foreach ($ln in ($raw -split "`r?`n", [System.StringSplitOptions]::None)) {
$doc.Lines.Add($ln)
}
for ($i=0; $i -lt $doc.Lines.Count; $i++) {
$line = $doc.Lines[$i]
if ($line -match '^\s*#') { continue }
if ($line -match '^\s*([A-Za-z0-9_]+)\s*=(.*)$') {
$k = $matches[1]
$v = ($matches[2]).Trim()
$doc.Map[$k] = $v
$doc.Index[$k] = $i
}
}
return $doc
}
function Save-Config([ConfigDoc]$doc, [string]$path) {
$backup = "$path.bak_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
Copy-Item -LiteralPath $path -Destination $backup -Force -ErrorAction Stop
# Riscrivo solo le linee delle chiavi (preservo commenti/righe vuote)
foreach ($k in $doc.Map.Keys) {
$v = [string]$doc.Map[$k]
if ($doc.Index.ContainsKey($k)) {
$idx = [int]$doc.Index[$k]
$doc.Lines[$idx] = "$k=$v"
foreach ($f in $FilesToDownload) {
$dest = Join-Path $InstallRoot $f
if (-not [string]::IsNullOrWhiteSpace($DownloadBaseUrl)) {
$url = ($DownloadBaseUrl.TrimEnd('/')) + "/" + $f
Download-File -Url $url -Destination $dest
} else {
# Aggiungo nuove chiavi alla fine
if (-not $doc.Lines[$doc.Lines.Count-1].StartsWith('# AGGIUNTE AUTO')) {
$doc.Lines.Add("")
$doc.Lines.Add("# AGGIUNTE AUTO (create da install-AdHoc-Backup.ps1)")
# Se non abbiamo URL, non sovrascriviamo: assumiamo che lo script sia già nella stessa cartella
if (-not (Test-Path $dest)) {
Write-Warning "File $f non trovato e nessun URL impostato. Copialo manualmente in $InstallRoot."
}
$doc.Lines.Add("$k=$v")
}
}
[IO.File]::WriteAllLines($path, $doc.Lines, [Text.UTF8Encoding]::new($false))
return $backup
}
function Set-ConfigValue([ConfigDoc]$doc, [string]$key, [string]$value) {
$doc.Map[$key] = $value
}
$configPath = Join-Path $InstallRoot $ConfigFileName
Ensure-DefaultConfig -ConfigPath $configPath
# ---------- Validatori ----------
function Validate-IntNonNeg($valText, [int]$min=0, [int]$max=[int]::MaxValue) {
if ([string]::IsNullOrWhiteSpace($valText)) { return $null } # mantieni
[int]$n = 0
if (-not [int]::TryParse($valText, [ref]$n)) { throw "Numero non valido." }
if ($n -lt $min -or $n -gt $max) { throw "Valore fuori range ($min..$max)." }
return $n
}
function Validate-Port($valText) { return (Validate-IntNonNeg $valText 1 65535) }
function Normalize-BoolText($text, [bool]$current) {
if ([string]::IsNullOrWhiteSpace($text)) { return $current }
switch ($text.Trim().ToLower()) {
'true' { return $true }
'false' { return $false }
's' { return $true }
'si' { return $true }
'y' { return $true }
'n' { return $false }
'no' { return $false }
default { throw "Valore non valido. Usa: true/false oppure s/n" }
# Chiede subito se vuole pianificare
if (Read-YesNo "Vuoi creare una pianificazione (operazione consigliata)?" $true) {
Show-ScheduleMenu
}
# Chiede subito se vuole modificare configurazione
if (Read-YesNo "Vuoi modificare adesso la configurazione del file backup.conf?" $false) {
Edit-BackupConfig -ConfigPath $configPath
}
Write-Host "Installazione completata." -ForegroundColor Green
}
function Read-Secret($prompt, $currentPlain) {
Write-Host "$prompt (lascia vuoto per mantenere l'attuale)" -ForegroundColor Yellow
$sec = Read-Host -AsSecureString
if ($sec.Length -eq 0) { return $currentPlain }
return [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec))
}
# ---------- Editor guidato backup.conf ----------
function Edit-BackupConfig {
param([string]$cfgPath)
Write-Title "Modifica configurazione (backup.conf)"
$doc = Load-Config $cfgPath
# Mappa variabili -> descrizione & validazione
$vars = @(
@{Key='BackupRoot'; Desc='Cartella radice dei backup (archivi, log, tool).'; Example='C:\Backups_AdHoc' },
@{Key='LocalRetentionDaysFiles'; Desc='Giorni di retention dei file in locale (\Files). 0=disabilita.'; Validate='int' },
@{Key='LocalRetentionDaysDb'; Desc='Giorni di retention dei database in locale (\Databases). 0=disabilita.'; Validate='int' },
@{Key='RemoteRetentionDays'; Desc='Giorni di retention su remoto (rclone).'; Validate='int' },
@{Key='KeepLocalArchives'; Desc='true=mantiene copie locali, false=le rimuove dopo upload.'; Validate='bool' },
@{Key='EnableFileBackup'; Desc='true=abilita backup di cartelle/sorgenti in .7z.'; Validate='bool' },
@{Key='EnableRcloneUpload'; Desc='true=abilita upload dei backup con rclone.'; Validate='bool' },
@{Key='ArchiveSources'; Desc='Sorgenti da archiviare, separa con | (es.: C:\Dati|D:\Export|\\nas\share).'; Example='C:\Zucchetti\ahr90|C:\Zucchetti\NetSetup' },
@{Key='EnableSqlBackup'; Desc='true=abilita backup dei DB SQL Server.'; Validate='bool' },
@{Key='SqlInstance'; Desc='Istanza SQL (es.: localhost, .\SQLEXPRESS, 192.168.1.10,1433, nome\istanza).'},
@{Key='SqlUseWindowsAuth'; Desc='true=Windows Authentication; false=SQL Auth (usa SqlUser/SqlPassword).'; Validate='bool' },
@{Key='SqlUser'; Desc='Utente SQL (usato solo se SqlUseWindowsAuth=false).'},
@{Key='SqlPassword'; Desc='Password SQL (usata solo se SqlUseWindowsAuth=false).'; Secret=$true },
@{Key='DbInclude'; Desc='Elenco DB da includere (|). Vuoto = auto-detect dei DB utente online.'; Example='DBProduzione|DBCRM|DBContabilita' },
@{Key='DbExclude'; Desc='DB da escludere se DbInclude è vuoto (default: master|model|msdb|tempdb).', Example='master|model|msdb|tempdb' },
@{Key='SqlCompressStage'; Desc='true= comprime la cartella _sql_stage in .7z dopo il backup.'; Validate='bool' },
@{Key='SqlDropBakAfterZip'; Desc='true= elimina i .bak dopo la compressione.'; Validate='bool' },
@{Key='SevenZipCompressionLevel'; Desc='Livello compressione 7-Zip (0..9). 13 spesso è il miglior compromesso.'; Validate='int' },
@{Key='RcloneRemoteDest'; Desc='Destinazione rclone in formato REMOTE:percorso (usa %COMPUTERNAME% se utile).'; Example='dropbox:/Backups_AdHoc/%COMPUTERNAME%' },
@{Key='RcloneBwl'; Desc='Limitazione di banda (es: 10M). Vuoto = nessun limite.'},
@{Key='RcloneExtraArgs'; Desc='Argomenti extra rclone separati da | (es: --fast-list|--s3-chunk-size=64M).'},
@{Key='MailEnabled'; Desc='true= invia una mail di report a fine job.'; Validate='bool' },
@{Key='MailSmtpHost'; Desc='Host SMTP (relay).'},
@{Key='MailSmtpPort'; Desc='Porta SMTP (tipico 587).'; Validate='port' },
@{Key='MailUseAuth'; Desc='true= il relay richiede autenticazione (compila utente/password).'; Validate='bool' },
@{Key='MailUser'; Desc='Utente SMTP (se richiesto).'},
@{Key='MailPassword'; Desc='Password SMTP (se richiesta).'; Secret=$true },
@{Key='MailFrom'; Desc='Indirizzo mittente.'},
@{Key='MailTo'; Desc='Destinatari separati da |.'; Example='it@azienda.it|sysadmin@azienda.it' },
@{Key='MailSubjectPref'; Desc='Prefisso oggetto (puoi lasciare spazio finale).' }
# ==========================
# FUNZIONE 2: PIANIFICAZIONE
# ==========================
function Create-BackupScheduledTask {
param(
[ValidateSet("Daily","Weekly")]
[string]$Mode = "Daily"
)
$changes = @{}
foreach ($v in $vars) {
$key = $v.Key
$cur = $doc.Map[$key]
Write-Host ""
Write-Host ">> $key" -ForegroundColor Green
Write-Host " $($v.Desc)"
if ($v.ContainsKey('Example') -and $v.Example) { Write-Host " Esempio: $($v.Example)" -ForegroundColor DarkGray }
if ($v.ContainsKey('Secret') -and $v.Secret) {
$newVal = Read-Secret "Nuovo valore per $key" $cur
} else {
$newValRaw = Read-Default "Inserisci un nuovo valore per $key (INVIO = mantieni)" $cur
# Validazione
if ($v.ContainsKey('Validate')) {
switch ($v.Validate) {
'int' { $n = Validate-IntNonNeg $newValRaw | Out-Null; if ($null -ne $n) { $newValRaw = [string]$n } }
'bool' { $b = Normalize-BoolText $newValRaw ($cur -eq 'true'); $newValRaw = if ($b) { 'true' } else { 'false' } }
'port' { $p = Validate-Port $newValRaw | Out-Null; if ($null -ne $p) { $newValRaw = [string]$p } }
}
}
$newVal = $newValRaw
}
if ($newVal -ne $cur) {
$changes[$key] = @{Old=$cur; New=$newVal}
Set-ConfigValue -doc $doc -key $key -value $newVal
}
$backupScriptPath = Join-Path $InstallRoot $BackupScriptName
if (-not (Test-Path $backupScriptPath)) {
Write-Warning "Lo script di backup non è stato trovato in $backupScriptPath. Installa prima (opzione 1)."
return
}
if ($changes.Count -gt 0) {
Write-Host ""
Write-Title "Riepilogo modifiche"
$changes.GetEnumerator() | ForEach-Object {
"{0} : '{1}' -> '{2}'" -f $_.Key, $_.Value.Old, $_.Value.New
} | Write-Host
# Ora di esecuzione
$hour = Read-Host "Inserisci l'ora di esecuzione (0-23) [default 22]"
if ([string]::IsNullOrWhiteSpace($hour)) { $hour = 22 }
$minute = Read-Host "Inserisci i minuti (0-59) [default 30]"
if ([string]::IsNullOrWhiteSpace($minute)) { $minute = 30 }
if (Read-YesNo "Confermi il salvataggio delle modifiche?") {
$bk = Save-Config -doc $doc -path $cfgPath
Write-Host "Configurazione salvata: $cfgPath" -ForegroundColor Cyan
Write-Host "Backup creato: $bk" -ForegroundColor DarkCyan
} else {
Write-Host "Annullato. Nessuna modifica scritta." -ForegroundColor Yellow
}
$time = New-TimeSpan -Hours ([int]$hour) -Minutes ([int]$minute)
$at = (Get-Date).Date + $time
if ($Mode -eq "Weekly") {
$day = Read-Host "Giorno della settimana (Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday) [default Monday]"
if ([string]::IsNullOrWhiteSpace($day)) { $day = "Monday" }
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek $day -At $at
} else {
Write-Host "Nessuna modifica apportata." -ForegroundColor Yellow
$trigger = New-ScheduledTaskTrigger -Daily -At $at
}
Pause-Enter
}
# ---------- Installazione ambiente ----------
function Ensure-Folder($path) {
if (-not (Test-Path -LiteralPath $path)) {
New-Item -ItemType Directory -Path $path -Force | Out-Null
}
}
function Install-Environment {
param([string]$cfgPath)
Write-Title "Installazione ambiente"
$doc = Load-Config $cfgPath
# Determina BackupRoot
$backupRoot = if ($BackupRootOverride) { $BackupRootOverride } elseif ($doc.Map['BackupRoot']) { $doc.Map['BackupRoot'] } else { 'C:\Backups_AdHoc' }
$backupRoot = Read-Default "BackupRoot di destinazione" $backupRoot
Set-ConfigValue -doc $doc -key 'BackupRoot' -value $backupRoot
$null = Save-Config -doc $doc -path $cfgPath
# Struttura cartelle
$folders = @(
$backupRoot,
Join-Path $backupRoot 'Logs',
Join-Path $backupRoot 'Out',
Join-Path $backupRoot 'Files',
Join-Path $backupRoot 'Databases',
Join-Path $backupRoot '_sql_stage',
Join-Path $backupRoot 'Bin',
Join-Path $backupRoot 'RClone'
)
foreach ($f in $folders) { Ensure-Folder $f }
# Copia script principali
$srcScript = Join-Path $PSScriptRoot 'AdHoc-Backup.ps1'
if (-not (Test-Path -LiteralPath $srcScript)) {
throw "AdHoc-Backup.ps1 non trovato in $PSScriptRoot"
}
Copy-Item -LiteralPath $srcScript -Destination (Join-Path $backupRoot 'AdHoc-Backup.ps1') -Force
# Copia backup.conf
Copy-Item -LiteralPath $cfgPath -Destination (Join-Path $backupRoot 'backup.conf') -Force
# Rclone: binario + config locale nello stesso folder ($BackupRoot\RClone)
Ensure-Rclone -TargetRoot $backupRoot
Write-Host "Installazione completata in: $backupRoot" -ForegroundColor Cyan
Pause-Enter
}
function Ensure-Rclone {
param([string]$TargetRoot)
$rcloneDir = Join-Path $TargetRoot 'RClone'
$rcloneExe = Join-Path $rcloneDir 'rclone.exe'
$rcloneConf = Join-Path $rcloneDir 'rclone.conf'
Ensure-Folder $rcloneDir
if (-not (Test-Path -LiteralPath $rcloneExe)) {
Write-Host "rclone.exe non trovato in $rcloneDir, provo a reperirlo..." -ForegroundColor Yellow
$candidates = @(
"$env:ProgramFiles\rclone\rclone.exe",
"$env:ProgramFiles\Rclone\rclone.exe",
"$env:ProgramFiles(x86)\rclone\rclone.exe",
"$env:ProgramFiles(x86)\Rclone\rclone.exe",
"$env:SystemRoot\System32\rclone.exe"
) | Where-Object { Test-Path -LiteralPath $_ }
if ($candidates.Count -gt 0) {
Copy-Item -LiteralPath $candidates[0] -Destination $rcloneExe -Force
Write-Host "Copiato rclone da: $($candidates[0])" -ForegroundColor Green
}
else {
# Tentativo di download (puoi saltarlo se non vuoi traffico Internet)
try {
$zipUrl = "https://downloads.rclone.org/rclone-current-windows-amd64.zip"
$tmpZip = Join-Path $env:TEMP "rclone-current.zip"
Write-Host "Scarico rclone da $zipUrl ..." -ForegroundColor Yellow
Invoke-WebRequest -Uri $zipUrl -OutFile $tmpZip -UseBasicParsing
$tmpDir = Join-Path $env:TEMP "rclone_unzip_$([guid]::NewGuid().ToString('N'))"
Expand-Archive -LiteralPath $tmpZip -DestinationPath $tmpDir -Force
$found = Get-ChildItem -Path $tmpDir -Filter rclone.exe -Recurse | Select-Object -First 1
if ($null -ne $found) {
Copy-Item -LiteralPath $found.FullName -Destination $rcloneExe -Force
Write-Host "rclone scaricato e installato." -ForegroundColor Green
} else {
Write-Host "Impossibile trovare rclone.exe nello ZIP. Mettilo manualmente in $rcloneDir" -ForegroundColor Red
}
} catch {
Write-Host "Download rclone fallito: $($_.Exception.Message)" -ForegroundColor Red
Write-Host "Puoi copiare manualmente rclone.exe in $rcloneDir" -ForegroundColor Yellow
}
}
}
# Config rclone: importa quella utente se esiste, altrimenti crea skeleton
if (-not (Test-Path -LiteralPath $rcloneConf)) {
$userConf = Join-Path $env:APPDATA 'rclone\rclone.conf'
if (Test-Path -LiteralPath $userConf) {
Copy-Item -LiteralPath $userConf -Destination $rcloneConf -Force
Write-Host "Importata configurazione rclone da $userConf" -ForegroundColor Green
} else {
@"
# rclone.conf (skeleton) — definisci qui il REMOTE usato in backup.conf (RcloneRemoteDest)
# Esempio dropbox:
# [dropbox]
# type = dropbox
"@ | Set-Content -LiteralPath $rcloneConf -Encoding UTF8 -NoNewline
Write-Host "Creato skeleton rclone.conf in $rcloneConf" -ForegroundColor Yellow
}
}
# NB: il nostro backup script userà --config "$BackupRoot\RClone\rclone.conf" come da linea guida.
}
# ---------- Pianificazione ----------
function Create-Schedule {
param([string]$cfgPath)
if (-not (Test-Admin)) {
throw "Per creare la pianificazione occorrono privilegi di Amministratore."
}
$doc = Load-Config $cfgPath
$backupRoot = if ($BackupRootOverride) { $BackupRootOverride } elseif ($doc.Map['BackupRoot']) { $doc.Map['BackupRoot'] } else { 'C:\Backups_AdHoc' }
$scriptPath = Join-Path $backupRoot 'AdHoc-Backup.ps1'
$confPath = Join-Path $backupRoot 'backup.conf'
if (-not (Test-Path -LiteralPath $scriptPath)) { throw "Script backup non trovato in $scriptPath (esegui prima l'installazione)." }
if (-not (Test-Path -LiteralPath $confPath)) { throw "Configurazione non trovata in $confPath (esegui prima l'installazione)." }
Write-Title "Crea pianificazione (Scheduled Task)"
$taskName = Read-Default "Nome attività" "AdHoc Backup Giornaliero"
# Ora (HH:mm)
while ($true) {
$timeTxt = Read-Default "Orario giornaliero (HH:mm)" "22:30"
if ([TimeSpan]::TryParse($timeTxt, [ref]([TimeSpan]$null))) { break }
try { [DateTime]::ParseExact($timeTxt, 'HH:mm', $null) | Out-Null; break } catch { Write-Host "Formato non valido. Usa HH:mm (es. 22:30)" -ForegroundColor Yellow }
}
$atTime = [DateTime]::ParseExact($timeTxt, 'HH:mm', $null).TimeOfDay
$trigger = New-ScheduledTaskTrigger -Daily -At $atTime
$psArgs = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`" -Conf `"$confPath`""
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument $psArgs
$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$backupScriptPath`""
# Eseguiamo come SYSTEM con privilegi alti
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable `
-RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) `
-MultipleInstances IgnoreNew
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$taskName = "Backup AdHoc"
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
Write-Host "Attività pianificata creata: $taskName, orario $timeTxt (account SYSTEM)." -ForegroundColor Cyan
Pause-Enter
Write-Host "Pianificazione '$taskName' creata in modalità $Mode." -ForegroundColor Green
}
# ---------- Menù ----------
function Show-Menu {
Clear-Host
Write-Host ""
Write-Host "==========================" -ForegroundColor DarkCyan
Write-Host " INSTALL AZIENDALE BACKUP" -ForegroundColor DarkCyan
Write-Host "==========================" -ForegroundColor DarkCyan
Write-Host "1) Installa lo script e l'ambiente"
Write-Host "2) Crea pianificazione (Scheduled Task)"
Write-Host "3) Modifica configurazione (backup.conf)"
Write-Host "4) Esci"
Write-Host ""
}
# Percorso di default per backup.conf
if (-not $ConfigPath) { $ConfigPath = Join-Path $PSScriptRoot 'backup.conf' }
while ($true) {
Show-Menu
$choice = Read-Default "Seleziona un'opzione" "1"
function Show-ScheduleMenu {
Write-Host "== CREA PIANIFICAZIONE ==" -ForegroundColor Cyan
Write-Host "1) Giornaliera"
Write-Host "2) Settimanale"
Write-Host "0) Annulla"
$choice = Read-Host "Seleziona"
switch ($choice) {
'1' {
Install-Environment -cfgPath $ConfigPath
}
'2' {
Create-Schedule -cfgPath $ConfigPath
}
'3' {
Edit-BackupConfig -cfgPath $ConfigPath
}
'4' { break }
default {
Write-Host "Opzione non valida." -ForegroundColor Yellow
Pause-Enter
}
"1" { Create-BackupScheduledTask -Mode Daily }
"2" { Create-BackupScheduledTask -Mode Weekly }
default { Write-Host "Nessuna pianificazione creata." }
}
}
Write-Host "Uscita." -ForegroundColor DarkGray
# ==========================
# FUNZIONE 3: MODIFICA CONFIG
# ==========================
function Edit-BackupConfig {
param(
[string]$ConfigPath
)
if (-not (Test-Path $ConfigPath)) {
Write-Warning "File $ConfigPath non trovato. Lo creo adesso."
Ensure-DefaultConfig -ConfigPath $ConfigPath
}
# Descrizioni in italiano per aiutare l'utente
$Descriptions = [ordered]@{
"BackupRoot" = "Cartella radice dove vanno archivi, log e tool."
"LocalRetentionDaysFiles"= "Giorni di conservazione FILE in locale."
"LocalRetentionDaysDb" = "Giorni di conservazione DATABASE in locale."
"RemoteRetentionDays" = "Giorni di conservazione su destinazione remota (rclone)."
"KeepLocalArchives" = "true = tiene una copia locale dopo l'upload."
"EnableFileBackup" = "true = esegue il backup delle cartelle in ArchiveSources."
"EnableRcloneUpload" = "true = dopo il backup carica su cloud tramite rclone."
"ArchiveSources" = "Elenco cartelle/sorgenti da salvare, separate da | (pipe)."
"EnableSqlBackup" = "true = fa anche i backup dei database SQL Server."
"SqlInstance" = "Nome o indirizzo dell'istanza SQL Server (es. localhost\SQLEXPRESS)."
"SqlUseWindowsAuth" = "true = usa l'utente Windows corrente; false = usa SqlUser/SqlPassword."
"SqlUser" = "Utente SQL (se SqlUseWindowsAuth=false)."
"SqlPassword" = "Password SQL (se SqlUseWindowsAuth=false)."
"DbInclude" = "Elenco DB da includere (se vuoto li prende tutti tranne gli esclusi)."
"DbExclude" = "DB da escludere quando DbInclude è vuoto."
"SqlCompressStage" = "true = comprime i .bak in uno .7z."
"SqlDropBakAfterZip" = "true = elimina i .bak dopo la compressione."
"SevenZipCompressionLevel" = "0..9 livello di compressione 7z (1=veloce, 9=max)."
"RcloneRemoteDest" = "Destinazione rclone: REMOTO:percorso."
"RcloneBwl" = "Limite banda rclone (es. 10M) oppure vuoto."
"RcloneExtraArgs" = "Argomenti extra rclone separati da |."
"MailEnabled" = "true = invia mail di report."
"MailSmtpHost" = "Server SMTP/relay."
"MailSmtpPort" = "Porta SMTP (es. 587)."
"MailUseAuth" = "true = il relay richiede utente/password."
"MailUser" = "Utente SMTP."
"MailPassword" = "Password SMTP."
"MailFrom" = "Mittente mail."
"MailTo" = "Destinatari (separati da |)."
"MailSubjectPref" = "Prefisso dell'oggetto mail."
}
Write-Host "== MODIFICA GUIDATA backup.conf ==" -ForegroundColor Cyan
foreach ($key in $Descriptions.Keys) {
$current = Get-ConfigValue -Path $ConfigPath -Key $key
Write-Host ""
Write-Host "$key" -ForegroundColor Yellow
Write-Host " $($Descriptions[$key])"
Write-Host " Valore attuale: $current"
$newVal = Read-Host " Nuovo valore (invio per lasciare quello attuale)"
if (-not [string]::IsNullOrWhiteSpace($newVal)) {
Set-ConfigValue -Path $ConfigPath -Key $key -Value $newVal
Write-Host " -> Aggiornato."
} else {
Write-Host " -> Lasciato invariato."
}
}
Write-Host ""
Write-Host "Configurazione aggiornata in $ConfigPath" -ForegroundColor Green
}
# ==========================
# MENÙ PRINCIPALE
# ==========================
do {
Write-Host ""
Write-Host "==============================" -ForegroundColor DarkCyan
Write-Host " INSTALL ADHOC BACKUP - MENU"
Write-Host "==============================" -ForegroundColor DarkCyan
Write-Host "1) Installa / aggiorna script"
Write-Host "2) Crea pianificazione (giornaliera/settimanale)"
Write-Host "3) Modifica configurazione backup.conf"
Write-Host "0) Esci"
$sel = Read-Host "Seleziona"
$configPath = Join-Path $InstallRoot $ConfigFileName
switch ($sel) {
"1" { Install-AdHocBackup }
"2" { Show-ScheduleMenu }
"3" { Edit-BackupConfig -ConfigPath $configPath }
"0" { break }
default { Write-Host "Scelta non valida." -ForegroundColor Red }
}
} while ($true)
Write-Host "Uscita dallo script." -ForegroundColor Cyan