Why pay for an expensive backup solution in your home-lab when you can install PowerShell for free? The script below will handle simple backups for you. You only have to add it to the Windows Scheduler or create a Cron job.
This Script’s features:
– Replicates a Source directory to a destination to be specified in the script
– Automatically creates the destination folder structure
– Identifies changes or new files in the Source folder and only copies the changed or new files files to the destination
– If a file is deleted from the source, it will move the file from the backup destination to a recycle bin folder to be specified in the script.
– The script will delete files from the recycle bin after 30 days.
– Events are logged to a logfile located in the same directory as the script. The logfile is rotated every day the script is run and are auto-purged every 90 days
– Progress indicator is displayed on the console
Note: The folders specified in the script below should be modified for your own use. Save the script with an editor (I use Notepad++), give it any name you want and save it as a Windows PowerShell (*.ps1) file.
# Set Source, Destination and Recycle Bin paths here
$sourcePath = "C:\LocalFolder"
$backupPath = "\\RemoteServer\Share\Backup"
$oldFilesPath = "\\RemoteServer\Share\OldFilesDirectory"
$scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
$logPath = Join-Path -Path $scriptPath -ChildPath ("Log_" + (Get-Date -Format "yyyyMMdd") + ".txt")
# Create or append to the log file and optionally display output
function LogWrite {
Param ([string]$logstring, [bool]$toConsole = $false)
$timestampedLog = "$(Get-Date -Format "yyyy-MM-dd HH:mm:ss"): $logstring"
Add-Content $logPath -Value $timestampedLog
if ($toConsole) {
Write-Output $timestampedLog
}
}
# Remove log files older than 90 days
Get-ChildItem -Path $scriptPath -Filter Log_*.txt | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-90) } | ForEach-Object {
LogWrite "Deleting old log file: $($_.Name)" -toConsole $true
Remove-Item $_.FullName
}
LogWrite "Starting backup operation." -toConsole $true
# Create backup and old files directories if these don't exist
if (-not (Test-Path -Path $backupPath)) {
New-Item -ItemType Directory -Path $backupPath
LogWrite "Created Backup directory: $backupPath" -toConsole $true
}
if (-not (Test-Path -Path $oldFilesPath)) {
New-Item -ItemType Directory -Path $oldFilesPath
LogWrite "Created Recycle Bin directory: $oldFilesPath" -toConsole $true
}
# Get the current state of source and backup directories
$sourceFiles = Get-ChildItem -Path $sourcePath -Recurse -File
$backupFiles = Get-ChildItem -Path $backupPath -Recurse -File
# Copy new and modified files with progress bar
$totalFiles = $sourceFiles.Count
$i = 0
foreach ($file in $sourceFiles) {
$i++
$percentComplete = ($i / $totalFiles) * 100
Write-Progress -Activity "Copying new and modified files" -Status "Processing $($file.Name) ($i/$totalFiles)" -PercentComplete $percentComplete
$backupFile = $backupFiles | Where-Object { $_.FullName -eq $file.FullName.Replace($sourcePath, $backupPath) }
if ($null -eq $backupFile -or $file.LastWriteTime -gt $backupFile.LastWriteTime) {
$destPath = $file.FullName.Replace($sourcePath, $backupPath)
$destDir = [System.IO.Path]::GetDirectoryName($destPath)
if (-not (Test-Path $destDir)) {
New-Item -ItemType Directory -Path $destDir
}
Copy-Item -Path $file.FullName -Destination $destPath -Force
LogWrite "Copied or updated file: $($file.FullName) to the Backup location."
}
}
# Refresh list after copy for moving deleted files
$backupFiles = Get-ChildItem -Path $backupPath -Recurse -File
$totalFiles = $backupFiles.Count
$i = 0
foreach ($file in $backupFiles) {
$i++
$percentComplete = ($i / $totalFiles) * 100
Write-Progress -Activity "Moving deleted files to OldFiles" -Status "Processing $($file.Name) ($i/$totalFiles)" -PercentComplete $percentComplete
$sourceFile = $sourceFiles | Where-Object { $_.FullName -eq $file.FullName.Replace($backupPath, $sourcePath) }
if ($null -eq $sourceFile) {
$oldFilePath = $file.FullName.Replace($backupPath, $oldFilesPath)
$oldFileDir = [System.IO.Path]::GetDirectoryName($oldFilePath)
if (-not (Test-Path $oldFileDir)) {
New-Item -ItemType Directory -Path $oldFileDir
}
Move-Item -Path $file.FullName -Destination $oldFilePath
LogWrite "Moved deleted file: $($file.FullName) to Recycle Bin at $oldFilesPath."
}
}
# Delete files from Old Files that are older than 30 days
$oldFiles = Get-ChildItem -Path $oldFilesPath -Recurse -File
$totalFiles = $oldFiles.Count
$i = 0
foreach ($file in $oldFiles) {
$i++
$percentComplete = ($i / $totalFiles) * 100
Write-Progress -Activity "Deleting files older than 30 days from OldFiles" -Status "Processing $($file.Name) ($i/$totalFiles)" -PercentComplete $percentComplete
if ($file.CreationTime -lt (Get-Date).AddDays(-30)) {
Remove-Item -Path $file.FullName
LogWrite "Deleted file older than 30 days: $($file.FullName)."
}
}
LogWrite "Backup operation completed." -toConsole $true