src/Private/Core/Copy-FotonHostFile.ps1
|
function Copy-FotonHostFile { <# Copy a live host file into the case package. Opens the source with FileShare.ReadWrite so Superfetch / Amcache / SRUM locks do not force an in-place read. Parse must use the copy, never the live path. Status is a fact about the attempt (Copied / Missing / CopyError). #> param( [Parameter(Mandatory)][string]$SourcePath, [Parameter(Mandatory)][string]$DestinationPath ) $row = [PSCustomObject]@{ SourcePath = $SourcePath DestName = [IO.Path]::GetFileName($DestinationPath) SizeBytes = 0 SHA256 = '' Status = 'CopyError' Note = '' } if ([string]::IsNullOrWhiteSpace($SourcePath) -or -not (Test-Path -LiteralPath $SourcePath -PathType Leaf)) { $row.Status = 'Missing' $row.Note = 'Source leaf not found. Visibility gap, not proof the artifact never existed on the host.' return $row } $src = $null $dst = $null try { $destDir = Split-Path -Parent $DestinationPath if ($destDir -and -not (Test-Path -LiteralPath $destDir)) { New-Item -Path $destDir -ItemType Directory -Force | Out-Null } $share = [System.IO.FileShare]::ReadWrite $src = [System.IO.File]::Open($SourcePath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, $share) $dst = [System.IO.File]::Create($DestinationPath) $src.CopyTo($dst) $dst.Flush() $dst.Dispose() $dst = $null $src.Dispose() $src = $null $item = Get-Item -LiteralPath $DestinationPath -Force -ErrorAction Stop $row.SizeBytes = $item.Length $row.SHA256 = Get-FotonSafeHash -Path $DestinationPath $row.Status = 'Copied' $row.Note = 'Copied with FileShare.ReadWrite. Do not parse the live path.' } catch { $row.Status = 'CopyError' $row.Note = $_.Exception.Message try { if (Test-Path -LiteralPath $DestinationPath -PathType Leaf) { Remove-Item -LiteralPath $DestinationPath -Force -ErrorAction SilentlyContinue } } catch { } } finally { if ($null -ne $dst) { try { $dst.Dispose() } catch { } } if ($null -ne $src) { try { $src.Dispose() } catch { } } } return $row } |