<# Respaldo de carpetas con Robocopy Herramientas BAIT · https://bait.cl/herramientas/script-respaldo/ Qué hace: Copia las carpetas de origen a una carpeta de destino (disco externo, otro disco, unidad de red o una carpeta sincronizada con OneDrive). Cada origen queda en su propia subcarpeta. Solo copia lo nuevo o modificado, así que después del primer respaldo es rápido. Guarda un registro (log) con fecha en \_registros. Modo normal (por defecto): agrega y actualiza archivos. Nunca borra nada del destino. Modo espejo (-Espejo): deja el destino idéntico al origen, y eso incluye BORRAR del destino lo que ya no exista en el origen. Si borras algo por error o un virus cifra tus archivos, el espejo copia ese daño al respaldo. Por eso no viene activado y pide confirmación. Uso: 1. Edita las carpetas de origen y destino en la sección CONFIGURACIÓN de abajo, o pásalas como parámetros. 2. Clic derecho > Ejecutar con PowerShell o bien: powershell -ExecutionPolicy Bypass -File .\respaldo-carpetas.ps1 Ejemplos: .\respaldo-carpetas.ps1 -Origen "C:\Users\Ana\Documents;C:\Contabilidad" -Destino "E:\Respaldo" .\respaldo-carpetas.ps1 -Programar -Hora 13:30 (crea una tarea programada diaria a las 13:30 que ejecuta este mismo archivo) No necesita administrador, salvo que copies carpetas de otros usuarios o del sistema. #> param( # ===================== CONFIGURACIÓN ===================== # Carpetas a respaldar. Separa varias con punto y coma. [string[]]$Origen = @( [Environment]::GetFolderPath('MyDocuments'), [Environment]::GetFolderPath('Desktop') ), # Dónde se guarda el respaldo. Ejemplos: E:\Respaldo \\SERVIDOR\Respaldos\Ana [string]$Destino = 'D:\Respaldo', # Deja el destino idéntico al origen (borra en el destino). Lee la advertencia de arriba. [switch]$Espejo, # Crea una tarea programada diaria que ejecuta este respaldo. [switch]$Programar, # Hora de la tarea programada (formato 24 horas). [string]$Hora = '13:00', # Sin preguntas ni pausa final (lo usa la tarea programada). [switch]$SinPreguntas # ========================================================== ) $Host.UI.RawUI.WindowTitle = 'Respaldo de carpetas (BAIT)' function Confirmar([string]$Texto) { if ($SinPreguntas) { return $true } return ((Read-Host "$Texto (S/N)") -match '^[sS]') } function Terminar([int]$Codigo = 0) { if (-not $SinPreguntas) { Read-Host 'Presiona Enter para cerrar' | Out-Null } exit $Codigo } # Permite pasar varias carpetas en un solo texto separado por punto y coma $carpetas = @($Origen | ForEach-Object { $_ -split ';' } | ForEach-Object { $_.Trim().TrimEnd('\') } | Where-Object { $_ }) Write-Host '' Write-Host '=== Respaldo de carpetas ===' -ForegroundColor Cyan Write-Host 'Origen:' $carpetas | ForEach-Object { Write-Host " $_" } Write-Host "Destino: $Destino" if ($Espejo) { Write-Host 'Modo: ESPEJO (borra en el destino lo que no está en el origen)' -ForegroundColor Yellow } else { Write-Host 'Modo: copia (no borra nada del destino)' } Write-Host '' # ---------- programar tarea diaria ---------- if ($Programar) { if (-not $PSCommandPath) { Write-Host 'Guarda el script en un archivo .ps1 para poder programarlo.' -ForegroundColor Yellow; Terminar 1 } try { $horaTarea = [datetime]::ParseExact($Hora, 'H:mm', $null) } catch { Write-Host "La hora '$Hora' no es válida. Usa el formato 24 horas, por ejemplo 13:30." -ForegroundColor Red; Terminar 1 } $nombreTarea = 'BAIT Respaldo de carpetas' if ($Espejo) { Write-Host 'Atención: la tarea programada usará el modo ESPEJO todos los días sin preguntar.' -ForegroundColor Yellow if (-not (Confirmar '¿Seguro que quieres programar el respaldo en modo espejo?')) { Terminar 0 } } # Argumentos con los que la tarea ejecutará este mismo archivo $argumentos = '-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}" -SinPreguntas -Origen "{1}" -Destino "{2}"' -f $PSCommandPath, ($carpetas -join ';'), $Destino if ($Espejo) { $argumentos += ' -Espejo' } $accion = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $argumentos $disparador = New-ScheduledTaskTrigger -Daily -At $horaTarea # StartWhenAvailable: si el equipo estaba apagado a esa hora, corre apenas se pueda $ajustes = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Hours 6) # Corre con tu usuario y solo cuando tienes la sesión iniciada (no pide contraseña) $principal = New-ScheduledTaskPrincipal -UserId ("{0}\{1}" -f $env:USERDOMAIN, $env:USERNAME) -LogonType Interactive if (Get-ScheduledTask -TaskName $nombreTarea -ErrorAction SilentlyContinue) { if (-not (Confirmar "Ya existe la tarea '$nombreTarea'. ¿Reemplazarla?")) { Terminar 0 } } try { Register-ScheduledTask -TaskName $nombreTarea -Action $accion -Trigger $disparador -Settings $ajustes -Principal $principal -Description 'Respaldo diario de carpetas creado con Herramientas BAIT.' -Force -ErrorAction Stop | Out-Null Write-Host "Tarea '$nombreTarea' creada: todos los días a las $($horaTarea.ToString('HH:mm'))." -ForegroundColor Green Write-Host "Importante: no muevas ni borres este archivo ($PSCommandPath), la tarea lo usa." Write-Host "Para quitarla: Unregister-ScheduledTask -TaskName '$nombreTarea' -Confirm:`$false" } catch { Write-Host "No se pudo crear la tarea: $($_.Exception.Message)" -ForegroundColor Red } if (-not (Confirmar '¿Quieres hacer un respaldo ahora también?')) { Terminar 0 } } # ---------- validaciones ---------- if (-not $carpetas) { Write-Host 'No hay carpetas de origen.' -ForegroundColor Red; Terminar 1 } $raizDestino = [System.IO.Path]::GetPathRoot($Destino) if ($raizDestino -and -not (Test-Path -LiteralPath $raizDestino)) { Write-Host "La unidad $raizDestino no está disponible. ¿Conectaste el disco o la unidad de red?" -ForegroundColor Red Terminar 1 } # Advertencia y confirmación del modo espejo if ($Espejo -and -not $SinPreguntas) { Write-Host '' Write-Host 'MODO ESPEJO: todo lo que esté en el destino y no exista en el origen se BORRARÁ.' -ForegroundColor Yellow Write-Host 'Si el origen tiene archivos dañados o borrados por error, el respaldo quedará igual.' -ForegroundColor Yellow $r = Read-Host 'Escribe ESPEJO para continuar' if ($r -cne 'ESPEJO') { Write-Host 'Cancelado. No se copió nada.'; Terminar 0 } } $carpetaLogs = Join-Path $Destino '_registros' New-Item -ItemType Directory -Path $carpetaLogs -Force | Out-Null $log = Join-Path $carpetaLogs ("respaldo-{0}.log" -f (Get-Date -Format 'yyyy-MM-dd_HHmm')) # ---------- copia ---------- $errores = 0 foreach ($o in $carpetas) { if (-not (Test-Path -LiteralPath $o)) { Write-Host "No existe: $o (se omite)" -ForegroundColor Yellow Add-Content -Path $log -Value "NO EXISTE: $o" $errores++ continue } # Cada origen va a su propia subcarpeta: D:\Respaldo\Documents, D:\Respaldo\Desktop, etc. $sub = Split-Path -Path $o -Leaf if (-not $sub) { $sub = $o.Substring(0, 1) } # si el origen es una unidad completa (E:\) $dest = Join-Path $Destino $sub Write-Host '' Write-Host "Copiando $o" -ForegroundColor Cyan Write-Host " a $dest" # /E copia subcarpetas, /MIR hace espejo (solo si lo pediste) # /COPY:DAT datos, atributos y fechas /DCOPY:T fechas de carpetas # /R:2 /W:5 reintenta 2 veces esperando 5 s /XJ no sigue enlaces (evita bucles) # /MT:8 copia con 8 hilos /NP sin porcentaje /TEE muestra y guarda en el log $modo = '/E' if ($Espejo) { $modo = '/MIR' } $opciones = @($o, $dest, $modo, '/COPY:DAT', '/DCOPY:T', '/R:2', '/W:5', '/XJ', '/MT:8', '/NP', '/TEE', "/LOG+:$log", '/XF', 'desktop.ini', 'thumbs.db', '~$*', '/XD', '$RECYCLE.BIN', 'System Volume Information') & robocopy.exe @opciones | Out-Host # Robocopy: 0 a 7 es éxito (con distintos detalles), 8 o más significa que algo falló $codigo = $LASTEXITCODE if ($codigo -ge 8) { Write-Host "Hubo errores al copiar $o (código $codigo). Revisa el registro." -ForegroundColor Red $errores++ } else { Write-Host "Listo: $o" -ForegroundColor Green } } Write-Host '' if ($errores -eq 0) { Write-Host 'Respaldo terminado sin errores.' -ForegroundColor Green } else { Write-Host "Respaldo terminado con $errores problema(s)." -ForegroundColor Yellow } Write-Host "Registro: $log" Write-Host 'Un respaldo que nunca se prueba no es un respaldo: abre la carpeta de destino de vez en cuando.' Terminar $errores