Búsqueda de sitios web

Creación de un sistema de copia de seguridad de PowerShell - Parte 4


Estamos casi al final de mi sistema de respaldo PowerShell. La última vez les mostré cómo manejo mis copias de seguridad incrementales diarias. Hoy pensé que debería retroceder y repasar cómo manejo las copias de seguridad completas semanales. Recuerde, sólo me preocupa hacer una copia de seguridad de unas cuantas carpetas críticas. Guardé esa lista en un archivo que puedo actualizar en cualquier momento si necesito comenzar a hacer una copia de seguridad de algo más.

El trabajo programado de PowerShell

Comencemos mirando el código que uso para configurar un trabajo programado de PowerShell que se ejecuta todos los viernes por la noche a las 10:00 p.m. Guardo el código en un archivo de script para poder recrear el trabajo si es necesario.

#create weekly full backups

$trigger = New-JobTrigger -At 10:00PM -DaysOfWeek Friday -WeeksInterval 1 -Weekly
$jobOpt = New-ScheduledJobOption -RunElevated -RequireNetwork -WakeToRun

$params = @{
 FilePath = "C:\scripts\WeeklyFullBackup.ps1"
 Name = "WeeklyFullBackup" 
 Trigger = $trigger 
 ScheduledJobOption = $jobOpt 
 MaxResultCount = 5 
 Credential = "$env:computername\jeff"
}
Register-ScheduledJob @params

Una cosa que quiero señalar es que en lugar de definir un bloque de script, estoy especificando una ruta de archivo. De esta manera puedo ajustar el script todo lo que quiera sin tener que recrear o modificar el trabajo programado. Por supuesto, como tengo esto en un archivo de script, no es tan difícil, pero no me gusta tener que tocar algo que funciona si no es necesario. Incluyo una credencial porque parte de mi tarea incluirá copiar los archivos de respaldo en mi dispositivo Synology NAS.

El script de control de respaldo

El script que invoca el trabajo programado es bastante sencillo. Tiene que procesar la lista de directorios y hacer una copia de seguridad de cada carpeta en un archivo RAR. Estoy usando WinRar para mi solución de archivo. Puede sustituir su propio código de copia de seguridad o de archivo. Tengo algunas variaciones que guardaré en artículos futuros. Luego, cada archivo comprimido se mueve a mi dispositivo NAS. Ahora que lo pienso, probablemente debería agregar algo de código para enviar también una copia a una de mis unidades en la nube como OneDrive.

Aquí está el guión completo.

#requires -version 5.1

[cmdletbinding(SupportsShouldProcess)]
Param()

If (Test-Path -Path c:\scripts\mybackupPaths.txt) {

    #filter out blanks and commented lines
    $paths = Get-Content c:\scripts\mybackupPaths.txt | Where-Object { $_ -match "(^[^#]\S*)" -and $_ -notmatch "^\s+$" }
   
    #import my custom module
    Import-Module C:\scripts\PSRAR\Dev-PSRar.psm1 -force
   
    $paths | ForEach-Object {

        if ($pscmdlet.ShouldProcess($_)) {
            Try {
                #invoke a control script using my custom module
                C:\scripts\RarBackup.ps1 -Path $_ -Verbose -ErrorAction Stop
                $ok = $True
            }
            Catch {
                $ok = $False
                Write-Warning $_.exception.message
            }
        }

        #clear corresponding incremental log files
        $name = ((Split-Path $_ -Leaf).replace(' ', ''))
        #specify the directory for the CSV log files
        $log = "D:\Backup\{0}-log.csv" -f $name

        if ($OK -AND (Test-Path $log) -AND ($pscmdlet.ShouldProcess($log, "Clear Log"))) {
            Remove-Item -path $log
        }
    }
}
else {
    Write-Warning "Failed to find c:\scripts\mybackupPaths.txt"
}

El script RarBackup.ps1 es el que realiza el archivo y la copia al NAS.

El script de respaldo

Este archivo invoca mis comandos RAR para crear el archivo y copiarlo al NAS. Tengo un código que usa Get-FileHash para validar que la copia del archivo se haya realizado correctamente.

#requires -version 5.1

[cmdletbinding(SupportsShouldProcess)]
Param(
    [Parameter(Mandatory)]
    [ValidateScript( { Test-Path $_ })]
    [string]$Path,
    [ValidateScript( { Test-Path $_ })]
    #my temporary work area with plenty of disk space
    [string]$TempPath = "D:\Temp",
    [ValidateSet("FULL", "INCREMENTAL")]
    [string]$Type = "FULL"
)

Write-Verbose "[$(Get-Date)] Starting $($myinvocation.MyCommand)"
if (-Not (Get-Module Dev-PSRar)) {
    Import-Module C:\scripts\PSRAR\Dev-PSRar.psm1 -force
}

