blance 32b265876c Add -AppendDate and -DateFormat parameters
Appends today's date to the remote filename before the extension
(e.g. export.txt → export_20260423.txt). Applies after -RenamePattern
if both are used. Works with -DryRun for preview.
2026-04-23 20:44:29 -05:00

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:

  1. -WinScpDllPath parameter (if provided)
  2. Same directory as the script
  3. lib\ subdirectory of the script folder
  4. C:\Program Files (x86)\WinSCP\
  5. C:\Program Files\WinSCP\
  6. 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
-AppendDate No false Append today's date to the remote filename before the extension (e.g. export.txtexport_20260423.txt)
-DateFormat No yyyyMMdd Date format string used with -AppendDate. Any valid Get-Date format accepted
-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-Clixml encrypts 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/-LocalRenameReplacement only rename the local file — the remote name is controlled by -RenamePattern/-RenameReplacement. You can use both together.

Appending the Date to Filenames

Use -AppendDate to automatically insert today's date before the file extension on the remote side. The local file is not modified.

# Upload every export.txt in all subfolders, renamed with today's date
.\Send-FilesToSftp.ps1 -LocalPath "C:\jobs" -RemotePath "/incoming" `
    -HostName "sftp.example.com" -UserName "uploader" `
    -FileFilter '^export\.txt$' -Recurse -AppendDate

Result: export.txtexport_20260423.txt

Use -DateFormat to change the format:

# Use dashes instead: export_2026-04-23.txt
.\Send-FilesToSftp.ps1 -LocalPath "C:\jobs" -RemotePath "/incoming" `
    -HostName "sftp.example.com" -UserName "uploader" `
    -FileFilter '^export\.txt$' -Recurse -AppendDate -DateFormat 'yyyy-MM-dd'

-AppendDate applies after -RenamePattern if both are used, and works with -DryRun so you can preview the result first.

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 -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:

# 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:

  1. Save credentials as the service account user:

    Get-Credential | Export-Clixml -Path "C:\secure\sftp_cred.xml"
    
  2. 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)
S
Description
No description provided
Readme 58 KiB
Languages
PowerShell 100%