From 6a451044dd6e6fa43e0ffd50b10879e555dd9724 Mon Sep 17 00:00:00 2001 From: Blance Date: Thu, 16 Apr 2026 21:34:20 -0500 Subject: [PATCH] Add -RenamePattern/-RenameReplacement for renaming files before upload --- README.md | 26 +++++++++++++++++++++++++ Send-FilesToSftp.ps1 | 46 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a31551c..7eb66df 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ 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 | +| `-RenameReplacement` | No | — | Replacement string for `-RenamePattern` (supports capture groups like `$1`) | | `-Recurse` | No | `false` | Scan subdirectories | | `-DeleteAfterTransfer` | No | `false` | Delete local files after successful upload | | `-DryRun` | No | `false` | Preview transfers without uploading | @@ -125,6 +127,30 @@ The `-FileFilter` parameter uses PowerShell regex (case-insensitive by default). -Recurse -FileFilter '\.pdf$' ``` +## Renaming Files Before Upload + +Use `-RenamePattern` (regex) and `-RenameReplacement` together to rename files on the remote side without touching the local files. + +| Goal | Pattern | Replacement | +|------|---------|-------------| +| Add date before extension | `'^(.+?)(\.[^.]+)$'` | `'${1}_20260416${2}'` | +| Add prefix | `'^'` | `'processed_'` | +| Add suffix before extension | `'^(.+?)(\.[^.]+)$'` | `'${1}_done${2}'` | +| Strip `_draft` from name | `'_draft'` | `''` | +| Replace spaces with underscores | `' '` | `'_'` | + +Rename uses PowerShell's `-replace` operator (regex, case-insensitive). The local file is **not** modified — only the remote destination path changes. + +### Add today's date to every filename +```powershell +$date = Get-Date -Format 'yyyyMMdd' +.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` + -HostName "sftp.example.com" -UserName "uploader" ` + -RenamePattern '^(.+?)(\.[^.]+)$' -RenameReplacement "${date}_`$1`$2" +``` + +> **Tip:** Use `-DryRun` first to preview the renamed remote paths before committing to the transfer. + ## SSH Host Key Fingerprint For production use, always provide the host key fingerprint to prevent MITM attacks: diff --git a/Send-FilesToSftp.ps1 b/Send-FilesToSftp.ps1 index 53f46e1..dcaa74d 100644 --- a/Send-FilesToSftp.ps1 +++ b/Send-FilesToSftp.ps1 @@ -43,6 +43,15 @@ .PARAMETER Recurse Scan subdirectories in LocalPath. +.PARAMETER RenamePattern + Regex pattern to match in the filename for renaming before upload. + Must be used together with -RenameReplacement. + Supports capture groups (e.g. '(.+)\.csv$' with replacement '$1_processed.csv'). + +.PARAMETER RenameReplacement + Replacement string for the -RenamePattern match. Supports regex capture groups ($1, ${1}, etc.). + Use '$0' to reference the full match. + .PARAMETER DeleteAfterTransfer Delete local files after successful upload (move behavior). @@ -60,6 +69,18 @@ .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` -HostName "sftp.example.com" -UserName "uploader" -FileFilter '\.csv$' +.EXAMPLE + # Rename files by appending today's date before the extension + .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` + -HostName "sftp.example.com" -UserName "uploader" ` + -RenamePattern '^(.+?)(\.[^.]+)$' -RenameReplacement "`$1_$(Get-Date -Format 'yyyyMMdd')`$2" + +.EXAMPLE + # Add a prefix to every uploaded file + .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` + -HostName "sftp.example.com" -UserName "uploader" ` + -RenamePattern '^' -RenameReplacement 'processed_' + .EXAMPLE # Unattended with saved credentials and move behavior .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` @@ -106,6 +127,10 @@ param( [string]$KeyFilePath, + [string]$RenamePattern, + + [string]$RenameReplacement, + [switch]$Recurse, [switch]$DeleteAfterTransfer, @@ -169,11 +194,18 @@ function Find-WinScpDll { # ── Main ───────────────────────────────────────────────────────────────────── try { + # ── Validate rename parameters ─────────────────────────────────────── + if (($RenamePattern -and -not $RenameReplacement) -or ($RenameReplacement -and -not $RenamePattern)) { + Write-Log "-RenamePattern and -RenameReplacement must be used together." -Level ERROR + exit 1 + } + 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 ($DryRun) { Write-Log "*** DRY RUN MODE - No files will be transferred ***" -Level WARN } # ── Find and load WinSCP ───────────────────────────────────────────── @@ -228,10 +260,13 @@ 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) - $remoteDest = ($RemotePath.TrimEnd('/') + $relativePath) -replace '\\', '/' + $remoteDir = ($RemotePath.TrimEnd('/') + ($f.DirectoryName.Substring($LocalPath.TrimEnd('\').Length) -replace '\\', '/')) + $remoteDest = "$remoteDir/$destName" $sizeKB = [math]::Round($f.Length / 1KB, 1) - Write-Log " $($f.FullName) → $remoteDest (${sizeKB} KB)" + $renameNote = if ($RenamePattern -and $destName -ne $f.Name) { " [renamed from $($f.Name)]" } else { '' } + Write-Log " $($f.FullName) → $remoteDest (${sizeKB} KB)$renameNote" } Write-Log "DRY RUN complete. $($allFiles.Count) file(s) would be transferred." -Level SUCCESS exit 0 @@ -294,8 +329,13 @@ try { $relativePath = $file.DirectoryName.Substring($LocalPath.TrimEnd('\').Length) -replace '\\', '/' } + $destName = if ($RenamePattern) { $file.Name -replace $RenamePattern, $RenameReplacement } else { $file.Name } $targetDir = $RemotePath.TrimEnd('/') + $relativePath - $targetPath = "$targetDir/$($file.Name)" + $targetPath = "$targetDir/$destName" + + if ($RenamePattern -and $destName -ne $file.Name) { + Write-Log "Renaming: $($file.Name) → $destName" + } try { # Ensure subdirectory exists on remote when recursing