Private/Backup-Item.ps1
|
# Internal helper -- copy a single file or directory into the backup tree. # Returns a PSCustomObject result row for the caller to accumulate into its # results table. Never throws -- copy failures degrade to Status='FAIL' rows. function Backup-Item { [CmdletBinding()] param( [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Source, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$BackupDir, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$DestSubPath, [Parameter(Mandatory)][ValidateNotNullOrEmpty()][string]$Label, [switch]$IsDir ) $dest = Join-Path $BackupDir $DestSubPath try { if ($IsDir) { if (Test-Path -LiteralPath $Source) { # [G4] sec F3: if the source directory IS a reparse point at the # top level, refuse to follow it -- the operator passed a path # that resolves to somewhere else, which is almost certainly # not what they wanted backed up. $srcItem = Get-Item -LiteralPath $Source -Force -ErrorAction Stop if ($srcItem.LinkType) { Write-Host " [SKIP] $Label ($($srcItem.LinkType) target $($srcItem.Target) -- not following)" -ForegroundColor Yellow return [PSCustomObject]@{ Item = $Label; Status = 'SKIP'; Detail = "$($srcItem.LinkType) -- not followed" } } $destParent = Split-Path $dest -Parent # [G4] sec F8: explicit -ErrorAction Stop so caller's # ErrorActionPreference inheritance can't bypass our try/catch. New-Item -ItemType Directory -Force -Path $destParent -ErrorAction Stop | Out-Null Copy-Item -Recurse -Force -LiteralPath $Source -Destination $dest -ErrorAction Stop $count = (Get-ChildItem -Recurse -LiteralPath $dest -File -Attributes !ReparsePoint -ErrorAction SilentlyContinue).Count Write-Host " [OK] $Label ($count files)" -ForegroundColor Green return [PSCustomObject]@{ Item = $Label; Status = 'OK'; Detail = "$count files" } } else { Write-Host " [SKIP] $Label (not found: $Source)" -ForegroundColor Yellow return [PSCustomObject]@{ Item = $Label; Status = 'SKIP'; Detail = 'Not found' } } } else { if (Test-Path -LiteralPath $Source) { $destParent = Split-Path $dest -Parent New-Item -ItemType Directory -Force -Path $destParent -ErrorAction Stop | Out-Null Copy-Item -Force -LiteralPath $Source -Destination $dest -ErrorAction Stop $size = (Get-Item -LiteralPath $dest).Length Write-Host " [OK] $Label ($size bytes)" -ForegroundColor Green return [PSCustomObject]@{ Item = $Label; Status = 'OK'; Detail = "$size bytes" } } else { Write-Host " [SKIP] $Label (not found: $Source)" -ForegroundColor Yellow return [PSCustomObject]@{ Item = $Label; Status = 'SKIP'; Detail = 'Not found' } } } } catch { Write-Host " [FAIL] $Label ($_)" -ForegroundColor Red return [PSCustomObject]@{ Item = $Label; Status = 'FAIL'; Detail = $_.Exception.Message } } } |