Proyectos · PowerShell · Básico

Copia de seguridad automática

Un script que comprime una carpeta en un ZIP con la fecha, guarda solo las últimas copias, apunta lo que hace en un registro y se puede programar para que se ejecute solo.

  • 5 pasos
  • 45 minutos aproximadamente
  • 40 líneas de código

Lo que vas a construir

Perder un trabajo por no tener copia es de los disgustos más evitables. En este proyecto escribes un script de PowerShell que copia una carpeta, la comprime con la fecha en el nombre, borra las copias viejas y deja constancia de todo. Al final lo programas para que Windows lo ejecute solo cada día.

Lo que vas a practicar

  • Variables y rutas con Join-Path
  • Crear carpetas si no existen
  • Comprimir con Compress-Archive
  • Ordenar y filtrar archivos con la tubería
  • Parámetros, try/catch y un registro

Cómo ejecutarlo: Guarda el código como copia.ps1 y ejecútalo en PowerShell con .\copia.ps1. Si Windows no te deja ejecutar scripts, permite los tuyos con Set-ExecutionPolicy -Scope CurrentUser RemoteSigned.

Así funciona al final
2026-05-10 21:30:04  Copia hecha: C:\Users\Lucia\Copias\Documentos_2026-05-10_21-30.zip (18.60 MB)
2026-05-10 21:30:04  Borrada la copia antigua Documentos_2026-05-04_21-30.zip

Antes de empezar, conviene haber hecho estas lecciones

Paso 1 de 5

Copiar una carpeta

Empieza por lo más simple: dos variables con la carpeta de origen y la de destino, y Copy-Item con -Recurse para copiar también las subcarpetas. $HOME es tu carpeta de usuario (por ejemplo, C:\Users\Lucia).

Funciona, pero cada vez sobrescribe la copia anterior: si un archivo se estropea, la copia también se estropea en la siguiente ejecución.

copia.ps1
$origen = "$HOME\Documents"
$destino = "$HOME\Copias"

Copy-Item -Path $origen -Destination $destino -Recurse -Force
Write-Output "Copia hecha en $destino"
Al ejecutarlo
Copia hecha en C:\Users\Lucia\Copias

Paso 2 de 5

Una copia por fecha

Get-Date -Format "yyyy-MM-dd_HH-mm" da la fecha y la hora en un formato que se ordena bien alfabéticamente. Con Join-Path se construye la ruta de la nueva carpeta sin preocuparse por las barras.

Test-Path comprueba si la carpeta de copias existe y, si no, New-Item la crea. | Out-Null evita que se muestre el objeto creado.

copia.ps1Lo nuevo, resaltado
$origen = "$HOME\Documents"
$carpetaCopias = "$HOME\Copias"

# Una carpeta por copia, con la fecha y la hora en el nombre
$fecha = Get-Date -Format "yyyy-MM-dd_HH-mm"
$destino = Join-Path $carpetaCopias "Documentos_$fecha"

if (-not (Test-Path $carpetaCopias)) {
    New-Item -ItemType Directory -Path $carpetaCopias | Out-Null
}

Copy-Item -Path $origen -Destination $destino -Recurse
Write-Output "Copia hecha en $destino"
Al ejecutarlo
Copia hecha en C:\Users\Lucia\Copias\Documentos_2026-05-04_21-30

Paso 3 de 5

Comprimir en un ZIP

En vez de copiar la carpeta, Compress-Archive crea un único archivo ZIP: ocupa menos y es más fácil de llevar a un disco externo o a la nube. "$origen\*" comprime el contenido de la carpeta.

(Get-Item $zip).Length es el tamaño en bytes; dividido entre 1MB y redondeado con [math]::Round, se muestra en megas.

copia.ps1Lo nuevo, resaltado
$origen = "$HOME\Documents"
$carpetaCopias = "$HOME\Copias"

$fecha = Get-Date -Format "yyyy-MM-dd_HH-mm"
$zip = Join-Path $carpetaCopias "Documentos_$fecha.zip"

if (-not (Test-Path $carpetaCopias)) {
    New-Item -ItemType Directory -Path $carpetaCopias | Out-Null
}

# Comprimir en un ZIP ocupa menos y es un solo archivo fácil de mover
Compress-Archive -Path "$origen\*" -DestinationPath $zip
$tamano = [math]::Round((Get-Item $zip).Length / 1MB, 2)
Write-Output "Copia hecha: $zip ($tamano MB)"
Al ejecutarlo
Copia hecha: C:\Users\Lucia\Copias\Documentos_2026-05-04_21-30.zip (18.42 MB)

Paso 4 de 5

Guardar solo las últimas copias

Si se hace una copia al día, el disco se acaba llenando. La tubería resuelve el problema en tres pasos: lista los ZIP, los ordena del más nuevo al más antiguo y, con Select-Object -Skip 5, se queda con todos menos los 5 primeros.

Esos son los que sobran: un foreach los borra uno a uno con Remove-Item y avisa de cada uno.

copia.ps1Lo nuevo, resaltado
$origen = "$HOME\Documents"
$carpetaCopias = "$HOME\Copias"
$copiasAGuardar = 5

$fecha = Get-Date -Format "yyyy-MM-dd_HH-mm"
$zip = Join-Path $carpetaCopias "Documentos_$fecha.zip"

if (-not (Test-Path $carpetaCopias)) {
    New-Item -ItemType Directory -Path $carpetaCopias | Out-Null
}

Compress-Archive -Path "$origen\*" -DestinationPath $zip
$tamano = [math]::Round((Get-Item $zip).Length / 1MB, 2)
Write-Output "Copia hecha: $zip ($tamano MB)"

# Borrar las copias antiguas y quedarse solo con las más recientes
$antiguas = Get-ChildItem $carpetaCopias -Filter "Documentos_*.zip" |
    Sort-Object LastWriteTime -Descending |
    Select-Object -Skip $copiasAGuardar

foreach ($copia in $antiguas) {
    Remove-Item $copia.FullName
    Write-Output "Borrada la copia antigua $($copia.Name)"
}
Al ejecutarlo
Copia hecha: C:\Users\Lucia\Copias\Documentos_2026-05-09_21-30.zip (18.57 MB)
Borrada la copia antigua Documentos_2026-05-03_21-30.zip

Paso 5 de 5

Parámetros, errores y registro

Con param() el script acepta opciones al ejecutarlo (.\copia.ps1 -Origen D:\Fotos -CopiasAGuardar 10) y mantiene valores por defecto. La función Escribir-Registro muestra cada mensaje y lo añade con la hora a registro.txt.

El try/catch captura cualquier fallo (una carpeta que no existe, un disco lleno), lo apunta en el registro y termina con exit 1 para que se note que algo fue mal. Para que se ejecute solo cada día, crea una tarea en el Programador de tareas de Windows que lance powershell.exe -File C:\ruta\copia.ps1.

copia.ps1Lo nuevo, resaltado
param(
    [string]$Origen = "$HOME\Documents",
    [string]$CarpetaCopias = "$HOME\Copias",
    [int]$CopiasAGuardar = 5
)

$registro = Join-Path $CarpetaCopias "registro.txt"

function Escribir-Registro([string]$mensaje) {
    $linea = "$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')  $mensaje"
    Write-Output $linea
    Add-Content -Path $registro -Value $linea
}

try {
    if (-not (Test-Path $Origen)) {
        throw "No existe la carpeta de origen: $Origen"
    }
    if (-not (Test-Path $CarpetaCopias)) {
        New-Item -ItemType Directory -Path $CarpetaCopias | Out-Null
    }

    $fecha = Get-Date -Format "yyyy-MM-dd_HH-mm"
    $zip = Join-Path $CarpetaCopias "Documentos_$fecha.zip"
    Compress-Archive -Path "$Origen\*" -DestinationPath $zip -ErrorAction Stop
    $tamano = [math]::Round((Get-Item $zip).Length / 1MB, 2)
    Escribir-Registro "Copia hecha: $zip ($tamano MB)"

    $antiguas = Get-ChildItem $CarpetaCopias -Filter "Documentos_*.zip" |
        Sort-Object LastWriteTime -Descending |
        Select-Object -Skip $CopiasAGuardar
    foreach ($copia in $antiguas) {
        Remove-Item $copia.FullName
        Escribir-Registro "Borrada la copia antigua $($copia.Name)"
    }
}
catch {
    Escribir-Registro "ERROR: $($_.Exception.Message)"
    exit 1
}
Al ejecutarlo
2026-05-10 21:30:04  Copia hecha: C:\Users\Lucia\Copias\Documentos_2026-05-10_21-30.zip (18.60 MB)
2026-05-10 21:30:04  Borrada la copia antigua Documentos_2026-05-04_21-30.zip

Retos para seguir

Has terminado el proyecto. Si quieres ir más allá, prueba a añadirle esto por tu cuenta:

  1. Añade un parámetro -Excluir para no copiar los archivos de ciertas extensiones, como .tmp.
  2. Haz que la copia se guarde también en un disco externo si está conectado.
  3. Envía un aviso de Windows (o un correo) cuando la copia falle.

Conceptos clave

Repasa la ruta de PowerShell o elige otro proyecto.