Created
February 16, 2026 05:38
-
-
Save anotherlab/0bfae255f41f87a83e31ec077bf3f606 to your computer and use it in GitHub Desktop.
Automated WSL Distribution Backup Script
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Automated WSL Distribution Backup Script | |
| .DESCRIPTION | |
| This script automatically backs up the default WSL distribution to a compressed archive | |
| and copies it to a specified destination. It performs the following operations: | |
| 1. Detects the default WSL distribution | |
| 2. Cleans the distribution name to remove problematic characters | |
| 3. Creates a date-stamped backup filename (YYYY-MM-DistroName) | |
| 4. Exports the WSL distribution to a TAR file in the temp directory | |
| 5. Compresses the TAR file to 7z format for space efficiency | |
| 6. Copies the compressed backup to the specified destination | |
| 7. Cleans up old backup files from temp directory | |
| .PARAMETER Destination | |
| The destination path where the backup file will be copied. | |
| Can be a local path, UNC network path, or mapped drive. | |
| .EXAMPLE | |
| .\backup-wsl2.ps1 -Destination "C:\Backups" | |
| .\backup-wsl2.ps1 -Destination "\\server\backups\wsl" | |
| .NOTES | |
| Requirements: | |
| - WSL2 must be installed and configured | |
| - 7-Zip must be installed at standard location (C:\Program Files\7-Zip\7z.exe) | |
| - Sufficient disk space in temp directory for TAR export | |
| - Write permissions to destination path | |
| #> | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$Destination | |
| ) | |
| # ================================================================================================ | |
| # HELPER FUNCTIONS | |
| # ================================================================================================ | |
| # Gets the default WSL distribution name by parsing 'wsl --list' output | |
| # This function is faster than starting a new WSL instance, but still relies on parsing text output | |
| function Get-DefaultWSLDistributionQuick { | |
| $results = wsl --list --all 2>$null | |
| foreach ($p in $results) { | |
| $line = $p.Trim() | |
| $len = $line.IndexOf(' (Default)') | |
| if ($len -ge 0) { | |
| return $line.Substring(0, $len) | |
| } | |
| } | |
| return $null | |
| } | |
| # Generates a standardized archive filename using current date and distribution name | |
| # Format: YYYY-MM-DistributionName (e.g., "2026-02-Ubuntu-24.04") | |
| function Get-ArchiveFileName { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$DistroName | |
| ) | |
| # Get current date, formatted as YYYY-MM, and combine with distribution name | |
| return (Get-Date).ToString("yyyy-MM") + "-" + $DistroName | |
| } | |
| # Constructs the full path to a TAR archive file in the system temp directory | |
| # Used to create temporary export files before compression | |
| function Get-TarArchivePath { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$ArchiveName | |
| ) | |
| # Get the temp folder path and combine with archive name and .tar extension | |
| return Join-Path -Path $env:TEMP -ChildPath "$ArchiveName.tar" | |
| } | |
| # Exports a WSL distribution to a TAR file using 'wsl --export' command | |
| # Includes comprehensive error handling and validation | |
| function Export-WSLDistribution { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$DistributionName, | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$ArchiveName | |
| ) | |
| try { | |
| # Get the full tar path using the existing function | |
| $tarPath = Get-TarArchivePath -ArchiveName $ArchiveName | |
| Write-Host "Exporting WSL distribution '$DistributionName' to '$tarPath'..." | |
| # Execute wsl --export command | |
| $result = wsl --export $DistributionName $tarPath 2>&1 | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "WSL export for '$DistributionName' to '$tarPath' failed with exit code $LASTEXITCODE. Error: $result" | |
| } | |
| if (Test-Path $tarPath) { | |
| Write-Host "Export completed successfully: $tarPath" | |
| return $tarPath | |
| } else { | |
| throw "Export appeared to succeed but tar file was not created at: $tarPath" | |
| } | |
| } | |
| catch { | |
| Write-Error "Failed to export WSL distribution '$DistributionName': $($_.Exception.Message)" | |
| throw | |
| } | |
| } | |
| # Removes old backup files (.tar and .7z) from temp directory that match the distribution name | |
| # Searches for files with current century prefix (e.g., "20") to avoid deleting unrelated files | |
| function Remove-OldWSLBackups { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$DistributionName | |
| ) | |
| try { | |
| # Get first 2 digits of current year | |
| $yearPrefix = (Get-Date).Year.ToString().Substring(0, 2) | |
| # Build search patterns for both .tar and .7z files | |
| $tarPattern = "$yearPrefix*$DistributionName*.tar" | |
| $zipPattern = "$yearPrefix*$DistributionName*.7z" | |
| Write-Host "Searching for old backups matching patterns: $tarPattern, $zipPattern" | |
| # Get matching files in temp directory for both extensions | |
| $tarBackups = Get-ChildItem -Path $env:TEMP -Filter $tarPattern -File -ErrorAction SilentlyContinue | |
| $zipBackups = Get-ChildItem -Path $env:TEMP -Filter $zipPattern -File -ErrorAction SilentlyContinue | |
| # Combine both results | |
| $oldBackups = @($tarBackups) + @($zipBackups) | |
| if ($oldBackups.Count -eq 0) { | |
| Write-Host "No old backup files found to delete." | |
| return | |
| } | |
| Write-Host "Found $($oldBackups.Count) old backup file(s) to delete:" | |
| foreach ($file in $oldBackups) { | |
| Write-Host " Deleting: $($file.FullName)" | |
| Remove-Item -Path $file.FullName -Force -ErrorAction Stop | |
| Write-Host " Successfully deleted: $($file.Name)" | |
| } | |
| Write-Host "Cleanup completed successfully." | |
| } | |
| catch { | |
| Write-Error "Failed to remove old WSL backups: $($_.Exception.Message)" | |
| throw | |
| } | |
| } | |
| # Compresses a TAR file to 7z format using 7-Zip for space efficiency | |
| # 7z typically provides better compression ratios than ZIP for large WSL exports | |
| function Compress-WSLBackup { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [ValidateNotNullOrEmpty()] | |
| [string]$TarPath | |
| ) | |
| try { | |
| # Get the zip path by changing the extension of the tar path to .7z | |
| $zipPath = [System.IO.Path]::ChangeExtension($TarPath, ".7z") | |
| # Need to use Start-Process to call 7z.exe, and wait for it to finish before checking if the file was created | |
| start-Process -FilePath "C:\Program Files\7-Zip\7z.exe" -ArgumentList "a", "-t7z", $zipPath, $TarPath -Wait -NoNewWindow | |
| if (Test-Path $zipPath) { | |
| Write-Host "Compression completed successfully: $zipPath" | |
| return $zipPath | |
| } else { | |
| throw "Compression appeared to succeed but 7-Zip file was not created at: $zipPath" | |
| } | |
| } | |
| catch { | |
| Write-Error "Failed to compress WSL backup: $($_.Exception.Message)" | |
| throw | |
| } | |
| } | |
| # ================================================================================================ | |
| # MAIN EXECUTION LOGIC | |
| # ================================================================================================ | |
| # Step 1: Detect the default WSL distribution | |
| $defaultDistro = Get-DefaultWSLDistributionQuick | |
| if (-not $defaultDistro) { | |
| throw "Unable to determine the default WSL distribution." | |
| } else { | |
| # Step 2: Clean the distribution name to remove problematic characters | |
| # WSL output can contain BOM, control characters, or extra whitespace that breaks commands | |
| $defaultDistro = ($defaultDistro -replace '[\x00-\x1F\x7F-\x9F]', '' -replace '\s+', ' ').Trim() | |
| # Step 3: Generate standardized filenames for the backup process | |
| $archiveName = Get-ArchiveFileName -DistroName $defaultDistro | |
| $tarPath = Get-TarArchivePath -ArchiveName $archiveName | |
| # Step 4: Export WSL distribution and compress (currently commented for testing) | |
| $tarPath = Export-WSLDistribution -DistributionName $defaultDistro -ArchiveName $archiveName | |
| # Step 5: Compress the TAR file to 7z format for efficient storage | |
| $zipPath = Compress-WSLBackup -TarPath $tarPath | |
| # Step 6: Copy the compressed backup to the specified destination | |
| Write-Host "Copying backup to destination: $Destination" | |
| Copy-Item -Path $zipPath -Destination $Destination | |
| # Step 6: Clean up old backup files from temp directory to free disk space | |
| Remove-OldWSLBackups -DistributionName $defaultDistro | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment