Add local archive and rename-after-upload options
此提交包含在:
@@ -42,10 +42,13 @@ The script auto-searches these locations in order:
|
||||
| `-CredentialFile` | No | — | Path to saved credential XML (see below) |
|
||||
| `-KeyFilePath` | No | — | Path to SSH private key (`.ppk`) |
|
||||
| `-SshHostKeyFingerprint` | No | — | SSH host key fingerprint for verification |
|
||||
| `-RenamePattern` | No | — | Regex pattern to match in filename for renaming before upload |
|
||||
| `-RenamePattern` | No | — | Regex pattern to match in filename for renaming on the **remote** side before upload |
|
||||
| `-RenameReplacement` | No | — | Replacement string for `-RenamePattern` (supports capture groups like `$1`) |
|
||||
| `-ArchivePath` | No | — | Move successfully uploaded files to this local folder (created if missing). Cannot combine with `-DeleteAfterTransfer` |
|
||||
| `-LocalRenamePattern` | No | — | Regex pattern to rename the **local** file after upload (in place, or into `-ArchivePath`) |
|
||||
| `-LocalRenameReplacement` | No | — | Replacement string for `-LocalRenamePattern` (supports capture groups like `$1`) |
|
||||
| `-Recurse` | No | `false` | Scan subdirectories |
|
||||
| `-DeleteAfterTransfer` | No | `false` | Delete local files after successful upload |
|
||||
| `-DeleteAfterTransfer` | No | `false` | Delete local files after successful upload. Cannot combine with `-ArchivePath` |
|
||||
| `-DryRun` | No | `false` | Preview transfers without uploading |
|
||||
| `-LogFile` | No | — | Path to log file (logs to console if omitted) |
|
||||
| `-WinScpDllPath` | No | — | Explicit path to `WinSCPnet.dll` |
|
||||
@@ -127,7 +130,35 @@ The `-FileFilter` parameter uses PowerShell regex (case-insensitive by default).
|
||||
-Recurse -FileFilter '\.pdf$'
|
||||
```
|
||||
|
||||
## Renaming Files Before Upload
|
||||
## Local Archive & Rename After Upload
|
||||
|
||||
After a successful upload you can archive or rename the local source file. These are mutually exclusive with `-DeleteAfterTransfer`.
|
||||
|
||||
### Archive to a folder
|
||||
```powershell
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-ArchivePath "C:\exports\sent"
|
||||
```
|
||||
|
||||
### Archive and rename while archiving (e.g. add `_sent` before extension)
|
||||
```powershell
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-ArchivePath "C:\exports\sent" `
|
||||
-LocalRenamePattern '^(.+?)(\.[^.]+)$' -LocalRenameReplacement '${1}_sent${2}'
|
||||
```
|
||||
|
||||
### Rename local file in place (no archive, no delete)
|
||||
```powershell
|
||||
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
|
||||
-HostName "sftp.example.com" -UserName "uploader" `
|
||||
-LocalRenamePattern '^' -LocalRenameReplacement 'done_'
|
||||
```
|
||||
|
||||
> `-LocalRenamePattern`/`-LocalRenameReplacement` only rename the local file — the remote name is controlled by `-RenamePattern`/`-RenameReplacement`. You can use both together.
|
||||
|
||||
## Renaming Files on the Remote Side
|
||||
|
||||
Use `-RenamePattern` (regex) and `-RenameReplacement` together to rename files on the remote side without touching the local files.
|
||||
|
||||
|
||||
+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)"
|
||||
}
|
||||
|
||||
新增問題並參考
封鎖使用者