Add local archive and rename-after-upload options
This commit is contained in:
+111
-10
@@ -52,8 +52,22 @@
|
||||
Replacement string for the -RenamePattern match. Supports regex capture groups ($1, ${1}, etc.).
|
||||
Use '$0' to reference the full match.
|
||||
|
||||
.PARAMETER ArchivePath
|
||||
Move successfully uploaded files to this local folder instead of leaving them in place.
|
||||
The folder will be created if it does not exist.
|
||||
Cannot be combined with -DeleteAfterTransfer.
|
||||
|
||||
.PARAMETER LocalRenamePattern
|
||||
Regex pattern to match in the filename when renaming the local file after a successful upload.
|
||||
Must be used with -LocalRenameReplacement.
|
||||
Works standalone (rename in place) or with -ArchivePath (rename while archiving).
|
||||
|
||||
.PARAMETER LocalRenameReplacement
|
||||
Replacement string for -LocalRenamePattern. Supports regex capture groups ($1, ${1}, etc.).
|
||||
|
||||
.PARAMETER DeleteAfterTransfer
|
||||
Delete local files after successful upload (move behavior).
|
||||
Cannot be combined with -ArchivePath.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Show what would be transferred without actually uploading.
|
||||
@@ -81,6 +95,25 @@
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-RenamePattern '^' -RenameReplacement 'processed_'
|
||||
|
||||
.EXAMPLE
|
||||
# Archive files to a local folder after upload
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-ArchivePath "C:\exports\sent"
|
||||
|
||||
.EXAMPLE
|
||||
# Archive and rename locally (append date) after upload
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-ArchivePath "C:\exports\sent" `
|
||||
-LocalRenamePattern '^(.+?)(\.[^.]+)$' -LocalRenameReplacement "`$1_sent`$2"
|
||||
|
||||
.EXAMPLE
|
||||
# Rename local file in place after upload (no archive)
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-LocalRenamePattern '^' -LocalRenameReplacement 'done_'
|
||||
|
||||
.EXAMPLE
|
||||
# Unattended with saved credentials and move behavior
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
@@ -131,6 +164,12 @@ param(
|
||||
|
||||
[string]$RenameReplacement,
|
||||
|
||||
[string]$ArchivePath,
|
||||
|
||||
[string]$LocalRenamePattern,
|
||||
|
||||
[string]$LocalRenameReplacement,
|
||||
|
||||
[switch]$Recurse,
|
||||
|
||||
[switch]$DeleteAfterTransfer,
|
||||
@@ -194,18 +233,34 @@ function Find-WinScpDll {
|
||||
# ── Main ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
try {
|
||||
# ── Validate rename parameters ───────────────────────────────────────
|
||||
# ── Validate parameters ──────────────────────────────────────────────
|
||||
if (($RenamePattern -and -not $RenameReplacement) -or ($RenameReplacement -and -not $RenamePattern)) {
|
||||
Write-Log "-RenamePattern and -RenameReplacement must be used together." -Level ERROR
|
||||
exit 1
|
||||
}
|
||||
if (($LocalRenamePattern -and -not $LocalRenameReplacement) -or ($LocalRenameReplacement -and -not $LocalRenamePattern)) {
|
||||
Write-Log "-LocalRenamePattern and -LocalRenameReplacement must be used together." -Level ERROR
|
||||
exit 1
|
||||
}
|
||||
if ($ArchivePath -and $DeleteAfterTransfer) {
|
||||
Write-Log "-ArchivePath and -DeleteAfterTransfer cannot be used together." -Level ERROR
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ── Create archive folder if needed ─────────────────────────────────
|
||||
if ($ArchivePath -and -not (Test-Path $ArchivePath)) {
|
||||
New-Item -ItemType Directory -Path $ArchivePath -Force | Out-Null
|
||||
Write-Log "Created archive folder: $ArchivePath"
|
||||
}
|
||||
|
||||
Write-Log "═══ SFTP Transfer Starting ═══"
|
||||
Write-Log "Local path : $LocalPath"
|
||||
Write-Log "Remote path : $RemotePath"
|
||||
Write-Log "File filter : $FileFilter"
|
||||
Write-Log "Host : ${HostName}:${Port}"
|
||||
if ($RenamePattern) { Write-Log "Rename : '$RenamePattern' → '$RenameReplacement'" }
|
||||
if ($RenamePattern) { Write-Log "Remote rename : '$RenamePattern' → '$RenameReplacement'" }
|
||||
if ($LocalRenamePattern) { Write-Log "Local rename : '$LocalRenamePattern' → '$LocalRenameReplacement'" }
|
||||
if ($ArchivePath) { Write-Log "Archive to : $ArchivePath" }
|
||||
if ($DryRun) { Write-Log "*** DRY RUN MODE - No files will be transferred ***" -Level WARN }
|
||||
|
||||
# ── Find and load WinSCP ─────────────────────────────────────────────
|
||||
@@ -260,13 +315,26 @@ try {
|
||||
if ($DryRun) {
|
||||
Write-Log "Files that would be transferred:" -Level INFO
|
||||
foreach ($f in $allFiles) {
|
||||
$destName = if ($RenamePattern) { $f.Name -replace $RenamePattern, $RenameReplacement } else { $f.Name }
|
||||
$relativePath = $f.FullName.Substring($LocalPath.TrimEnd('\').Length)
|
||||
$remoteDir = ($RemotePath.TrimEnd('/') + ($f.DirectoryName.Substring($LocalPath.TrimEnd('\').Length) -replace '\\', '/'))
|
||||
$remoteDest = "$remoteDir/$destName"
|
||||
$sizeKB = [math]::Round($f.Length / 1KB, 1)
|
||||
$renameNote = if ($RenamePattern -and $destName -ne $f.Name) { " [renamed from $($f.Name)]" } else { '' }
|
||||
Write-Log " $($f.FullName) → $remoteDest (${sizeKB} KB)$renameNote"
|
||||
$destName = if ($RenamePattern) { $f.Name -replace $RenamePattern, $RenameReplacement } else { $f.Name }
|
||||
$localFinalName = if ($LocalRenamePattern) { $f.Name -replace $LocalRenamePattern, $LocalRenameReplacement } else { $f.Name }
|
||||
$remoteDir = $RemotePath.TrimEnd('/') + ($f.DirectoryName.Substring($LocalPath.TrimEnd('\').Length) -replace '\\', '/')
|
||||
$remoteDest = "$remoteDir/$destName"
|
||||
$sizeKB = [math]::Round($f.Length / 1KB, 1)
|
||||
$remoteRenameNote = if ($RenamePattern -and $destName -ne $f.Name) { " [remote name: $destName]" } else { '' }
|
||||
Write-Log " UPLOAD : $($f.FullName) → $remoteDest (${sizeKB} KB)$remoteRenameNote"
|
||||
|
||||
if ($DeleteAfterTransfer) {
|
||||
Write-Log " LOCAL : DELETE $($f.FullName)"
|
||||
}
|
||||
elseif ($ArchivePath) {
|
||||
$archiveDest = Join-Path $ArchivePath $localFinalName
|
||||
$localNote = if ($LocalRenamePattern -and $localFinalName -ne $f.Name) { " [renamed from $($f.Name)]" } else { '' }
|
||||
Write-Log " LOCAL : MOVE → $archiveDest$localNote"
|
||||
}
|
||||
elseif ($LocalRenamePattern -and $localFinalName -ne $f.Name) {
|
||||
$renameDest = Join-Path $f.DirectoryName $localFinalName
|
||||
Write-Log " LOCAL : RENAME → $renameDest"
|
||||
}
|
||||
}
|
||||
Write-Log "DRY RUN complete. $($allFiles.Count) file(s) would be transferred." -Level SUCCESS
|
||||
exit 0
|
||||
@@ -347,12 +415,42 @@ try {
|
||||
$transferOptions = New-Object WinSCP.TransferOptions
|
||||
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
|
||||
|
||||
$result = $session.PutFiles($file.FullName, $targetPath, $DeleteAfterTransfer, $transferOptions)
|
||||
# Always upload without auto-delete; handle local disposition ourselves
|
||||
$result = $session.PutFiles($file.FullName, $targetPath, $false, $transferOptions)
|
||||
$result.Check()
|
||||
|
||||
$sizeKB = [math]::Round($file.Length / 1KB, 1)
|
||||
Write-Log "Transferred: $($file.Name) → $targetPath (${sizeKB} KB)" -Level SUCCESS
|
||||
$successCount++
|
||||
|
||||
# ── Local post-transfer disposition ──────────────────────
|
||||
$localFinalName = if ($LocalRenamePattern) { $file.Name -replace $LocalRenamePattern, $LocalRenameReplacement } else { $file.Name }
|
||||
|
||||
if ($DeleteAfterTransfer) {
|
||||
Remove-Item -LiteralPath $file.FullName -Force
|
||||
Write-Log "Deleted local: $($file.FullName)"
|
||||
}
|
||||
elseif ($ArchivePath) {
|
||||
# Preserve subdirectory structure when recursing
|
||||
$archiveDir = if ($Recurse -and $relativePath) {
|
||||
$sub = $file.DirectoryName.Substring($LocalPath.TrimEnd('\').Length).TrimStart('\')
|
||||
Join-Path $ArchivePath $sub
|
||||
} else { $ArchivePath }
|
||||
|
||||
if (-not (Test-Path $archiveDir)) {
|
||||
New-Item -ItemType Directory -Path $archiveDir -Force | Out-Null
|
||||
}
|
||||
|
||||
$archiveDest = Join-Path $archiveDir $localFinalName
|
||||
Move-Item -LiteralPath $file.FullName -Destination $archiveDest -Force
|
||||
$archiveNote = if ($localFinalName -ne $file.Name) { " (renamed from $($file.Name))" } else { '' }
|
||||
Write-Log "Archived: $($file.FullName) → $archiveDest$archiveNote"
|
||||
}
|
||||
elseif ($LocalRenamePattern -and $localFinalName -ne $file.Name) {
|
||||
$renameDest = Join-Path $file.DirectoryName $localFinalName
|
||||
Rename-Item -LiteralPath $file.FullName -NewName $localFinalName -Force
|
||||
Write-Log "Renamed local: $($file.Name) → $localFinalName"
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Log "FAILED: $($file.Name) — $($_.Exception.Message)" -Level ERROR
|
||||
@@ -373,6 +471,9 @@ try {
|
||||
if ($DeleteAfterTransfer) {
|
||||
Write-Log " Mode : MOVE (source files deleted on success)"
|
||||
}
|
||||
elseif ($ArchivePath) {
|
||||
Write-Log " Mode : ARCHIVE → $ArchivePath"
|
||||
}
|
||||
else {
|
||||
Write-Log " Mode : COPY (source files retained)"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user