# 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](https://winscp.net/eng/downloads.php), 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 ```powershell # 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 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 | | `-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) ```powershell $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) ```powershell # 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 ```powershell .\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 ```powershell .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` -HostName "sftp.example.com" -UserName "uploader" -FileFilter '\.csv$' ``` ### Move files (delete after upload) with logging ```powershell .\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 ```powershell .\Send-FilesToSftp.ps1 -LocalPath "C:\exports" -RemotePath "/incoming" ` -HostName "sftp.example.com" -UserName "uploader" -DryRun ``` ### Recursive with subdirectories ```powershell .\Send-FilesToSftp.ps1 -LocalPath "C:\data\projects" -RemotePath "/archive" ` -HostName "sftp.example.com" -UserName "uploader" ` -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: ```powershell # 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: ```powershell Get-Credential | Export-Clixml -Path "C:\secure\sftp_cred.xml" ``` 2. **Create the scheduled task:** ```powershell $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) ```