#Requires -Version 5.1 <# .SYNOPSIS Deshace la preparacion del ejercicio RT-2026 y deja el equipo como estaba antes. .DESCRIPTION Contraparte de rt-setup.ps1. Elimina, en este orden: 1. Las tareas programadas del ejercicio (RT-2026*). 2. Las carpetas de trabajo: C:\ProgramData\RT y C:\ProgramData\RT26. 3. Los registros por usuario: %LOCALAPPDATA%\RT-launcher. 4. El script descargado en C:\Windows\Temp\rt-setup.ps1. ANTES DE BORRAR NADA comprueba que el ejercicio se haya deshecho, porque RT26 contiene los manifiestos y los respaldos: es lo que permite revertir. Si encuentra material sin revertir, SE NIEGA a continuar y dice donde esta. Es deliberado: borrar los respaldos con ficheros cifrados pendientes dejaria esos ficheros sin forma de recuperar su nombre. .PARAMETER BaseUrl Raiz del centro de mando, por si hay que informar. No se usa para borrar nada. .PARAMETER Confirmar Obligatorio. Sin el, el script solo dice lo que HARIA, sin tocar nada. .PARAMETER ForzarSinComprobar Salta la comprobacion de material pendiente. Usar solo si se sabe lo que se hace: los ficheros cifrados que queden perderan su nombre original para siempre. .EXAMPLE .\rt-uninstall.ps1 No hace nada: avisa de que falta -Confirmar. .EXAMPLE .\rt-uninstall.ps1 -Confirmar Deshace la preparacion en este equipo, si no hay material sin revertir. #> [CmdletBinding()] param( [string]$BaseUrl = 'https://enormous-purely-maggot.ngrok-free.app/rt', [switch]$Confirmar, [switch]$ForzarSinComprobar, [switch]$SaltarRevertir ) $ErrorActionPreference = 'Continue' $carpetaRT = 'C:\ProgramData\RT' $carpetaRT26 = 'C:\ProgramData\RT26' $log = 'C:\Windows\Temp\rt-uninstall.log' try { Start-Transcript -Path $log -Append -ErrorAction SilentlyContinue | Out-Null } catch { } function Escribir { param([string]$Texto, [string]$Color = 'Gray') Write-Host $Texto -ForegroundColor $Color } $quien = [Security.Principal.WindowsIdentity]::GetCurrent().Name $esSistema = $quien -match 'SYSTEM$' $elevado = $esSistema -or [bool]([Security.Principal.WindowsPrincipal]::new( [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) Escribir '' Escribir '=== RT-2026 - deshacer la preparacion en este equipo ===' 'Cyan' Escribir ("equipo : {0}" -f $env:COMPUTERNAME) Escribir ("ejecuta : {0} elevado={1}" -f $quien, $elevado) Escribir ("registro: {0}" -f $log) Escribir '' if (-not $elevado) { Escribir 'Hay que ejecutarlo ELEVADO.' 'Red' Stop-Transcript -ErrorAction SilentlyContinue | Out-Null exit 1 } if (-not $Confirmar) { Escribir 'Falta -Confirmar. Sin ese parametro no se toca nada.' 'Yellow' Escribir 'Uso: .\rt-uninstall.ps1 -Confirmar -Execute' 'Yellow' Stop-Transcript -ErrorAction SilentlyContinue | Out-Null exit 1 } # --------------------------------------------------------------------------- # PERFILES DEL EQUIPO # # Se calcula AQUI, al principio, porque lo usan la reversion, la red de seguridad y el bloque # de registro y fondo. Antes vivia dentro del guardian, y al mover el guardian detras de la # reversion quedo definido tarde: los bucles recorrian una lista vacia y no hacian nada. # # Un perfil es una carpeta de C:\Users con su NTUSER.DAT: eso es lo que distingue a un perfil # real de las carpetas de sistema (Public, Default, Default User...). # --------------------------------------------------------------------------- $noSonPerfiles = @('Public', 'Default', 'Default User', 'All Users', 'WDAGUtilityAccount') $perfiles = @(Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue | Where-Object { ($noSonPerfiles -notcontains $_.Name) -and (Test-Path -LiteralPath (Join-Path $_.FullName 'NTUSER.DAT')) }) Write-Output ("perfiles del equipo: {0}" -f $perfiles.Count) foreach ($p in $perfiles) { Write-Output (" - " + $p.Name) } # --------------------------------------------------------------------------- # 1) REVERTIR Y RETIRAR, sin depender de la sesion del usuario # # La reversion TIENE que ser automatica: no se puede pedir al cliente que cada usuario inicie # sesion para que su equipo vuelva a la normalidad. # # - Los FICHEROS los restaura el propio arnes desde su manifiesto, con rutas absolutas, y # eso SYSTEM lo hace sin la sesion de nadie. # - El REGISTRO y el FONDO son de cada usuario. Si su colmena NO esta en uso se arregla # directamente cargandola (reg load). Si esta en uso (tiene sesion) se deja una tarea # interactiva, que es lo unico que puede tocar su HKCU vivo. # --------------------------------------------------------------------------- $lanzador = Join-Path $carpetaRT 'rt-launch.ps1' $reversionPendiente = $false if ((-not $SaltarRevertir) -and (Test-Path -LiteralPath $lanzador)) { # ---- 1) FICHEROS, como SYSTEM ---- Escribir '' Escribir '--- revertir los ficheros, como SYSTEM ---' 'Cyan' foreach ($accion in @('Vector2Revert', 'Retire')) { Escribir (" ejecutando {0}..." -f $accion) $salida = & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass ` -File $lanzador -Action $accion -Execute 2>&1 $codigo = $LASTEXITCODE if ($codigo -eq 0) { Escribir (" {0}: correcto (exit 0)" -f $accion) 'Green' } else { Escribir (" {0}: exit {1}" -f $accion, $codigo) 'Yellow' Escribir (" " + (($salida | Select-Object -Last 3) -join ' | ')) 'Yellow' } } # ---- 1.bis RED DE SEGURIDAD: quitar la extension si el arnes no restauro ---- # # El "cifrado" del Vector 2 es SOLO un cambio de nombre: el fichero X.rt26 ES el original # (lo dice el propio Invoke-Revert.ps1). No hay contenido que descifrar. # # El revert depende del manifiesto del Vector 2, y en las pruebas del 07/10 ese manifiesto # no aparece donde el revert lo busca: dice exit 0 y no restaura nada. Con esta red, la # limpieza cumple su funcion igual, y sin riesgo alguno para los datos. Escribir '' Escribir '--- red de seguridad: ficheros que sigan con la extension del ejercicio ---' 'Cyan' $extension = '.rt26' $recuperados = 0 $subcarpetas = @('Desktop', 'Documents', 'Documentos', 'Downloads', 'Descargas') foreach ($p in $perfiles) { foreach ($sub in $subcarpetas) { $d = Join-Path $p.FullName $sub if (-not (Test-Path -LiteralPath $d)) { continue } foreach ($f in @(Get-ChildItem -LiteralPath $d -File -Filter "*$extension" -ErrorAction SilentlyContinue)) { $nuevo = $f.Name -replace ([regex]::Escape($extension) + '$'), '' if ([string]::IsNullOrWhiteSpace($nuevo)) { continue } try { Rename-Item -LiteralPath $f.FullName -NewName $nuevo -Force -ErrorAction Stop Escribir (" restaurado: {0}" -f $nuevo) 'Green' $recuperados++ } catch { Escribir (" no se pudo renombrar {0}: {1}" -f $f.Name, $_.Exception.Message) 'Yellow' } } # Y la nota del rescate, que el arnes deja en el Escritorio y en Documentos foreach ($nota in @('LEEME_RANSOM.txt', 'LEEME_RANSOM', 'LEEME_RANSOM.html')) { $f = Join-Path $d $nota if (Test-Path -LiteralPath $f) { Remove-Item -LiteralPath $f -Force -ErrorAction SilentlyContinue Escribir (" nota eliminada: {0}" -f $f) 'Green' } } } } Escribir (" ficheros recuperados por nombre: {0}" -f $recuperados) if ($recuperados -gt 0) { Escribir ' (el contenido es el original: el cifrado del ejercicio solo renombra)' 'Cyan' } # Y AHORA la retirada de senuelos, que antes no podia funcionar. # # El Retire busca los senuelos por su NOMBRE ORIGINAL, y en el bloque 1 todavia se llamaban # X.rt26: decia "missing" de los ocho y dejaba retirados=0. Al restaurar los nombres aqui, # el manifiesto de plantado ya los reconoce y se borran de verdad. if (($recuperados -gt 0) -and (Test-Path -LiteralPath $lanzador)) { Escribir '' Escribir '--- retirada de senuelos, ahora que tienen su nombre original ---' 'Cyan' $salidaRet = & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $lanzador -Action Retire -Execute 2>&1 $codigoRet = $LASTEXITCODE Escribir (" retirada de senuelos: exit {0}" -f $codigoRet) 'Green' # Comprobacion: los senuelos del manifiesto de plantado no deben seguir en el perfil $quedanSenuelos = 0 $dirManifiestos = Join-Path $carpetaRT26 'v1\manifests' if (Test-Path -LiteralPath $dirManifiestos) { foreach ($m in @(Get-ChildItem -LiteralPath $dirManifiestos -Filter 'plant-*.jsonl' -ErrorAction SilentlyContinue)) { foreach ($linea in (Get-Content -LiteralPath $m.FullName -ErrorAction SilentlyContinue)) { if (-not $linea.Trim()) { continue } try { $e = $linea | ConvertFrom-Json if ($e.planted_path -and (Test-Path -LiteralPath ([string]$e.planted_path))) { $quedanSenuelos++ } } catch { } } } } Escribir (" senuelos que siguen en el perfil: {0}" -f $quedanSenuelos) } # ---- 2) REGISTRO Y FONDO, por perfil ---- Escribir '' Escribir '--- registro y fondo, perfil por perfil ---' 'Cyan' # El fondo original que capturo el arnes antes del ejercicio $fondoRestore = $null foreach ($cand in @( (Join-Path $carpetaRT26 'wallpaper_restore.jpg'), (Join-Path $carpetaRT 'harness\assets\wallpaper_restore.jpg'), (Join-Path $carpetaRT 'stage\harness\assets\wallpaper_restore.jpg') )) { if (Test-Path -LiteralPath $cand) { $fondoRestore = $cand; break } } if ($fondoRestore) { Escribir (" fondo original: {0}" -f $fondoRestore) } # La imagen se copia FUERA de RT26, porque la limpieza borra esa carpeta al final. Y se # deja un script que la aplica con SystemParametersInfo: es lo unico que cambia el fondo # al momento, y hay que ejecutarlo desde la sesion del usuario. # En C:\ProgramData, NO en C:\Windows\Temp: la limpieza borra los rt-* de Temp, y si la # imagen desaparece antes de que la tarea la aplique, SystemParametersInfo no da error y # el escritorio se queda NEGRO (medido). Aqui sobrevive hasta el final de la limpieza. $carpetaFondo = 'C:\ProgramData\RT-fondo' if (-not (Test-Path -LiteralPath $carpetaFondo)) { New-Item -ItemType Directory -Path $carpetaFondo -Force | Out-Null } $imagenFondo = Join-Path $carpetaFondo 'wallpaper_restore.jpg' $scriptFondo = Join-Path $carpetaFondo 'rt-wallpaper.ps1' if ($fondoRestore) { Copy-Item -LiteralPath $fondoRestore -Destination $imagenFondo -Force -ErrorAction SilentlyContinue if (Test-Path -LiteralPath $imagenFondo) { $cuerpo = @' param([string]$Imagen) $firma = @" using System; using System.Runtime.InteropServices; public class RTFondo { [DllImport("user32.dll", CharSet = CharSet.Auto)] public static extern int SystemParametersInfo(int accion, int param, string valor, int flags); } "@ try { Add-Type -TypeDefinition $firma -ErrorAction Stop [void][RTFondo]::SystemParametersInfo(20, 0, $Imagen, 3) "fondo aplicado: $Imagen" } catch { "no se pudo aplicar el fondo: $($_.Exception.Message)" } '@ Set-Content -LiteralPath $scriptFondo -Value $cuerpo -Encoding UTF8 Escribir (" imagen del fondo guardada en {0}" -f $imagenFondo) 'Green' } } foreach ($p in $perfiles) { $hive = Join-Path $p.FullName 'NTUSER.DAT' $tempName = "RT_TMP_" + ($p.Name -replace '[^A-Za-z0-9]', '') $claveTemp = "HKU\$tempName" # Sin sesion abierta, la colmena se puede cargar $carga = & reg.exe load $claveTemp $hive 2>&1 if ($LASTEXITCODE -ne 0) { # Tiene sesion: su HKCU esta vivo y solo lo puede tocar una tarea suya Escribir (" {0}: colmena en uso (tiene sesion) -> tarea interactiva" -f $p.Name) 'Yellow' $reversionPendiente = $true try { $ajustes = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries # El fondo primero: es lo que ve el cliente, y el lanzador aborta si no hay # manifiesto, asi que por esa via nunca se aplicaba. if ((Test-Path -LiteralPath $scriptFondo) -and (Test-Path -LiteralPath $imagenFondo)) { $arg = ('-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -Imagen "{1}"' -f $scriptFondo, $imagenFondo) $nombreTarea = ("RT-2026 Fondo ({0})" -f $p.Name) } else { $arg = ('-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File "{0}" -Action Vector2Revert -Execute' -f $lanzador) $nombreTarea = ("RT-2026 Deshacer ({0})" -f $p.Name) } $acc = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arg $tri = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) $pri = New-ScheduledTaskPrincipal -UserId ("{0}\{1}" -f $env:COMPUTERNAME, $p.Name) -LogonType Interactive -RunLevel Limited Register-ScheduledTask -TaskName $nombreTarea -Action $acc -Trigger $tri -Settings $ajustes -Principal $pri -Force | Out-Null Start-ScheduledTask -TaskName $nombreTarea -ErrorAction Stop Escribir (" tarea lanzada para {0}: {1}" -f $p.Name, $nombreTarea) 'Green' # Y hay que darle tiempo: la limpieza borra las tareas al final, y con el # Stop-ScheduledTask que se hace antes de borrar, esta moriria sin haber # aplicado el fondo. Medido: sin esta espera, el fondo se quedaba en el de # ataque aunque la tarea se lanzara correctamente. Escribir ' esperando a que aplique el fondo...' Start-Sleep -Seconds 12 } catch { Escribir (" no se pudo lanzar para {0}: {1}" -f $p.Name, $_.Exception.Message) 'Yellow' } continue } try { foreach ($sub in @('Software\RT26', 'FuisteAtacadoPorUnRansomware')) { $ruta = "Registry::$claveTemp\$sub" if (Test-Path -LiteralPath $ruta) { Remove-Item -LiteralPath $ruta -Recurse -Force -ErrorAction SilentlyContinue Escribir (" {0}: borrada la clave {1}" -f $p.Name, $sub) 'Green' } } if ($fondoRestore) { $rutaEscritorio = "Registry::$claveTemp\Control Panel\Desktop" if (Test-Path -LiteralPath $rutaEscritorio) { Set-ItemProperty -LiteralPath $rutaEscritorio -Name 'WallPaper' -Value $fondoRestore -Force -ErrorAction SilentlyContinue Escribir (" {0}: fondo original restaurado (se vera al iniciar sesion)" -f $p.Name) 'Green' } } } catch { Escribir (" {0}: aviso al tocar el registro: {1}" -f $p.Name, $_.Exception.Message) 'Yellow' } finally { $null = & reg.exe unload $claveTemp 2>&1 } } } # --------------------------------------------------------------------------- # 2) COMPROBACION PREVIA (despues de intentar revertir): no se borran respaldos con material sin revertir # --------------------------------------------------------------------------- $pendientes = @() # a) Ficheros cifrados que sigan con la extension del ejercicio foreach ($p in $perfiles) { foreach ($sub in @('Desktop', 'Documents', 'Documentos', 'Downloads', 'Descargas')) { $d = Join-Path $p.FullName $sub if (Test-Path -LiteralPath $d) { $f = @(Get-ChildItem -LiteralPath $d -Recurse -File -Filter '*.rt26' -ErrorAction SilentlyContinue) foreach ($x in $f) { $pendientes += ("cifrado sin revertir {0}" -f $x.FullName) } } } } # b) SeƱuelos que sigan plantados, segun el manifiesto del propio arnes $dirManifiestos = Join-Path $carpetaRT26 'v1\manifests' if (Test-Path -LiteralPath $dirManifiestos) { foreach ($m in @(Get-ChildItem -LiteralPath $dirManifiestos -Filter 'canaries-*.jsonl' -ErrorAction SilentlyContinue)) { foreach ($linea in (Get-Content -LiteralPath $m.FullName -ErrorAction SilentlyContinue)) { if (-not $linea.Trim()) { continue } try { $e = $linea | ConvertFrom-Json $ruta = [string]$e.planted_path if ($ruta -and (Test-Path -LiteralPath $ruta)) { $pendientes += ("senuelo sin retirar {0}" -f $ruta) } } catch { } } } } if ($pendientes.Count -gt 0 -and -not $ForzarSinComprobar) { Escribir ("HAY {0} ELEMENTO(S) SIN DESHACER. NO SE BORRA NADA." -f $pendientes.Count) 'Red' Escribir '' $pendientes | Select-Object -First 20 | ForEach-Object { Escribir (" " + $_) 'Yellow' } if ($pendientes.Count -gt 20) { Escribir (" ... y {0} mas" -f ($pendientes.Count - 20)) 'Yellow' } Escribir '' Escribir 'Ejecuta antes, en la sesion de cada usuario afectado:' 'Cyan' Escribir ' -Action Vector2Revert -Execute (devuelve el nombre y el fondo)' 'Cyan' Escribir ' -Action Retire -Execute (retira los senuelos)' 'Cyan' Escribir '' Escribir 'Y solo despues, vuelve a lanzar esta limpieza.' 'Cyan' Escribir 'Si de verdad hay que borrar sin comprobar: -ForzarSinComprobar (los ficheros cifrados perderan su nombre)' 'Red' Stop-Transcript -ErrorAction SilentlyContinue | Out-Null exit 2 } if ($ForzarSinComprobar -and $pendientes.Count -gt 0) { Escribir ("AVISO: se continua con {0} elemento(s) sin deshacer, por -ForzarSinComprobar" -f $pendientes.Count) 'Red' Escribir '' } # --------------------------------------------------------------------------- # 2) La limpieza # --------------------------------------------------------------------------- $borrados = 0 $fallos = 0 # 2.1 Tareas programadas # # OJO con el orden: una tarea que se borra MIENTRAS se esta ejecutando sigue corriendo, y si # su ejecucion registra tareas al final, las deja puestas despues del borrado. Medido en la # VM: el registro decia "borrada" de cuatro y seguian existiendo diez. # # Por eso: primero se paran, despues se borran, y se repite la pasada hasta que no quede # ninguna. Con una espera entre pasadas, para dar tiempo a que terminen las que corrian. Escribir '--- tareas programadas ---' 'Cyan' # Cual de estas tareas es la que esta ejecutando este script. Se reconoce porque su accion # menciona rt-uninstall. Esa no se puede parar (nos matariamos) ni borrar todavia; se # desregistra al final del todo. $propiaTarea = $null foreach ($t in @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'RT-2026*' })) { foreach ($a in @($t.Actions)) { if ($a.Arguments -and ($a.Arguments -match 'rt-uninstall')) { $propiaTarea = $t.TaskName; break } } if ($propiaTarea) { break } } if ($propiaTarea) { Escribir (" (esta limpieza corre dentro de '{0}': no se toca hasta el final)" -f $propiaTarea) 'Cyan' } for ($pasada = 1; $pasada -le 3; $pasada++) { $tareas = @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { ($_.TaskName -like 'RT-2026*') -and ($_.TaskName -ne $propiaTarea) }) if ($tareas.Count -eq 0) { break } Escribir (" pasada {0}: {1} tarea(s)" -f $pasada, $tareas.Count) # Con schtasks, no con los cmdlets: Unregister/Stop-ScheduledTask construyen una consulta # WQL con LIKE, y los corchetes de los nombres antiguos son una clase de caracteres alli, # asi que no encuentran la tarea y el borrado falla sin decir por que. schtasks usa el # nombre literal y limpia tambien esas. foreach ($t in $tareas) { $null = schtasks.exe /End /TN $t.TaskName 2>&1 } Start-Sleep -Seconds 3 foreach ($t in $tareas) { $salida = schtasks.exe /Delete /TN $t.TaskName /F 2>&1 if ($LASTEXITCODE -eq 0) { Escribir (" borrada: {0}" -f $t.TaskName) 'Green' $borrados++ } else { Escribir (" FALLO al borrar {0}: {1}" -f $t.TaskName, ($salida -join ' ')) 'Red' $fallos++ } } Start-Sleep -Seconds 2 } $quedanTareas = @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { ($_.TaskName -like 'RT-2026*') -and ($_.TaskName -ne $propiaTarea) }) if ($quedanTareas.Count -gt 0) { Escribir (" AVISO: siguen existiendo {0} tarea(s):" -f $quedanTareas.Count) 'Yellow' $quedanTareas | ForEach-Object { Escribir (" " + $_.TaskName) 'Yellow' } Escribir ' Suele ser porque su tarea padre seguia corriendo y las ha vuelto a registrar.' 'Yellow' Escribir ' Vuelve a lanzar esta limpieza: la segunda vez ya no hay nada corriendo.' 'Yellow' } else { Escribir ' ninguna tarea RT-2026' 'Green' } # 2.2 Carpetas de trabajo Escribir '' Escribir '--- carpetas ---' 'Cyan' foreach ($c in @($carpetaRT, $carpetaRT26)) { if (-not (Test-Path -LiteralPath $c)) { Escribir (" no existia: {0}" -f $c) continue } try { Remove-Item -LiteralPath $c -Recurse -Force -ErrorAction Stop Escribir (" borrada: {0}" -f $c) 'Green' $borrados++ } catch { Escribir (" FALLO al borrar {0}: {1}" -f $c, $_.Exception.Message) 'Red' $fallos++ } } # 2.3 Registros por usuario Escribir '' Escribir '--- registros de los usuarios ---' 'Cyan' foreach ($p in $perfiles) { $d = Join-Path $p.FullName 'AppData\Local\RT-launcher' if (Test-Path -LiteralPath $d) { try { Remove-Item -LiteralPath $d -Recurse -Force -ErrorAction Stop Escribir (" borrado: {0}" -f $d) 'Green' $borrados++ } catch { Escribir (" FALLO al borrar {0}: {1}" -f $d, $_.Exception.Message) 'Red' $fallos++ } } } if ($perfiles.Count -eq 0) { Escribir ' no se encontraron perfiles' } # 2.4 El script descargado Escribir '' Escribir '--- ficheros temporales ---' 'Cyan' foreach ($f in @('C:\Windows\Temp\rt-setup.ps1', 'C:\Windows\Temp\rt-bootstrap.ps1')) { if (Test-Path -LiteralPath $f) { try { Remove-Item -LiteralPath $f -Force -ErrorAction Stop Escribir (" borrado: {0}" -f $f) 'Green' $borrados++ } catch { Escribir (" FALLO al borrar {0}: {1}" -f $f, $_.Exception.Message) 'Red' $fallos++ } } } # --------------------------------------------------------------------------- # 3) Resumen y comprobacion final # --------------------------------------------------------------------------- Escribir '' Escribir '--- estado final ---' 'Cyan' Escribir (" elementos borrados : {0}" -f $borrados) Escribir (" fallos : {0}" -f $fallos) Escribir '' Escribir (" C:\ProgramData\RT existe: {0} (False)" -f (Test-Path -LiteralPath $carpetaRT)) Escribir (" C:\ProgramData\RT26 existe: {0} (False)" -f (Test-Path -LiteralPath $carpetaRT26)) Escribir (" tareas RT-2026 : {0} (0)" -f @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'RT-2026*' }).Count) Escribir '' Escribir 'OJO: lo que NO toca esta limpieza, a proposito:' 'Yellow' Escribir ' - El registro HKCU:\Software\RT26 y el fondo de cada usuario: los deshace Vector2Revert' 'Yellow' Escribir ' en la sesion de ese usuario, porque son suyos.' 'Yellow' Escribir ' - El manifiesto de la maquina no se puede deshacer desde aqui si ya se borro RT26.' 'Yellow' Escribir '' # Y ahora si: la tarea que nos ejecuto se desregistra al final, ya sin nada mas que hacer. # Desregistrar no mata el proceso (solo pararlo lo hace), asi que esto es seguro. if ($propiaTarea) { Escribir '' Escribir '--- la tarea que ha ejecutado esta limpieza ---' 'Cyan' try { $salida = schtasks.exe /Delete /TN $propiaTarea /F 2>&1 if ($LASTEXITCODE -eq 0) { Escribir (" desregistrada: {0}" -f $propiaTarea) 'Green' $borrados++ } else { Escribir (" no se pudo desregistrar {0}: {1}" -f $propiaTarea, ($salida -join ' ')) 'Yellow' } } catch { Escribir (" no se pudo desregistrar {0}: {1}" -f $propiaTarea, $_.Exception.Message) 'Yellow' } } Escribir '' Escribir (" tareas RT-2026 que quedan: {0} (0)" -f @(Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $_.TaskName -like 'RT-2026*' }).Count) Escribir 'Equipo como antes de la preparacion.' 'Cyan' Stop-Transcript -ErrorAction SilentlyContinue | Out-Null exit $(if ($fallos -gt 0) { 1 } else { 0 })