10 KiB
Send-FilesToSftp.ps1
PowerShell script to transfer files to an SFTP server with regex filtering, secure credential handling, and logging.
Prerequisites
WinSCP .NET Assembly (WinSCPnet.dll) is required. Install using one of these methods:
| Method | Steps |
|---|---|
| Drop-in | Download the .NET assembly package, extract WinSCPnet.dll next to the script |
| NuGet | Install-Package WinSCP -Source nuget.org |
| WinSCP Installer | Install WinSCP and check the ".NET assembly" option during setup |
The script auto-searches these locations in order:
-WinScpDllPathparameter (if provided)- Same directory as the script
lib\subdirectory of the script folderC:\Program Files (x86)\WinSCP\C:\Program Files\WinSCP\- NuGet package cache (
~\.nuget\packages\winscp)
Quick Start
# Basic usage — prompted for password interactively
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
-HostName "sftp.example.com" -UserName "uploader"
Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
-LocalPath |
Yes | — | Local source folder to scan |
-RemotePath |
Yes | — | Remote SFTP destination folder |
-HostName |
Yes | — | SFTP server hostname or IP |
-UserName |
Yes | — | SFTP username |
-FileFilter |
No | .* |
Regex pattern to match filenames |
-Port |
No | 22 |
SFTP port |
-Credential |
No | — | PSCredential object |
-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 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. 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 |
Authentication
The script supports three auth methods, checked in this order:
1. Credential Object (interactive or pipeline)
$cred = Get-Credential
.\Send-FilesToSftp.ps1 -LocalPath "C:\data" -RemotePath "/uploads" `
-HostName "sftp.example.com" -UserName "brad" -Credential $cred
2. Saved Credential File (unattended / scheduled tasks)
# One-time setup — run as the same user that will run the scheduled task
Get-Credential | Export-Clixml -Path "C:\secure\sftp_cred.xml"
# Use in script
.\Send-FilesToSftp.ps1 -LocalPath "C:\data" -RemotePath "/uploads" `
-HostName "sftp.example.com" -UserName "svc_upload" `
-CredentialFile "C:\secure\sftp_cred.xml"
Note:
Export-Clixmlencrypts credentials using Windows DPAPI, tied to the user account and machine that created the file. Only that same user on that same machine can decrypt it.
3. SSH Private Key
.\Send-FilesToSftp.ps1 -LocalPath "C:\data" -RemotePath "/uploads" `
-HostName "sftp.example.com" -UserName "svc_upload" `
-KeyFilePath "C:\keys\id_rsa.ppk"
4. Interactive Prompt
If none of the above are provided and no key file is specified, you'll be prompted for a password at runtime.
File Filtering (Regex)
The -FileFilter parameter uses PowerShell regex (case-insensitive by default).
| Filter | Matches |
|---|---|
'\.csv$' |
All CSV files |
'\.xlsx?$' |
.xls and .xlsx files |
'^report_\d{8}' |
Files starting with report_ + 8 digits (e.g. report_20260415.csv) |
'(?-i)^Data' |
Files starting with Data (case-sensitive) |
'badge.*\.xlsx$' |
Excel files with badge anywhere in the name |
'\.(csv|txt)$' |
CSV or TXT files |
'.*' |
Everything (default) |
Usage Examples
Transfer all CSVs
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
-HostName "sftp.example.com" -UserName "uploader" -FileFilter '\.csv$'
Move files (delete after upload) with logging
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
-HostName "sftp.example.com" -UserName "svc_upload" `
-CredentialFile "C:\secure\cred.xml" `
-DeleteAfterTransfer -LogFile "C:\logs\sftp_transfer.log"
Dry run — preview without transferring
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
-HostName "sftp.example.com" -UserName "uploader" -DryRun
Recursive with subdirectories
.\Send-FilesToSftp.ps1 -LocalPath "C:\data\projects" -RemotePath "/archive" `
-HostName "sftp.example.com" -UserName "uploader" `
-Recurse -FileFilter '\.pdf$'
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
.\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)
.\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)
.\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" `
-HostName "sftp.example.com" -UserName "uploader" `
-LocalRenamePattern '^' -LocalRenameReplacement 'done_'
-LocalRenamePattern/-LocalRenameReplacementonly 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.
| 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
$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
-DryRunfirst 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:
# Get the fingerprint from the server
ssh-keyscan sftp.example.com | ssh-keygen -lf -
# Use it in the script
.\Send-FilesToSftp.ps1 -LocalPath "C:\data" -RemotePath "/uploads" `
-HostName "sftp.example.com" -UserName "uploader" `
-SshHostKeyFingerprint "ssh-rsa 2048 aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99"
Pass "*" to accept any key (development/testing only — not recommended for production).
Scheduled Task Setup
To run unattended via Windows Task Scheduler:
-
Save credentials as the service account user:
Get-Credential | Export-Clixml -Path "C:\secure\sftp_cred.xml" -
Create the scheduled task:
$action = New-ScheduledTaskAction ` -Execute "powershell.exe" ` -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\scripts\Send-FilesToSftp.ps1" -LocalPath "C:\exports" -RemotePath "/incoming" -HostName "sftp.example.com" -UserName "svc_upload" -CredentialFile "C:\secure\sftp_cred.xml" -FileFilter "\.csv$" -DeleteAfterTransfer -LogFile "C:\logs\sftp.log"' $trigger = New-ScheduledTaskTrigger -Daily -At "2:00AM" Register-ScheduledTask -TaskName "SFTP Upload" ` -Action $action -Trigger $trigger ` -User "DOMAIN\svc_upload" -Password "****" ` -RunLevel Highest
Exit Codes
| Code | Meaning |
|---|---|
0 |
All files transferred (or no files matched filter) |
1 |
One or more failures occurred |
Log Output
Logs include timestamps and severity levels. Sample output:
[2026-04-16 14:30:01] [INFO] ═══ SFTP Transfer Starting ═══
[2026-04-16 14:30:01] [INFO] Local path : C:\exports
[2026-04-16 14:30:01] [INFO] Remote path : /incoming
[2026-04-16 14:30:01] [INFO] File filter : \.csv$
[2026-04-16 14:30:01] [INFO] Found 3 file(s) matching filter
[2026-04-16 14:30:02] [SUCCESS] Connected to sftp.example.com
[2026-04-16 14:30:03] [SUCCESS] Transferred: report_20260415.csv → /incoming/report_20260415.csv (42.3 KB)
[2026-04-16 14:30:03] [SUCCESS] Transferred: badges_export.csv → /incoming/badges_export.csv (18.1 KB)
[2026-04-16 14:30:04] [SUCCESS] Transferred: access_log.csv → /incoming/access_log.csv (7.8 KB)
[2026-04-16 14:30:04] [INFO] ═══ Transfer Complete ═══
[2026-04-16 14:30:04] [INFO] Succeeded : 3
[2026-04-16 14:30:04] [INFO] Mode : COPY (source files retained)