public/Remove-PSProfileSnapshot.ps1
|
<# .SYNOPSIS Removes snapshots and all associated files. .DESCRIPTION Deletes profile snapshots based on various criteria. You can remove snapshots by name, GUID, or apply retention policies to automatically clean up old snapshots. Retention policies allow you to keep only the most recent N snapshots or snapshots created within a certain timespan. When removing a snapshot, ALL associated files are deleted: - Backup file (.backup) - Hash file (.hash) - Metadata file (.metadata) - Pre-restore safety backup (.backup.pre-restore) This prevents orphaned files from accumulating on the filesystem. .PARAMETER Name The name of a specific snapshot to remove. Can be piped from Get-PSProfileSnapshot. .PARAMETER GUID The GUID of a specific snapshot to remove. Can be piped from Get-PSProfileSnapshot. .PARAMETER KeepLast Remove all snapshots except the most recent N snapshots. For example, -KeepLast 3 will keep only the 3 most recent snapshots. .PARAMETER OlderThan Remove snapshots older than the specified timespan. For example, -OlderThan (New-TimeSpan -Days 30) removes snapshots older than 30 days. .INPUTS None .OUTPUTS None .EXAMPLE Remove-PSProfileSnapshot -Name "old-config" Removes the snapshot named "old-config" and all associated files. .EXAMPLE Get-PSProfileSnapshot | Where-Object Name -EQ "old-config" | Remove-PSProfileSnapshot Removes snapshot by piping from Get-PSProfileSnapshot. .EXAMPLE Remove-PSProfileSnapshot -KeepLast 5 Removes all snapshots except the 5 most recent ones. .EXAMPLE Remove-PSProfileSnapshot -OlderThan (New-TimeSpan -Days 30) Removes all snapshots older than 30 days. .NOTES Be cautious when using retention policies. They will remove multiple snapshots. Use -WhatIf to preview what would be deleted before executing. #> function Remove-PSProfileSnapshot { [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High', DefaultParameterSetName = 'ByName')] param( [Parameter(Mandatory = $true, ParameterSetName = 'ByName', ValueFromPipelineByPropertyName = $true)] [Alias('SnapshotName')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'ByGUID', ValueFromPipelineByPropertyName = $true)] [Alias('SnapshotGUID')] [guid]$GUID, [Parameter(Mandatory = $true, ParameterSetName = 'KeepLast')] [int]$KeepLast, [Parameter(Mandatory = $true, ParameterSetName = 'OlderThan')] [timespan]$OlderThan ) try { $profilesPath = Split-Path -Path $profile -Parent $backupFiles = Get-ChildItem -Path $profilesPath -Filter "*.backup" -ErrorAction Stop | Sort-Object -Property CreationTime -Descending if (-not $backupFiles) { Write-Host "ℹ️ No snapshots found to remove." -ForegroundColor Cyan return } $snapshotsToRemove = @() switch ($PSCmdlet.ParameterSetName) { 'ByName' { # Remove by snapshot name (either custom name from metadata or timestamp) foreach ($backupFile in $backupFiles) { $guid = $backupFile.BaseName -replace '^.*_([a-f0-9\-]+)$', '$1' $metadataFileName = "$($profile)_$($guid).metadata" # Check metadata first for custom name if (Test-Path -Path $metadataFileName -ErrorAction SilentlyContinue) { $metadata = Get-Content -Path $metadataFileName -Raw | ConvertFrom-Json -ErrorAction SilentlyContinue if ($metadata.Name -eq $Name) { $snapshotsToRemove += @{ BackupFile = $backupFile GUID = $guid } break } } # Also check if name matches the timestamp format of CreatedTime $timestampName = $backupFile.CreationTime.ToString("yyyy-MM-dd HH:mm:ss") if ($timestampName -eq $Name) { $snapshotsToRemove += @{ BackupFile = $backupFile GUID = $guid } break } } if ($snapshotsToRemove.Count -eq 0) { Write-Host "⚠️ No snapshot found with name: $SnapshotName" -ForegroundColor Yellow return } } 'ByGUID' { # Remove by GUID foreach ($backupFile in $backupFiles) { $guid = $backupFile.BaseName -replace '^.*_([a-f0-9\-]+)$', '$1' if ($guid -eq $SnapshotGUID.ToString()) { $snapshotsToRemove += @{ BackupFile = $backupFile GUID = $guid } break } } if ($snapshotsToRemove.Count -eq 0) { Write-Host "⚠️ No snapshot found with GUID: $SnapshotGUID" -ForegroundColor Yellow return } } 'KeepLast' { # Keep only the last N snapshots if ($KeepLast -lt 1) { Write-Error "❌ KeepLast must be at least 1." return } if ($backupFiles.Count -le $KeepLast) { Write-Host "ℹ️ You have $($backupFiles.Count) snapshot(s); no removal needed." -ForegroundColor Cyan return } # Remove all but the last N $snapshotsToRemove = @() for ($i = $KeepLast; $i -lt $backupFiles.Count; $i++) { $guid = $backupFiles[$i].BaseName -replace '^.*_([a-f0-9\-]+)$', '$1' $snapshotsToRemove += @{ BackupFile = $backupFiles[$i] GUID = $guid } } } 'OlderThan' { # Remove snapshots older than the specified timespan $cutoffTime = (Get-Date) - $OlderThan foreach ($backupFile in $backupFiles) { if ($backupFile.CreationTime -lt $cutoffTime) { $guid = $backupFile.BaseName -replace '^.*_([a-f0-9\-]+)$', '$1' $snapshotsToRemove += @{ BackupFile = $backupFile GUID = $guid } } } if ($snapshotsToRemove.Count -eq 0) { Write-Host "ℹ️ No snapshots found older than $($OlderThan.ToString())." -ForegroundColor Cyan return } } } # Remove the identified snapshots and ALL associated files foreach ($snapshot in $snapshotsToRemove) { $backupFile = $snapshot.BackupFile $guid = $snapshot.GUID $hashFileName = "$($profile)_$($guid).hash" $metadataFileName = "$($profile)_$($guid).metadata" $preRestoreFileName = "$($profile)_$($guid).backup.pre-restore" $actionTarget = "$($backupFile.Name) and associated files (hash, metadata, pre-restore)" if ($PSCmdlet.ShouldProcess($actionTarget, 'Remove')) { $filesRemoved = 0 try { # Remove backup file if (Test-Path -Path $backupFile.FullName -ErrorAction SilentlyContinue) { Remove-Item -Path $backupFile.FullName -Force -ErrorAction Stop $filesRemoved++ } # Remove hash file if it exists if (Test-Path -Path $hashFileName -ErrorAction SilentlyContinue) { Remove-Item -Path $hashFileName -Force -ErrorAction Stop $filesRemoved++ } # Remove metadata file if it exists if (Test-Path -Path $metadataFileName -ErrorAction SilentlyContinue) { Remove-Item -Path $metadataFileName -Force -ErrorAction Stop $filesRemoved++ } # Remove pre-restore safety backup if it exists if (Test-Path -Path $preRestoreFileName -ErrorAction SilentlyContinue) { Remove-Item -Path $preRestoreFileName -Force -ErrorAction Stop $filesRemoved++ } Write-Host "✅ Removed snapshot and $filesRemoved associated file(s): $($backupFile.Name)" -ForegroundColor Green } catch { Write-Error "❌ Could not remove snapshot: $($backupFile.Name)" } } } Write-Host "`n✅ Snapshot removal complete." -ForegroundColor Green } catch { Write-Error "❌ An error occurred while removing snapshots." throw $_.Exception.Message } } |