Backup-Directory.ps1
Niveau : Intermédiaire
Backup de répertoires avec rotation et compression.
Description
Ce script effectue des sauvegardes de répertoires : - Compression ZIP native - Rotation automatique des anciennes sauvegardes - Exclusion de fichiers/dossiers - Vérification d'intégrité - Logging détaillé
Prérequis
- Système : Windows Server 2016+ ou Windows 10/11
- PowerShell : Version 5.1 minimum
- Permissions : Lecture sur le répertoire source, écriture sur la destination
- Modules : Aucun module externe requis
Cas d'Usage
- Sauvegardes automatisées : Planifier des backups quotidiens avec le Planificateur de tâches
- Protection de données : Créer des copies de sécurité avant des migrations ou mises à jour
- Archivage : Conserver plusieurs versions de répertoires avec rotation automatique
- Disaster Recovery : Maintenir des sauvegardes hors-ligne pour la reprise après incident
Script
#Requires -Version 5.1
<#
.SYNOPSIS
Backup de répertoires avec rotation.
.DESCRIPTION
Crée des sauvegardes compressées de répertoires avec
rotation automatique des anciennes sauvegardes.
.PARAMETER Source
Chemin du répertoire source à sauvegarder.
.PARAMETER Destination
Chemin du répertoire de destination.
.PARAMETER KeepBackups
Nombre de sauvegardes à conserver (défaut: 7).
.PARAMETER ExcludePatterns
Patterns à exclure (ex: *.log, temp).
.PARAMETER Verify
Vérifie l'intégrité après création.
.PARAMETER WhatIf
Simulation sans création.
.EXAMPLE
.\Backup-Directory.ps1 -Source "C:\Data" -Destination "D:\Backup"
.EXAMPLE
.\Backup-Directory.ps1 -Source "C:\Web" -Destination "\\server\backup" -KeepBackups 14 -Verify
.NOTES
Author: ShellBook
Version: 1.0
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)]
[ValidateScript({ Test-Path $_ -PathType Container })]
[string]$Source,
[Parameter(Mandatory)]
[string]$Destination,
[Parameter()]
[ValidateRange(1, 365)]
[int]$KeepBackups = 7,
[Parameter()]
[string[]]$ExcludePatterns = @(),
[Parameter()]
[switch]$Verify
)
#region Functions
function Write-Log {
param(
[string]$Message,
[ValidateSet('Info', 'Warning', 'Error', 'Success')]
[string]$Level = 'Info'
)
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$colors = @{
'Info' = 'Cyan'
'Warning' = 'Yellow'
'Error' = 'Red'
'Success' = 'Green'
}
Write-Host "[$timestamp] " -NoNewline -ForegroundColor Gray
Write-Host "[$Level] " -NoNewline -ForegroundColor $colors[$Level]
Write-Host $Message
}
function Format-FileSize {
param([long]$Bytes)
switch ($Bytes) {
{ $_ -ge 1GB } { return "{0:N2} GB" -f ($_ / 1GB) }
{ $_ -ge 1MB } { return "{0:N2} MB" -f ($_ / 1MB) }
{ $_ -ge 1KB } { return "{0:N2} KB" -f ($_ / 1KB) }
default { return "$_ B" }
}
}
function Get-DirectorySize {
param([string]$Path)
(Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum).Sum
}
function Remove-OldBackups {
param(
[string]$BackupPath,
[string]$Pattern,
[int]$KeepCount
)
$backups = Get-ChildItem -Path $BackupPath -Filter $Pattern -File |
Sort-Object -Property CreationTime -Descending
if ($backups.Count -gt $KeepCount) {
$toDelete = $backups | Select-Object -Skip $KeepCount
foreach ($backup in $toDelete) {
Write-Log "Removing old backup: $($backup.Name)" -Level Warning
Remove-Item -Path $backup.FullName -Force
}
return $toDelete.Count
}
return 0
}
function Test-ZipIntegrity {
param([string]$ZipPath)
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($ZipPath)
$entryCount = $zip.Entries.Count
$zip.Dispose()
return @{
Success = $true
EntryCount = $entryCount
}
}
catch {
return @{
Success = $false
Error = $_.Exception.Message
}
}
}
#endregion
#region Main
$ErrorActionPreference = 'Stop'
Write-Host ""
Write-Host ("=" * 60) -ForegroundColor Cyan
Write-Host " BACKUP DIRECTORY" -ForegroundColor Green
Write-Host ("=" * 60) -ForegroundColor Cyan
# Préparer les chemins
$sourceName = Split-Path -Path $Source -Leaf
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$backupName = "${sourceName}_${timestamp}.zip"
# Créer le répertoire destination si nécessaire
if (-not (Test-Path $Destination)) {
Write-Log "Creating destination directory: $Destination" -Level Info
if ($PSCmdlet.ShouldProcess($Destination, "Create directory")) {
New-Item -Path $Destination -ItemType Directory -Force | Out-Null
}
}
$backupPath = Join-Path -Path $Destination -ChildPath $backupName
Write-Log "Source: $Source" -Level Info
Write-Log "Destination: $backupPath" -Level Info
Write-Log "Keep backups: $KeepBackups" -Level Info
# Calculer la taille source
Write-Log "Calculating source size..." -Level Info
$sourceSize = Get-DirectorySize -Path $Source
Write-Log "Source size: $(Format-FileSize $sourceSize)" -Level Info
# Préparer les fichiers à inclure (avec exclusions)
$tempFolder = $null
if ($ExcludePatterns.Count -gt 0) {
Write-Log "Applying exclusion patterns..." -Level Info
# Créer un dossier temporaire filtré
$tempFolder = Join-Path -Path $env:TEMP -ChildPath "backup_$(Get-Random)"
New-Item -Path $tempFolder -ItemType Directory -Force | Out-Null
# Copier avec exclusions
$excludeRegex = ($ExcludePatterns | ForEach-Object {
[regex]::Escape($_).Replace('\*', '.*').Replace('\?', '.')
}) -join '|'
Get-ChildItem -Path $Source -Recurse | Where-Object {
$relativePath = $_.FullName.Substring($Source.Length)
-not ($relativePath -match $excludeRegex)
} | ForEach-Object {
$destPath = Join-Path -Path $tempFolder -ChildPath $_.FullName.Substring($Source.Length)
if ($_.PSIsContainer) {
New-Item -Path $destPath -ItemType Directory -Force | Out-Null
} else {
$destDir = Split-Path -Path $destPath -Parent
if (-not (Test-Path $destDir)) {
New-Item -Path $destDir -ItemType Directory -Force | Out-Null
}
Copy-Item -Path $_.FullName -Destination $destPath -Force
}
}
$compressSource = $tempFolder
} else {
$compressSource = $Source
}
# Créer la sauvegarde
Write-Log "Creating backup..." -Level Info
$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
if ($PSCmdlet.ShouldProcess($backupPath, "Create backup")) {
try {
Compress-Archive -Path "$compressSource\*" -DestinationPath $backupPath -CompressionLevel Optimal -Force
$stopwatch.Stop()
$backupSize = (Get-Item $backupPath).Length
$ratio = [math]::Round(($backupSize / $sourceSize) * 100, 1)
Write-Log "Backup created: $backupName" -Level Success
Write-Log "Backup size: $(Format-FileSize $backupSize) ($ratio% of original)" -Level Info
Write-Log "Duration: $($stopwatch.Elapsed.ToString('mm\:ss'))" -Level Info
}
catch {
Write-Log "Backup failed: $_" -Level Error
throw
}
finally {
# Nettoyer le dossier temporaire
if ($tempFolder -and (Test-Path $tempFolder)) {
Remove-Item -Path $tempFolder -Recurse -Force
}
}
# Check d'intégrité
if ($Verify) {
Write-Log "Verifying backup integrity..." -Level Info
$integrityResult = Test-ZipIntegrity -ZipPath $backupPath
if ($integrityResult.Success) {
Write-Log "Integrity OK: $($integrityResult.EntryCount) entries" -Level Success
} else {
Write-Log "Integrity check failed: $($integrityResult.Error)" -Level Error
throw "Backup integrity verification failed"
}
}
# Rotation des anciennes sauvegardes
Write-Log "Checking backup rotation..." -Level Info
$pattern = "${sourceName}_*.zip"
$deletedCount = Remove-OldBackups -BackupPath $Destination -Pattern $pattern -KeepCount $KeepBackups
if ($deletedCount -gt 0) {
Write-Log "Removed $deletedCount old backup(s)" -Level Info
}
}
# Lister les sauvegardes disponibles
Write-Host ""
Write-Host ("-" * 60) -ForegroundColor Cyan
Write-Log "Available backups:" -Level Info
Get-ChildItem -Path $Destination -Filter "${sourceName}_*.zip" |
Sort-Object -Property CreationTime -Descending |
ForEach-Object {
$age = (Get-Date) - $_.CreationTime
$ageStr = if ($age.Days -gt 0) { "$($age.Days)d ago" } else { "$($age.Hours)h ago" }
Write-Host " $($_.Name) ($(Format-FileSize $_.Length)) - $ageStr"
}
Write-Host ""
Write-Host ("=" * 60) -ForegroundColor Cyan
Write-Log "Backup completed successfully!" -Level Success
Write-Host ("=" * 60) -ForegroundColor Cyan
#endregion
Utilisation
# Backup simple
.\Backup-Directory.ps1 -Source "C:\Data" -Destination "D:\Backup"
# Avec rotation personnalisée
.\Backup-Directory.ps1 -Source "C:\Web" -Destination "D:\Backup" -KeepBackups 14
# Avec exclusions et vérification
.\Backup-Directory.ps1 -Source "C:\Project" -Destination "D:\Backup" `
-ExcludePatterns @("*.log", "node_modules", ".git") -Verify
# Simulation
.\Backup-Directory.ps1 -Source "C:\Data" -Destination "D:\Backup" -WhatIf
Sortie Exemple
============================================================
BACKUP DIRECTORY
============================================================
[2024-01-15 14:30:22] [Info] Source: C:\Data
[2024-01-15 14:30:22] [Info] Destination: D:\Backup\Data_20240115_143022.zip
[2024-01-15 14:30:22] [Info] Keep backups: 7
[2024-01-15 14:30:22] [Info] Calculating source size...
[2024-01-15 14:30:23] [Info] Source size: 1.45 GB
[2024-01-15 14:30:23] [Info] Creating backup...
[2024-01-15 14:31:15] [Success] Backup created: Data_20240115_143022.zip
[2024-01-15 14:31:15] [Info] Backup size: 512.34 MB (35.3% of original)
[2024-01-15 14:31:15] [Info] Duration: 00:52
[2024-01-15 14:31:15] [Info] Verifying backup integrity...
[2024-01-15 14:31:18] [Success] Integrity OK: 2456 entries
[2024-01-15 14:31:18] [Info] Checking backup rotation...
[2024-01-15 14:31:18] [Warning] Removing old backup: Data_20240108_143022.zip
------------------------------------------------------------
[2024-01-15 14:31:18] [Info] Available backups:
Data_20240115_143022.zip (512.34 MB) - 0h ago
Data_20240114_143022.zip (508.12 MB) - 1d ago
Data_20240113_143022.zip (505.89 MB) - 2d ago
============================================================
[2024-01-15 14:31:18] [Success] Backup completed successfully!
============================================================
Tâche Planifiée
# Créer une tâche planifiée pour backup quotidien
$action = New-ScheduledTaskAction -Execute 'PowerShell.exe' `
-Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Backup-Directory.ps1" -Source "C:\Data" -Destination "D:\Backup" -Verify'
$trigger = New-ScheduledTaskTrigger -Daily -At 2am
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd
Register-ScheduledTask -TaskName "DailyBackup" `
-Action $action -Trigger $trigger -Settings $settings `
-Description "Daily backup of C:\Data"