Private/Folders/Get-AvmFileSnapshot.ps1
|
function Get-AvmFileSnapshot { <# .SYNOPSIS Capture the on-disk bytes of a set of files so they can be restored later. .DESCRIPTION Supports the read-only contract of drift mode (-CheckDrift) for engines whose underlying tool has no dry-run and can only report drift by rewriting the file. The engine snapshots before it runs the tool, compares afterwards to detect drift, then restores the snapshot so the caller's working copy is left byte-identical. Absent paths are recorded with a $null value so Restore-AvmFileSnapshot can delete a file the tool created. .PARAMETER Path Absolute paths to capture. Paths that do not exist are recorded as absent rather than skipped. .OUTPUTS hashtable keyed by path; values are byte[] for existing files and $null for absent ones. #> [CmdletBinding()] [OutputType([hashtable])] param( [Parameter(Mandatory)] [AllowEmptyCollection()] [string[]] $Path ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' $snapshot = @{} foreach ($item in $Path) { if (-not $item) { continue } if ($snapshot.ContainsKey($item)) { continue } if (Test-Path -LiteralPath $item -PathType Leaf) { $snapshot[$item] = [System.IO.File]::ReadAllBytes($item) } else { $snapshot[$item] = $null } } return $snapshot } function Restore-AvmFileSnapshot { <# .SYNOPSIS Restore files captured by Get-AvmFileSnapshot to their original bytes. .DESCRIPTION Rewrites every snapshotted file whose content changed, deletes files that were absent when the snapshot was taken, and deletes any extra path supplied via -CurrentPath that the snapshot does not know about (i.e. created by the tool). Files whose bytes are already identical are left untouched so timestamps stay stable. Restoration is best-effort per file: a failure to restore one file does not abort the rest, because this runs from a finally block where the engine may already be unwinding a tool error. .PARAMETER Snapshot Hashtable produced by Get-AvmFileSnapshot. .PARAMETER CurrentPath Optional list of paths that exist now. Any entry absent from the snapshot is treated as tool-created and removed. .OUTPUTS None. #> [CmdletBinding()] [OutputType([void])] param( [Parameter(Mandatory)] [hashtable] $Snapshot, [AllowEmptyCollection()] [string[]] $CurrentPath = @() ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' foreach ($item in $Snapshot.Keys) { try { $original = $Snapshot[$item] $exists = Test-Path -LiteralPath $item -PathType Leaf if ($null -eq $original) { if ($exists) { Remove-Item -LiteralPath $item -Force } continue } if ($exists) { $current = [System.IO.File]::ReadAllBytes($item) if ([System.Linq.Enumerable]::SequenceEqual([byte[]]$current, [byte[]]$original)) { continue } } else { $parent = Split-Path -Parent $item if ($parent -and -not (Test-Path -LiteralPath $parent -PathType Container)) { $null = New-Item -ItemType Directory -Path $parent -Force } } [System.IO.File]::WriteAllBytes($item, $original) } catch { Write-Verbose ("Avm: could not restore '{0}': {1}" -f $item, $_.Exception.Message) } } foreach ($item in $CurrentPath) { if (-not $item -or $Snapshot.ContainsKey($item)) { continue } try { if (Test-Path -LiteralPath $item -PathType Leaf) { Remove-Item -LiteralPath $item -Force } } catch { Write-Verbose ("Avm: could not remove '{0}': {1}" -f $item, $_.Exception.Message) } } } |