192 lines
7.2 KiB
Markdown
192 lines
7.2 KiB
Markdown
|
|
# 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 |
|
||
|
|
| `-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$'
|
||
|
|
```
|
||
|
|
|
||
|
|
## 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)
|
||
|
|
```
|