Public/Backup.ps1
|
using namespace System.Collections using namespace System.Collections.Concurrent using namespace System.IO.Compression using namespace System.IO #### # Backup-FileParallel function Backup-FileParallel { #### Recursive gzip streaming parallel compression with fail fast semantics and interactive feedback. #### #### **Parameters** #### - `[string]`: __Path__ #### - *Existing source directory. Walked recursively.* #### - `[string]`: __OutPath__ #### - *Destination root. Created if missing. Mirrors the source tree.* #### - `[int]`: __Throttle__ #### - *Throttle passed to `ForEach-Object -Parallel`. Defaults to 4.* #### #### **Returns** #### - *None. Writes .gz files to OutPath and a CompressionErrors.json on partial failure.* #### #### **Throws** #### - *When Path does not exist.* [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string]$OutPath, [Parameter(Mandatory = $false)] [int]$Throttle = 4 ) $validDir = (Test-Path $Path -PathType Container) if (-not $validDir) { throw "Path not found" } if (-not (Test-Path $OutPath)) { New-Item $OutPath -ItemType Directory -Force } $Path = Resolve-Path $Path $OutPath = Resolve-Path $OutPath $prlErr = [ConcurrentDictionary[string, string]]::new() [ArrayList]$mtxProg = [ArrayList]::Synchronized(@(0)) $startTime = Get-Date Get-ChildItem $Path -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object -Parallel { $errors = $using:prlErr $prog = $using:mtxProg [void]$prog.Add(1) if (($prog.Count % 100) -eq 0) { $currentTime = Get-Date $elapsedTime = [string]::Format('{0:hh\:mm\:ss}', ($currentTime - $using:startTime)) Write-Progress -Activity 'Compressing' -Status "$elapsedTime Processed: $($prog.Count)" } $fPath = $_.FullName $relativePath = [System.IO.Path]::GetRelativePath($using:Path, $_.FullName) $destPath = Join-Path -Path $using:OutPath -ChildPath $relativePath $destDir = [System.IO.Path]::GetDirectoryName($destPath) if (-not (Test-Path -Path $destDir)) { [void](New-Item -Path $destDir -ItemType Directory -Force) } $gzipfPath = "${destPath}.gz" try { $fileStream = [System.IO.File]::OpenRead($fPath) $gzipStream = [System.IO.File]::Create($gzipfPath) $gzipWriter = [System.IO.Compression.GZipStream]::new($gzipStream, [System.IO.Compression.CompressionLevel]::SmallestSize, $false) $fileStream.CopyTo($gzipWriter) } catch { [void]($errors.TryAdd($fPath, $_.Exception.Message)) } finally { if ($null -ne $gzipWriter) { $gzipWriter.Close() } if ($null -ne $fileStream) { $fileStream.Close() } } } -ThrottleLimit $Throttle if ($prlErr.Count -gt 0) { $errFilePath = Join-Path $OutPath "CompressionErrors.json" $prlErr.GetEnumerator() | ConvertTo-Json -Depth 10 | Out-File -FilePath "$errFilePath" Write-Warning "See error details in $errFilePath" } else { Write-Information 'Compression complete' } } |