<# .SYNOPSIS Instala el agente SoporteAI en esta PC. Arranca al iniciar sesión, corre en segundo plano. .EXAMPLE .\install.ps1 -ServerUrl "wss://soporte.negotiolandia.com" -ClientToken "tok_xxx" -Name "PC-Juan" .EXAMPLE .\install.ps1 -Uninstall .PARAMETER ServerUrl URL WSS del relay, ej: wss://soporte.negotiolandia.com .PARAMETER ClientToken Token único asignado por el técnico (algo como tok_xxx). .PARAMETER Name Nombre legible que verá el técnico. Default: nombre del equipo. .PARAMETER InstallDir Carpeta destino. Default: %LOCALAPPDATA%\SoporteAI (no requiere admin). .PARAMETER Uninstall Quita la tarea programada, detiene el proceso y borra la carpeta. #> param( [string]$ServerUrl = "wss://soporte.negotiolandia.com", [string]$ClientToken = "", [string]$Name = $env:COMPUTERNAME, [string]$InstallDir = "$env:LOCALAPPDATA\SoporteAI", [switch]$Uninstall ) $ErrorActionPreference = "Stop" $autoStartName = "SoporteAI Agent" $autoStartKey = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" # ---------- Uninstall ---------- if ($Uninstall) { Write-Host "Desinstalando SoporteAI..." -ForegroundColor Yellow # 1) quitar autoarranque (Registry Run + tarea programada legacy si existe) try { Remove-ItemProperty -Path $autoStartKey -Name $autoStartName -ErrorAction Stop Write-Host " Autoarranque (registry) eliminado" } catch { Write-Host " (entry de registry no existía)" } try { Unregister-ScheduledTask -TaskName $autoStartName -Confirm:$false -ErrorAction Stop Write-Host " Tarea programada legacy eliminada" } catch { } # 2) detener proceso try { Get-Process -Name "soporteai-agent" -ErrorAction Stop | Stop-Process -Force Write-Host " Proceso detenido" } catch { Write-Host " (proceso no estaba corriendo)" } # 3) borrar carpeta (con retries — PyInstaller bootloader puede tardar en soltar el archivo) if (Test-Path $InstallDir) { $attempt = 0 while ($attempt -lt 6 -and (Test-Path $InstallDir)) { Start-Sleep -Milliseconds 500 try { Remove-Item -Recurse -Force $InstallDir -ErrorAction Stop } catch { } $attempt++ } if (Test-Path $InstallDir) { Write-Warning " No se pudo borrar $InstallDir (algun proceso puede tenerlo abierto). Borralo a mano." } else { Write-Host " Carpeta eliminada: $InstallDir" } } Write-Host "Desinstalado." -ForegroundColor Green exit 0 } # ---------- Validación ---------- if (-not $ServerUrl) { Write-Error "Falta -ServerUrl. Uso: .\install.ps1 -ServerUrl 'wss://soporte.negotiolandia.com'" exit 1 } if ($ServerUrl -notmatch "^wss?://") { Write-Error "ServerUrl debe empezar con wss:// (o ws:// para testing local)" exit 1 } # Si no se pasa ClientToken, el agente se auto-registra al primer arranque. # El técnico verá la PC como "pendiente" y la aprobará desde su lado. # ---------- Localizar el .exe (local o descarga remota) ---------- $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Get-Location } $exeSource = Join-Path $scriptDir "soporteai-agent.exe" if (-not (Test-Path $exeSource)) { # No está local: intentar descargar desde el servidor. # Convertir wss:// a https:// para la URL de descarga. $httpsBase = $ServerUrl -replace '^wss://', 'https://' -replace '^ws://', 'http://' $httpsBase = $httpsBase.TrimEnd('/') $downloadUrl = "$httpsBase/downloads/soporteai-agent.exe" $exeSource = Join-Path $env:TEMP "soporteai-agent.exe" Write-Host " Descargando agente desde $downloadUrl ..." try { # TLS 1.2 mínimo (algunos Windows viejos lo necesitan explícito) [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 } catch {} try { Invoke-WebRequest -Uri $downloadUrl -OutFile $exeSource -UseBasicParsing -TimeoutSec 120 } catch { Write-Error "No se pudo descargar el agente desde $downloadUrl : $_" exit 1 } if (-not (Test-Path $exeSource) -or (Get-Item $exeSource).Length -lt 1MB) { Write-Error "El archivo descargado parece corrupto (menor a 1 MB)." exit 1 } Write-Host " Descarga completa: $([math]::Round((Get-Item $exeSource).Length/1MB,1)) MB" } Write-Host "" Write-Host "Instalando SoporteAI" -ForegroundColor Cyan Write-Host " Server : $ServerUrl" Write-Host " Nombre : $Name" Write-Host " Destino : $InstallDir" Write-Host "" # ---------- Detener instancia previa si existe ---------- try { Get-Process -Name "soporteai-agent" -ErrorAction Stop | Stop-Process -Force Write-Host " Instancia anterior detenida" Start-Sleep -Milliseconds 500 } catch {} # ---------- Crear directorio y copiar exe ---------- New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null $exeDest = Join-Path $InstallDir "soporteai-agent.exe" Copy-Item $exeSource $exeDest -Force Write-Host " [1/4] exe copiado a $exeDest" # ---------- Escribir config.json ---------- # Preservar token previo si existe (re-instalación) y no se pasó uno nuevo. $configPath = Join-Path $InstallDir "config.json" $existingToken = "" $existingClientId = "" if (Test-Path $configPath) { try { $prev = Get-Content $configPath -Raw -Encoding UTF8 | ConvertFrom-Json if ($prev.client_token) { $existingToken = $prev.client_token } if ($prev.client_id) { $existingClientId = $prev.client_id } } catch { } } $tokenToWrite = if ($ClientToken) { $ClientToken } elseif ($existingToken) { $existingToken } else { "" } $configObj = [ordered]@{ server_url = $ServerUrl client_token = $tokenToWrite name = $Name } if ($existingClientId) { $configObj.client_id = $existingClientId } # Escribir JSON sin BOM (UTF8 plain). PowerShell 5.1's "UTF8" añade BOM y rompe Python. $jsonText = $configObj | ConvertTo-Json [System.IO.File]::WriteAllText($configPath, $jsonText, [System.Text.UTF8Encoding]::new($false)) if ($tokenToWrite) { Write-Host " [2/4] config.json (token existente preservado)" } else { Write-Host " [2/4] config.json (sin token: el agente se auto-registrará)" } # ---------- Registrar autoarranque al login (Registry HKCU Run) ---------- # Limpiar cualquier tarea programada legacy de versiones anteriores try { Unregister-ScheduledTask -TaskName $autoStartName -Confirm:$false -ErrorAction Stop } catch { } $runCommand = "`"$exeDest`" --config `"$configPath`"" Set-ItemProperty -Path $autoStartKey -Name $autoStartName -Value $runCommand Write-Host " [3/4] autoarranque registrado en HKCU\Run" # ---------- Iniciar ahora ---------- # Usar Start-Process (con UseShellExecute por defecto) — compatible con .exe PyInstaller $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $exeDest $startInfo.Arguments = "--config `"$configPath`"" $startInfo.WorkingDirectory = $InstallDir $startInfo.UseShellExecute = $true $startInfo.WindowStyle = "Hidden" [System.Diagnostics.Process]::Start($startInfo) | Out-Null Start-Sleep -Seconds 3 $running = Get-Process -Name "soporteai-agent" -ErrorAction SilentlyContinue if ($running) { Write-Host " [4/4] agente arrancado (PID $($running.Id -join ', '))" } else { Write-Warning " [4/4] el agente no aparece corriendo todavia. Revisa el log:" Write-Warning " $env:LOCALAPPDATA\SoporteAI\agent.log" } Write-Host "" Write-Host "Instalación completa." -ForegroundColor Green Write-Host "" if (-not $tokenToWrite) { Write-Host "Esta PC aparecerá en la consola de tu ingeniero asignado, pendiente de habilitación." -ForegroundColor Cyan Write-Host "Una vez habilitada, podrá iniciar sesiones de asistencia (siempre con tu autorización)." -ForegroundColor Cyan Write-Host "" } Write-Host "Lo que pasa de ahora en adelante:" Write-Host " - El agente arranca solo cada vez que inicies sesión en Windows." Write-Host " - Cuando tu ingeniero solicite asistencia, verás un diálogo y debes Aceptar." Write-Host " - Todas las acciones quedan registradas en:" Write-Host " $env:USERPROFILE\.soporteai_audit.log" Write-Host "" Write-Host "Para desinstalar mas tarde:" Write-Host " .\install.ps1 -Uninstall" Write-Host ""