Skip to content

Instantly share code, notes, and snippets.

@kitsumed
Last active October 19, 2025 00:42
Show Gist options
  • Select an option

  • Save kitsumed/65effded3297e152d972907f8c1a8b75 to your computer and use it in GitHub Desktop.

Select an option

Save kitsumed/65effded3297e152d972907f8c1a8b75 to your computer and use it in GitHub Desktop.
Restic Backup Helper In Powershell
# PowerShell script for ImDisk and Restic setup (process-scoped TEMP/TMP for RAM DISK APPS) with menu. Quickly made with help of AI once I realised that on Windows restic actually write a lot of GB in %temp% to disk, causing wear on HDD/SSD.
# NOTE: With default restic config, around 400/500MB of RAM DISK should be enough, up to 1GB if there are issues.
<#
Copyright 2025 Kitsumed (Med)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#>
# Function to write colored text
function Write-Color {
param (
[string]$Text,
[ConsoleColor]$Color = "White"
)
$currentColor = $Host.UI.RawUI.ForegroundColor
$Host.UI.RawUI.ForegroundColor = $Color
Write-Host $Text
$Host.UI.RawUI.ForegroundColor = $currentColor
}
# --- Check for ImDisk ---
try {
$imdiskVer = imdisk --version 2>$null # Suppress error output, do not use imdiskVer as it is probably empty, or contains the full GPL license
if ($LASTEXITCODE -eq 0) {
Write-Color "✅ ImDisk detected on your system." Green
Write-Color "It is recommended to use a RAM Disk of around 500MB or more to reduce HDD/SSD wear when handling temporary data." Cyan
Start-Sleep -Seconds 2
}
else {
throw "ImDisk not found"
}
}
catch {
Write-Color "⚠️ ImDisk was not detected." Red
Write-Color "Consider installing ImDisk (https://github.com/DavidXanatos/ImDisk) or using another RAM Disk application." Yellow
Write-Color "This helps reduce HDD/SSD wear since %TEMP% and %TMP% are often heavily written and deleted." Cyan
Write-Color "Waiting 10 seconds before continuing..." Magenta
Start-Sleep -Seconds 10
}
# --- If ImDisk detected, ask for Drive letter/path ---
if ($LASTEXITCODE -eq 0) {
$ramTEMPPath = Read-Host "Enter the full path to use for %TEMP% files (e.g. R:\TEMP). Leave empty to use system %TEMP%"
if ([string]::IsNullOrWhiteSpace($ramTEMPPath)) {
Write-Color "`nNo input detected. Using system TEMP: $env:TEMP" Cyan
}
else {
if (-not (Test-Path $ramTEMPPath)) {
Write-Color "`nRAM disk path not found. Creating: $ramTEMPPath" Yellow
try {
New-Item -ItemType Directory -Path $ramTEMPPath -Force | Out-Null
Write-Color "Successfully created $ramTEMPPath" Green
} catch {
Write-Color "❌ Failed to create RAM disk directory. Check permissions or path validity." Red
}
}
$env:TEMP = $ramTEMPPath
$env:TMP = $ramTEMPPath
Write-Color "`n%TEMP% and %TMP% updated for this process to: $ramTEMPPath" Green
}
}
# --- Check for Restic in current directory ---
$resticPath = Join-Path -Path (Get-Location) -ChildPath "restic.exe"
if (Test-Path $resticPath) {
Write-Color "`n✅ Restic found in current directory." Green
# Prompt for repository, default to current directory if Enter pressed
$repoPath = Read-Host "Enter the path to your Restic repository (Empty to use current directory)"
if ([string]::IsNullOrWhiteSpace($repoPath)) {
$repoPath = (Get-Location).Path
Write-Color "No input detected. Using current directory as repository: $repoPath" Cyan
}
else {
Write-Color "Repository set to: $repoPath" Cyan
}
}
else {
Write-Color "`n❌ restic.exe not found in current directory!" Red
Write-Color "Please download it from https://github.com/restic/restic and place it here." Yellow
exit 1
}
Write-Color "`nSetup complete." Green
# --- Menu ---
function Show-Menu {
Clear-Host
Write-Color "======================================" Cyan
Write-Color " Restic Backup Menu " Yellow
Write-Color "======================================" Cyan
Write-Color "1. Show instructions / Help" Green
Write-Color "2. Perform a Health Check of Snapshots" Green
Write-Color "3. Create a New Snapshot" Green
Write-Color "4. Clean leftover files (Forget & Purge)" Green
Write-Color "5. List Snapshots" Green
Write-Color "6. Browse Snapshots Files" Green
Write-Color "7. Restore Snapshot" Green
Write-Color "8. Exit" Green
Write-Color "======================================" Cyan
}
do {
Show-Menu
$choice = Read-Host "Select an option [1-8]"
switch ($choice) {
'1' {
Write-Color "`n📘 Instructions / Help:" Yellow
Write-Color "- Start by performing a basic health check (Choice 2, then 1)." Cyan
Write-Color "- Backup your files (Choice 3)." Cyan
Write-Color "- You can perform another basic health check or a Reading Data check (to ensure blobs files are not corrupted and still matches saved hashes)." Cyan
Write-Color "- Clean leftover blobs files and old snapshots (Choice 4)." Cyan
Write-Color "`n📘 Recommendations:" Yellow
Write-Color "* Use a RAM Disk of around 500MB+ for %TEMP% files to reduce HDD/SSD wear." Cyan
Write-Color "`n📘 Restic for Beginners:" Yellow
Write-Color "* Restic stores your backups as `"snapshots`" inside a `"repository`"." Cyan
Write-Color "* Each snapshot represents the state of your files at a specific point in time." Cyan
Write-Color "* Restic uses `"deduplication`" to save space — it splits files into small `"blobs`" (data chunks) and only stores unique blobs once." Cyan
Write-Color "* When a file hasn't changed since the last backup, Restic reuses the same blob references instead of saving the file again." Cyan
Write-Color "* This makes backups fast and keeps your repository from growing too large." Cyan
Write-Color "* When cleaning old data (Forget & Purge), Restic first removes outdated snapshots according to a `"retention policy`"," Cyan
Write-Color " then deletes any blob files that are no longer referenced by any remaining snapshot." Cyan
Write-Color "* This script uses the following retention policy:" Cyan
Write-Color " - Keep 12 monthly snapshots (one per month for the past year)" Cyan
Write-Color " - Keep 1 yearly snapshot (a long-term backup for the last year)" Cyan
Write-Color "* By default, snapshots are grouped using two keys:" Cyan
Write-Color " - The name of the computer that created the backup (`host`)" Cyan
Write-Color " - The full path of the directory being backed up (`paths`)" Cyan
Write-Color "* This ensures that different computers, or different folders on the same machine," Cyan
Write-Color " each maintain their own independent snapshot history and cleanup cycle." Cyan
Write-Color " Note that even if snapshots are separate, Restic still reuses existing `"blobs`" whenever possible." Cyan
Write-Color " This means that if you move a directory to a new location and back it up again," Cyan
Write-Color " Restic will show the snapshot as independent but WILL NOT duplicate the data - it will detect that the file contents are the same," Cyan
Write-Color " and simply reference the already existing blobs instead of re-copying them." Cyan
Read-Host "`nPress Enter to return to menu..."
}
'2' {
Write-Color "`n🔍 Perform a Health Check" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "1. Basic Health Check (fast, minimal writes)" Green
Write-Color "2. Check by Reading Data (more intensive, reads backup data)" Green
Write-Color "*. Press anything else to return to main menu" Green
Write-Color "--------------------------------------" Cyan
$subChoice = Read-Host "Select an option [1-3]"
switch ($subChoice) {
'1' {
# Basic Health Check
$cmd = "`"$resticPath`" --repo `"$repoPath`" check"
Write-Color "`n🧾 Command preview:" Yellow
Write-Color $cmd Cyan
$confirm = Read-Host "Run this command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nRunning Basic Health Check..." Green
& "$resticPath" --repo "$repoPath" check
}
else {
Write-Color "Skipped Basic Health Check." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'2' {
# Check if ImDisk was detected and show warning if not available
if ($LASTEXITCODE -ne 0) {
Write-Color "`n⚠️ Warning: ImDisk was not detected earlier." Red
Write-Color "Running a read-data check will cause A LOT of WRITE operations on your OS %TEMP% disk. We recommend setting up a RAM DISK." Yellow
$cont = Read-Host "Are you sure you want to continue? (y/n)"
if ($cont -notmatch '^[Yy]$') {
Write-Color "Cancelled by user." Yellow
break
}
}
# Ask user for percentage of data to read
$inputPercent = Read-Host "Enter the percentage of data to read (1–100, press Enter for 100%)"
$percent = 1
if ([string]::IsNullOrWhiteSpace($inputPercent)) { $inputPercent = "100" }
if (-not [int]::TryParse($inputPercent, [ref]$percent)) {
Write-Color "`n❌ Invalid input. Please enter a numeric value." Red
Read-Host "`nPress Enter to return to menu..."
break
}
# Build argument array
$cmdArgs = @("--repo", "$($repoPath)", "check", "--no-cache")
if ([int]$percent -ge 1 -and [int]$percent -lt 100) {
$cmdArgs += "--read-data-subset=${percent}%" # Read % of whole data
}
elseif ([int]$percent -ge 100) {
$cmdArgs += "--read-data" # full read data 100%
}
elseif ([int]$percent -lt 1) {
Write-Color "`n❌ Invalid percentage value. Must be 1 or higher." Red
Read-Host "`nPress Enter to return to menu..."
break
}
# Preview command
Write-Color "`n🧾 Command preview:" Yellow
Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan
# Confirm and run
$confirm = Read-Host "Run this command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nRunning Health Check (read-data $percent%)..." Green
& $resticPath @cmdArgs
}
else {
Write-Color "Skipped Health Check with read-data." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
default {
Write-Color "`n❌ Invalid option.." Red
Read-Host "`nPress Enter to return to menu..."
}
}
}
'3' {
Write-Color "`n💾 Create a New Snapshot" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "This will create a new Restic backup (snapshot)." Cyan
Write-Color "You must enter a valid folder path to back up. Press Ctrl+C to cancel." Cyan
Write-Color "--------------------------------------" Cyan
# Check if ImDisk was detected and show warning if not available
if ($LASTEXITCODE -ne 0) {
Write-Color "`n⚠️ Warning: ImDisk was not detected earlier." Red
Write-Color "Running a backup will cause A LOT of WRITE operations on your OS %TEMP% disk. We recommend setting up a RAM DISK." Yellow
$cont = Read-Host "Are you sure you want to continue? (y/n)"
if ($cont -notmatch '^[Yy]$') {
Write-Color "Cancelled by user." Yellow
break
}
}
# Prompt until a non-empty, existing directory is provided
while ($true) {
$sourcePath = Read-Host "Enter the path to back up (no default; must be a folder)"
if ([string]::IsNullOrWhiteSpace($sourcePath)) {
Write-Color "❌ Empty input is not allowed. Please enter a valid folder path." Red
continue
}
if (-not (Test-Path -Path $sourcePath -PathType Container)) {
Write-Color "❌ Path not found or not a folder: $sourcePath" Red
continue
}
break
}
Write-Color "Selected source: $sourcePath" Cyan
# Build the command
$cmd = "`"$resticPath`" --repo `"$repoPath`" backup `"$sourcePath`" --no-cache"
Write-Color "`n🧾 Command preview:" Yellow
Write-Color $cmd Cyan
$confirm = Read-Host "Run this command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nRunning backup..." Green
& "$resticPath" --repo "$repoPath" backup "$sourcePath" --no-cache
}
else {
Write-Color "Backup cancelled by user." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'4' {
Write-Color "`n🧹 Clean Leftover Files (Forget & Prune)" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "This operation will remove old snapshots based on the retention policy:" Cyan
Write-Color " - Keep 12 monthly snapshots" Cyan
Write-Color " - Keep 1 yearly snapshot" Cyan
Write-Color " TLDR; Keep max 1 backup (snapshot) per month up to one year." Cyan
Write-Color "It will also prune unreferenced data to free disk space." Cyan
Write-Color "--------------------------------------" Cyan
# Ask for dry-run
$dryRun = Read-Host "Do you want to do a dry-run (test-run without I/O operations) first? (y/n)"
$dryRunOption = ""
if ($dryRun -match '^[Yy]$') {
$dryRunOption = "--dry-run"
Write-Color "Dry-run enabled. Restic will show what would be deleted without actually removing anything." Cyan
}
# Build the arguments array
$cmdArgs = @(
"--repo", $repoPath,
"forget",
"--prune",
"--keep-monthly", "12",
"--keep-yearly", "1",
"--group-by", "host,paths"
)
# Add dry-run if enabled
if ($dryRunOption) {
$cmdArgs += $dryRunOption
}
# Preview command
Write-Color "`n🧾 Command preview:" Yellow
Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan
# Confirm and run
$confirm = Read-Host "Run this cleanup command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nRunning cleanup (forget & prune)..." Green
& $resticPath @cmdArgs
}
else {
Write-Color "Cleanup cancelled by user." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'5' {
Write-Color "`n🗂️ List All Snapshots" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "This will list all backups (snapshots) currently stored in the repository." Cyan
Write-Color "Useful for verifying existing backups and retention status." Cyan
Write-Color "--------------------------------------" Cyan
# Build the command
$cmd = "`"$resticPath`" --repo `"$repoPath`" snapshots"
Write-Color "`n🧾 Command preview:" Yellow
Write-Color $cmd Cyan
$confirm = Read-Host "Run this command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nListing snapshots..." Green
& "$resticPath" --repo "$repoPath" snapshots
}
else {
Write-Color "Listing cancelled by user." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'6' {
Write-Color "`n📂 View Content of a Snapshot" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "This option lists all files and directories stored inside a specific snapshot." Cyan
Write-Color "You can also search for keywords or filter specific filenames or paths." Cyan
Write-Color "--------------------------------------" Cyan
# Ask for snapshot ID
while ($true) {
$snapshotID = Read-Host "Enter the snapshot ID or 'latest'"
if ([string]::IsNullOrWhiteSpace($snapshotID)) {
Write-Color "❌ Snapshot ID cannot be empty." Red
continue
}
break
}
# Ask if user wants to filter results
$filter = Read-Host "Enter one or more keywords to search for (comma-separated), or press Enter to show everything"
$filters = @()
if (-not [string]::IsNullOrWhiteSpace($filter)) {
$filters = $filter -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" }
Write-Color "`n🔍 Filter enabled: showing only items that contain: $($filters -join ', ')" Cyan
}
# Build restic command
$cmdArgs = @("--repo", "$repoPath", "ls", "$snapshotID")
# Preview
Write-Color "`n🧾 Command preview:" Yellow
Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan
$confirm = Read-Host "Run this command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nListing content of snapshot $snapshotID..." Green
if ($filters.Count -eq 0) {
# No filter: show all results directly
& $resticPath @cmdArgs
}
else {
# Filter results live while streaming
& $resticPath @cmdArgs | ForEach-Object {
$line = $_
foreach ($f in $filters) {
if ($line -match [Regex]::Escape($f)) {
Write-Host $line
break
}
}
}
}
}
else {
Write-Color "Operation cancelled by user." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'7' {
Write-Color "`n🔄 Restore Files from Snapshot" Yellow
Write-Color "--------------------------------------" Cyan
Write-Color "You can restore either a full snapshot path or a specific sub-directory inside the snapshot." Cyan
Write-Color "The '--include <path>' option tells Restic to only restore the specified sub-directory path from the snapshot." Cyan
Write-Color "If you don't specify --include, the entire snapshot is restored." Cyan
Write-Color "`n⚠️ Warning: The default behavior of the restore operation is to overwrite existing files with restored file inside the output path." Red
Write-Color "--------------------------------------" Cyan
# Ask for snapshot ID
while ($true) {
$snapshotID = Read-Host "Enter the snapshot ID or 'latest' to restore from"
if ([string]::IsNullOrWhiteSpace($snapshotID)) {
Write-Color "❌ Snapshot ID cannot be empty." Red
continue
}
break
}
# Ask if user wants to restore a specific folder
$includePath = Read-Host "Enter the path to restore (relative to snapshot browsing tree) or press Enter to restore full snapshot"
if (-not [string]::IsNullOrWhiteSpace($includePath)) {
$includeOption = "--include `"$includePath`""
Write-Color "`nThe restore will include only the specified path: $includePath" Cyan
}
else {
$includeOption = ""
Write-Color "`nThe restore will include the full snapshot." Cyan
}
# Ask for restore target directory
while ($true) {
$targetPath = Read-Host "Enter the target directory where files will be restored"
if ([string]::IsNullOrWhiteSpace($targetPath)) {
Write-Color "❌ Target directory cannot be empty." Red
continue
}
# Create target folder if it does not exist
if (-not (Test-Path $targetPath)) {
New-Item -ItemType Directory -Path $targetPath | Out-Null
Write-Color "Created target directory: $targetPath" Green
}
break
}
# Ask for dry-run
$dryRun = Read-Host "Do you want to do a dry-run (test-run without I/O operations) first? (y/n)"
# Build the arguments array
$cmdArgs = @(
"--repo", $repoPath,
"restore", $snapshotID,
"--target", $targetPath
)
# Add include option if specified
if (-not [string]::IsNullOrWhiteSpace($includePath)) {
$cmdArgs += "--include"
$cmdArgs += $includePath
}
# Add dry-run option if enabled
if ($dryRun -match '^[Yy]$') {
$cmdArgs += "--dry-run"
Write-Color "Dry-run enabled. Restic will show what would be restored without actually restoring." Cyan
}
# Preview command
Write-Color "`n🧾 Command preview:" Yellow
Write-Color "`"$resticPath`" $($cmdArgs -join ' ')" Cyan
# Confirm and run
$confirm = Read-Host "Run this restore command now? (y/n)"
if ($confirm -match '^[Yy]$') {
Write-Color "`nRunning restore..." Green
& $resticPath @cmdArgs
}
else {
Write-Color "Restore cancelled by user." Yellow
}
Read-Host "`nPress Enter to return to menu..."
}
'8' {
Write-Color "`nExiting... Goodbye!" Green
}
default {
Write-Color "`n❌ Invalid option. Please select a number between 1 and 7." Red
Read-Host "`nPress Enter to return to menu..."
}
}
} while ($choice -ne '8')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment