diff --git a/install-AdHoc-Backup-GUI.ps1 b/install-AdHoc-Backup-GUI.ps1 new file mode 100644 index 0000000..e4b4ce6 --- /dev/null +++ b/install-AdHoc-Backup-GUI.ps1 @@ -0,0 +1,467 @@ +#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) +#> + +Add-Type -AssemblyName System.Windows.Forms +Add-Type -AssemblyName System.Drawing +Add-Type -AssemblyName System.IO.Compression.FileSystem + +$ErrorActionPreference = 'Stop' + +function New-LogTimestamp { (Get-Date).ToString('yyyy-MM-dd HH:mm:ss') } + +$global:LogFile = $null +$global:LogTextBox = $null + +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([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 { + $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 + } + } +} + +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) +} + +function New-DailyOrWeeklyTask { + param( + [Parameter(Mandatory)][string]$TaskName, + [Parameter(Mandatory)][string]$ScriptPath, + [Parameter(Mandatory)][string]$ConfigPath, + [Parameter(Mandatory)][DateTime]$Time, + [ValidateSet('DAILY','WEEKLY')] [string]$Schedule = 'DAILY', + [ValidateSet('MON','TUE','WED','THU','FRI','SAT','SUN')] [string]$Day = 'MON', + [switch]$Quiet + ) + $psArgs = @( + '-NoProfile', + '-ExecutionPolicy', 'Bypass', + '-File', ('"{0}"' -f $ScriptPath), + '-Config', ('"{0}"' -f $ConfigPath) + ) + if ($Quiet) { $psArgs += '-Quiet' } + + $tr = 'powershell.exe ' + ($psArgs -join ' ') + $st = $Time.ToString('HH:mm') + + $cmd = @( + '/Create', + '/SC', $Schedule, + '/TN', ('"{0}"' -f $TaskName), + '/TR', ('"{0}"' -f $tr), + '/ST', $st, + '/RL', 'HIGHEST', + '/F' + ) + if ($Schedule -eq 'WEEKLY') { $cmd += @('/D', $Day) } + + Write-Log "Creo/aggiorno attività pianificata: $TaskName ($Schedule) alle $st" + try { + schtasks.exe @cmd | Out-Null + Write-Log "Attività pianificata impostata." + return $true + } catch { + Write-Log "Creazione attività fallita: $($_.Exception.Message)" 'ERR' + return $false + } +} + +$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) + +$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) + +$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:'; $lblTask.Location = New-Object System.Drawing.Point(180, 85); $lblTask.Size = New-Object System.Drawing.Size(80, 22) +$grpSched.Controls.Add($lblTask) + +$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) + +$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) + +$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) + +$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) + +$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' } + } +} + +$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." } + } + + 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 ($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' + } + } else { + Write-Log "Avvio immediato disattivato (NoRun)." + } + + $lblStatus.Text = 'Completato.' + } catch { + $lblStatus.Text = 'Errore.' + Write-Log $_.Exception.Message 'ERR' + } +}) + +Write-Log "Percorso predefinito: $defaultInstall" +[void]$form.ShowDialog()