Public/Backup.ps1
|
using namespace System.Collections using namespace System.Collections.Concurrent using namespace System.IO.Compression using namespace System.IO Set-StrictMode -Version Latest Set-Alias -Name tp -Value Test-Path Set-Alias -Name nuit -Value New-Item #### ## Backup-FilesParallel function Backup-FilesParallel { #### 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.* #### [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Path, [Parameter(Mandatory = $true)] [string]$OutPath, [Parameter(Mandatory = $false)] [int]$Throttle = 4 ) #### - Displays time elapsed when run interactively in the terminal #### - OutPath is created on demand #### - @ToDo The termination of this function is not good at all. Find a better way to complete the job. $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 ### - `ConcurrentDictionary[string,string]` for holding errors $prlErr = [ConcurrentDictionary[string, string]]::new() [ArrayList]$mtxProg = [ArrayList]::Synchronized(@(0)) $startTime = Get-Date gci $Path -Recurse -File -ErrorAction SilentlyContinue | % -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 { #### ```powershell #### # Uses #### [System.IO.Compression.GZipStream] #### [System.IO.Compression.CompressionLevel]::SmallestSize #### ``` #### $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' } #### **Returns** #### - *None. Writes .gz files to OutPath and a CompressionErrors.json on partial failure.* #### #### **Throws** #### - *When Path does not exist.* } |