#replace spaces in path names
$name = "{0}_{1}-{2}.rar" -f (Get-Date -format "yyyyMMdd"), (Split-Path -Path $Path -Leaf).replace(' ', ''), $Type
$target = Join-Path -Path $TempPath -ChildPath $name

#I have hard coded my NAS backup. Would be better as a parameter with a default value.
$nasPath = Join-Path -Path \\DS416\backup -ChildPath $name
Write-Verbose "[$(Get-Date)] Archiving $path to $target"

if ($pscmdlet.ShouldProcess($Path)) {
    #Create the RAR archive -you can use any archiving technique you want
    Add-RARContent -path $Path -Archive $target -CompressionLevel 5 -Comment "$Type backup of $(($Path).ToUpper()) from $env:Computername"

    Write-Verbose "[$(Get-Date)] Copying $target to $nasPath"
    Try {
        #copy the RAR file to the NAS for offline storage
        Copy-Item -Path $target -Destination $NASPath -ErrorAction Stop
    }
    Catch {
        Write-Warning "Failed to copy $target. $($_.exception.message)"
        Throw $_
    }
    #verify the file was copied successfully
    Write-Verbose "[$(Get-Date)] Validating file hash"

    $here = Get-FileHash $Target
    $there = Get-FileHash $nasPath
    if ($here.hash -eq $there.hash) {
        #delete the file if the hashes match
        Write-Verbose "[$(Get-Date)] Deleting $target"
        Remove-Item $target
    }
    else {
        Write-Warning "File hash difference detected."
        Throw "File hash difference detected"
    }
}

Write-Verbose "[$(Get-Date)] Ending $($myinvocation.MyCommand)"

#end of script file

Estructuralmente, podría haber puesto todo este código en un único script de PowerShell. Pero me gusta un enfoque más modular para poder reutilizar scripts o comandos. O si necesito revisar algo, no tengo que preocuparme por romper un guión monolítico.

Informes

El último paso que quiero compartir es informar. No me preocupo demasiado por comprobar los resultados de los trabajos programados. Todo lo que necesito hacer es revisar mi carpeta de respaldo para ver mis archivos comprimidos. Debido a que mis archivos de respaldo siguen una convención de nomenclatura estándar, puedo usar expresiones regulares para hacer coincidir el nombre del archivo. Utilizo este script de control para ver mis archivos de respaldo.

# requires -version 5.1

[cmdletbinding()]
Param(
    [Parameter(Position = 0, HelpMessage = "Enter the path with extension of the backup files.")]
    [ValidateNotNullOrEmpty()]
    [ValidateScript( { Test-Path $_ })]
    [string]$Path = "\\ds416\backup",
    [Parameter(HelpMessage = "Get backup files only with no formatted output.")]
    [Switch]$Raw
)

<#
 A regular expression pattern to match on backup file name with named captures
 to be used in adding some custom properties. My backup names are like:

  20191101_Scripts-FULL.rar
  20191107_Scripts-INCREMENTAL.rar

  #>
[regex]$rx = "^20\d{6}_(?<set>\w+)-(?<type>\w+)\.rar$"

<#
I am doing so 'pre-filtering' on the file extension and then using the regular
expression filter to fine tune the results
#>
$files = Get-ChildItem -path $Path -filter *.rar | Where-Object { $rx.IsMatch($_.name) }

#add some custom properties to be used with formatted results based on named captures
foreach ($item in $files) {
    $setpath = $rx.matches($item.name).groups[1].value
    $settype = $rx.matches($item.name).groups[2].value

    $item | Add-Member -MemberType NoteProperty -Name SetPath -Value $setpath
    $item | Add-Member -MemberType NoteProperty -Name SetType -Value $setType
}

if ($raw) {
    $Files
}
else {
    $files | Sort-Object SetPath, SetType, LastWriteTime | Format-Table -GroupBy SetPath -Property LastWriteTime, Length, Name
}

El comportamiento predeterminado es mostrar la salida formateada.

Normalmente, nunca querrás incluir cmdlets de formato en tu código. Pero en este caso, estoy usando un script para obtener el resultado que quiero sin tener que escribir mucho. Por eso es un script y no una función. Teniendo esto en cuenta, el script de control también tiene un parámetro -Raw que escribe los objetos del archivo en la canalización.

Mi script de copia de seguridad semanal elimina los archivos incrementales. Por ahora, eliminaré manualmente las copias de seguridad completas más antiguas. Aunque podría agregar código para conservar solo las últimas 5 copias de seguridad completas.

Este sistema aún es nuevo y constantemente lo modifico y agrego nuevas herramientas. No creo que pueda convertir esto en un módulo PowerShell completo e independiente dado que he escrito mi código para mí y mi red. Pero debería haber suficiente código mío que puedas utilizar para crear tu propia solución. Tengo otras ideas y técnicas de respaldo que compartiré contigo en otro momento. Mientras tanto, me encantaría saber qué piensas sobre todo esto.

Artículos relacionados