PowerShell FTP and SFTP: Transfer Files with WinSCP

Daily file exchange with vendors via SFTP is a five-minute manual ritual that should be a zero-touch scheduled job. Automating PowerShell SFTP FTP file transfer is straightforward with either the WinSCP .NET assembly or the Posh-SSH module — both provide full upload, download, and synchronization capabilities from PowerShell scripts. This post covers both approaches with practical examples for the most common transfer scenarios including key-based authentication and error handling.
WinSCP .NET Assembly Setup
WinSCP is a free, open-source SFTP/FTP client that ships with a .NET assembly usable from PowerShell. Download WinSCP from winscp.net and note the installation path. Load the assembly before use:
# Load the WinSCP .NET assembly — adjust path to your WinSCP installation
$winscpDll = "C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
if (-not (Test-Path $winscpDll)) {
throw "WinSCP .NET assembly not found at $winscpDll"
}
Add-Type -Path $winscpDll
# Create session options
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "sftp.vendor.com"
UserName = "ftpuser"
Password = "s3cret"
SshHostKeyFingerprint = "ssh-rsa 2048 xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
}
Upload a File via SFTP
Open a WinSCP session, transfer the file, and close the session cleanly in a try/finally block:
$session = New-Object WinSCP.Session
try {
$session.Open($sessionOptions)
$transferOptions = New-Object WinSCP.TransferOptions
$transferOptions.TransferMode = [WinSCP.TransferMode]::Binary
$result = $session.PutFiles(
"C:\Exports\data-$(Get-Date -Format 'yyyyMMdd').csv",
"/incoming/",
$false, # do not delete local file
$transferOptions
)
$result.Check() # throws on any transfer failure
Write-Host "Upload complete: $($result.Transfers.Count) file(s)"
}
catch {
Write-Error "SFTP upload failed: $($_.Exception.Message)"
throw
}
finally {
$session.Dispose()
}
Download Files from SFTP
Download all files matching a pattern from a remote directory to a local folder:
$localDir = "C:\Downloads\VendorFiles"
$remoteDir = "/outgoing/*.csv"
if (-not (Test-Path $localDir)) { New-Item -Path $localDir -ItemType Directory | Out-Null }
$session = New-Object WinSCP.Session
try {
$session.Open($sessionOptions)
$result = $session.GetFiles($remoteDir, "$localDir\", $false)
$result.Check()
Write-Host "Downloaded $($result.Transfers.Count) file(s) to $localDir"
$result.Transfers | ForEach-Object { Write-Host " $($_.FileName)" }
}
finally {
$session.Dispose()
}
Sync a Local Folder to SFTP
WinSCP’s SynchronizeDirectories method uploads only new or changed files — ideal for daily batch jobs where you want to push incremental changes:
$session = New-Object WinSCP.Session
try {
$session.Open($sessionOptions)
$syncResult = $session.SynchronizeDirectories(
[WinSCP.SynchronizationMode]::Remote, # local to remote
"C:\Reports", # local path
"/reports", # remote path
$false # don't delete remote files not in local
)
$syncResult.Check()
Write-Host "Sync complete:"
Write-Host " Uploaded: $($syncResult.Uploads.Count)"
Write-Host " Removed: $($syncResult.Removals.Count)"
}
finally {
$session.Dispose()
}
Key-Based Authentication
For automated scripts, SSH key authentication is more secure than passwords. Generate a key pair, upload the public key to the SFTP server, and reference the private key file in the session options:
# Key-based authentication — no password needed
$sessionOptions = New-Object WinSCP.SessionOptions -Property @{
Protocol = [WinSCP.Protocol]::Sftp
HostName = "sftp.vendor.com"
UserName = "ftpuser"
SshPrivateKeyPath = "C:\Keys\vendor_rsa.ppk" # PuTTY format
SshHostKeyFingerprint = "ssh-rsa 2048 xx:xx:xx:xx:xx:xx"
}
# If the private key has a passphrase:
# $sessionOptions.PrivateKeyPassphrase = "keypassphrase"
Error Handling for Failed Transfers
WinSCP’s $result.Check() throws an exception if any transfer failed. Catch it and log the specific file failures:
$session = New-Object WinSCP.Session
try {
$session.Open($sessionOptions)
$result = $session.PutFiles("C:\Exports\*.csv", "/incoming/", $false)
foreach ($transfer in $result.Transfers) {
if ($transfer.Error -ne $null) {
Write-Warning "Failed: $($transfer.FileName) — $($transfer.Error.Message)"
} else {
Write-Host "OK: $($transfer.FileName)"
}
}
}
catch {
Write-Error "Session failed: $($_.Exception.Message)"
}
finally {
$session.Dispose()
}
Common Errors and Fixes
-
Host key verification failure on first connection. WinSCP refuses to connect to a server whose host key fingerprint is unknown. On first connection, get the fingerprint interactively from WinSCP’s GUI and record it in
SshHostKeyFingerprint. Never useGiveUpSecurityAndAcceptAnySshHostKey = $truein production. -
WinSCP path and .NET assembly path must be correct. The
Add-Type -Pathcall fails silently or throws an error if the path is wrong or the WinSCP installation is a different version than expected. Verify the path withTest-Pathbefore loading, and pin the WinSCP version in your deployment to avoid assembly version mismatches.
Related Cmdlets / See Also
Wrapping Up
WinSCP’s .NET assembly is the most capable and reliable SFTP automation option for PowerShell. Always open and dispose sessions in a try/finally block, use key-based authentication for unattended scripts, call $result.Check() to surface transfer errors, and schedule the script as a Task Scheduler job for fully automated file exchange.


