Files.comExaVault

PowerShell SFTP: Three Practical Ways to Automate File Transfers on Windows

SFTP & SSH

PowerShell has no SFTP cmdlets of its own, so a script either borrows them from the Posh-SSH module or the OpenSSH client built into Windows, or it hands the SFTP connection to Files.com and never speaks the protocol at all. Here is working code for all three ways, plus the parts the snippets usually skip: SSH keys, host verification, credentials on a scheduled task, and making a failed transfer fail loudly.

A surprising number of important business processes still begin with someone opening an SFTP client, dragging a file into a folder, and hoping they remember to do it again tomorrow. A PowerShell SFTP script ends that ritual.

PowerShell can automate the work, but it does not include native SFTP cmdlets. On Windows, you generally have three good options: Posh-SSH for PowerShell-native SFTP commands, Windows OpenSSH for a lightweight, zero-install approach, and letting Files.com make the SFTP connection for you, so the script never speaks SFTP at all.

This guide covers all three. More importantly, it covers the details that determine whether an unattended transfer will still be working six months from now: SSH keys, host verification, scheduled-task credentials, exit codes, cleanup, and logging.

Option 1: Use Posh-SSH for PowerShell-native SFTP

Posh-SSH is an open-source PowerShell module that provides SSH and SFTP cmdlets. It is a good fit when you want to combine file transfers with PowerShell logic such as loops, filtering, validation, notifications, or retries.

Install it from the PowerShell Gallery:

Install-Module -Name Posh-SSH -Scope CurrentUser

If the script will run under a dedicated service account, install the module for that account or use -Scope AllUsers from an elevated PowerShell session.

Upload a file with a username and password

The following example opens an SFTP session, uploads a file, and closes the session:

$credential = Get-Credential -UserName "alice" -Message "Enter the SFTP password"

$session = New-SFTPSession `
    -ComputerName "sftp.example.com" `
    -Credential $credential `
    -AcceptKey

Set-SFTPItem `
    -SFTPSession $session `
    -Path "C:\exports\orders.csv" `
    -Destination "/inbound" `
    -Force

Remove-SFTPSession -SFTPSession $session | Out-Null

Three cmdlets do the work. New-SFTPSession opens the connection; SFTP normally uses port 22, so add -Port 2222 if your server uses a different one. Set-SFTPItem uploads a local file or directory, and the -Force switch allows an existing destination file to be overwritten. Remove-SFTPSession closes the connection.

List and download remote files

Once a session is open, you can inspect a directory or download a file:

Get-SFTPChildItem `
    -SFTPSession $session `
    -Path "/outbound"

Get-SFTPItem `
    -SFTPSession $session `
    -Path "/outbound/report.csv" `
    -Destination "C:\imports" `
    -Force

That makes Posh-SSH useful for the jobs a batch file cannot express: downloading every file in an outbound folder, processing only files with a particular extension, archiving files after a successful download, or checking a file's size or existence before starting the next job.

Use an SSH key for unattended transfers

Passwords are inconvenient for scheduled jobs and often conflict with multifactor-authentication policies. SSH keys are usually the better choice.

Pass the private key path to New-SFTPSession with -KeyFile:

$emptyPassword = New-Object System.Security.SecureString
$credential = [PSCredential]::new("alice", $emptyPassword)

$session = New-SFTPSession `
    -ComputerName "sftp.example.com" `
    -Credential $credential `
    -KeyFile "C:\keys\sftp_ed25519"

Posh-SSH expects an OpenSSH-compatible private key. That is the format produced by ssh-keygen on current versions of Windows.

If the key is protected by a passphrase, put that passphrase in the password field of the credential:

$keyPassphrase = Read-Host "Private-key passphrase" -AsSecureString
$credential = [PSCredential]::new("alice", $keyPassphrase)

$session = New-SFTPSession `
    -ComputerName "sftp.example.com" `
    -Credential $credential `
    -KeyFile "C:\keys\sftp_ed25519"

For more background, see our guides to setting up SSH keys and importing SSH keys into WinSCP.

Always close the session

The simple example works, but it has a flaw: if the upload throws an exception, the cleanup line may never run.

Use try and finally so the session is closed whether the transfer succeeds or fails:

$session = $null

try {
    $session = New-SFTPSession `
        -ComputerName "sftp.example.com" `
        -Credential $credential `
        -KeyFile "C:\keys\sftp_ed25519"

    Set-SFTPItem `
        -SFTPSession $session `
        -Path "C:\exports\orders.csv" `
        -Destination "/inbound" `
        -Force
}
finally {
    if ($null -ne $session) {
        Remove-SFTPSession -SFTPSession $session | Out-Null
    }
}

For an unattended job, also make PowerShell treat non-terminating errors as failures:

$ErrorActionPreference = "Stop"

Put that near the top of the script.

Do not blindly accept host keys

The -AcceptKey switch is convenient, but don't let it become a permanent shortcut. It tells Posh-SSH to trust the host key presented by the server.

A host key proves that you are connecting to the expected server rather than another system impersonating it. Before accepting a server for the first time, compare its fingerprint with one supplied through a trusted channel by the server administrator.

A sensible first-connection process is:

  1. Obtain the expected host-key fingerprint from the administrator.
  2. Connect and verify that the presented fingerprint matches.
  3. Accept and save the key.
  4. Remove -AcceptKey from the production script.

Posh-SSH stores trusted-host information per user. That matters when a script moves from your interactive account to a service account: the service account may not yet trust the server.

Option 2: Use the OpenSSH client included with Windows

Current releases of Windows 10 and Windows 11 include Microsoft's OpenSSH client as an optional Windows capability. On many systems, sftp.exe is already available.

Check from PowerShell:

Get-Command sftp.exe

If it is installed, you can automate transfers without adding a PowerShell module.

Create an SFTP batch file

The OpenSSH client can read commands from a text file. For example, save the following as C:\jobs\upload.sftp:

put C:\exports\orders.csv /inbound/orders.csv
bye

Run it from PowerShell:

& sftp.exe `
    -b "C:\jobs\upload.sftp" `
    -i "C:\keys\sftp_ed25519" `
    -o BatchMode=yes `
    "alice@sftp.example.com"

if ($LASTEXITCODE -ne 0) {
    throw "SFTP upload failed with exit code $LASTEXITCODE"
}

-b supplies the batch file, while -i identifies the private key. -o BatchMode=yes prevents the client from falling back to an interactive password prompt.

For a nonstandard port, use uppercase -P:

& sftp.exe `
    -P 2222 `
    -b "C:\jobs\upload.sftp" `
    -i "C:\keys\sftp_ed25519" `
    -o BatchMode=yes `
    "alice@sftp.example.com"

Be careful: OpenSSH uses lowercase -p for a different purpose. The SFTP port option is uppercase -P.

Verify the server's host key

OpenSSH stores trusted server keys in the user's known_hosts file, normally located at:

%USERPROFILE%\.ssh\known_hosts

Connect interactively once as the account that will run the scheduled job:

sftp -i C:\keys\sftp_ed25519 alice@sftp.example.com

Compare the displayed fingerprint with the expected fingerprint before answering yes.

Do not disable verification with options such as StrictHostKeyChecking=no in a production job. Doing so removes an important protection against connecting to the wrong server.

When OpenSSH is the right choice

The built-in client is ideal when you cannot install additional PowerShell modules, the transfer is simple and predictable, you only need a few put, get, rename, or mkdir commands, and you are comfortable handling the surrounding logic in PowerShell.

Its main limitation is that the SFTP batch language is intentionally simple. Conditions, date filtering, notifications, and retry policies must live in the PowerShell wrapper.

For example, PowerShell can generate a batch file dynamically:

$files = Get-ChildItem "C:\exports\*.csv"

$commands = foreach ($file in $files) {
    'put "{0}" "/inbound/{1}"' -f $file.FullName, $file.Name
}

$commands += "bye"
$commands | Set-Content "C:\jobs\upload-generated.sftp"

& sftp.exe `
    -b "C:\jobs\upload-generated.sftp" `
    -i "C:\keys\sftp_ed25519" `
    -o BatchMode=yes `
    "alice@sftp.example.com"

if ($LASTEXITCODE -ne 0) {
    throw "SFTP upload failed with exit code $LASTEXITCODE"
}

Option 3: Let Files.com handle the SFTP connection

The first two approaches put the entire SFTP stack inside your PowerShell script. You need a module or client, a private key on the machine, host-key verification, outbound access on port 22, and your own retry logic.

There is another approach: take SFTP out of the script.

With Files.com, the SFTP connection runs in the cloud. Files.com connects to your partner's server, while your PowerShell script communicates only with Files.com over HTTPS.

The key is a Remote Server Mount. You configure the partner's SFTP server once, then mount it as a folder on your Files.com site, for example /partners/brightway.

That folder acts as a live view of the partner's server. Upload a file, create a directory, list its contents, or delete an item, and the operation is passed through to the remote server in real time. Files.com does not retain a separate copy of the mounted data.

Configure the SFTP mount once

In the Files.com web app, add an SFTP Remote Server and enter the partner's hostname, port, and authentication details. Files.com supports a username and password, an SSH private key, or both together.

If the partner requires key-based authentication, Files.com can generate a 4096-bit RSA key pair. You send the public key to the partner's SFTP administrator, while the private key remains inside Files.com.

You can also supply an existing key in OpenSSH, PuTTY (.ppk), or SSH2 format. Passwords and private keys are stored encrypted, and the shared credential manager lets you reuse a credential without entering it separately for every connection.

Host-key verification is built in as well. On the first connection, Files.com can detect and store the server's host key, or you can enter a fingerprint supplied by the partner. If the host key changes later, Files.com disables the connection until an administrator approves the new key.

That gives you the protection that options such as -AcceptKey bypass, without having to implement host-key management in every script.

On the partner's side, the main requirement is to allowlist the relevant Files.com IP addresses. Once that is done, mount the server on an empty Files.com folder and the SFTP setup for that partner is complete.

Transfer files from PowerShell over HTTPS

After the mount is configured, PowerShell no longer needs to make an SFTP connection.

Install the Files.com CLI by downloading files-cli.exe from the GitHub releases page. Use the amd64 build for most Windows systems or arm64 for Windows on Arm, then place the executable somewhere on your PATH.

The CLI authenticates with a Files.com API key and communicates over HTTPS on port 443:

files-cli config set --api-key="YOUR_API_KEY"

files-cli upload `
  "C:\exports\orders.csv" `
  "/partners/brightway/inbound/orders.csv"

The file travels to Files.com over HTTPS. As it arrives, Files.com writes it through the mount to the partner's SFTP server.

The Windows machine does not need Posh-SSH, the partner's private key, a known_hosts file, outbound access on port 22, or a custom transfer retry loop. The CLI runs transfers in parallel and resumes them if the connection drops, on the same transfer engine as the Files.com Desktop App and SDKs (customers have pushed it past 20 Gbit/s), and each operation appears in the Files.com audit log with the user, file, and timestamp.

Downloads follow the same pattern. Files placed on the partner's server appear in the mounted folder, where the CLI can retrieve them:

files-cli download `
  "/partners/brightway/outbound" `
  "C:\imports" `
  --sync

With --sync, the CLI transfers only files that are new or have changed, based on their modification dates.

There are two practical details to remember when using the CLI in a scheduled task. Run files-cli config set as the same Windows account that will run the task, because the CLI stores its configuration in that user's profile. And check $LASTEXITCODE after every CLI command, just as you would after calling sftp.exe.

If your Files.com site uses a custom domain, configure it once:

files-cli config set `
  --endpoint="https://files.yourcompany.com"

The result is a much smaller PowerShell script. The mount handles the SFTP session, credentials, host-key checks, and remote connectivity; the script only needs to upload or download files.

You may not need a script at all

Once the partner's server appears as a Files.com folder, PowerShell may become optional.

A Files.com Sync can move files between a Files.com folder and the remote server on a schedule. It can push files to the partner or pull files from the partner, in one direction or both.

You can then use Files.com automations to process what arrives. Consider an invoice workflow: a partner uploads invoices to its own SFTP server, Files.com pulls the files every ten minutes, and a Move Files automation routes each invoice to the appropriate processing folder. There is no Windows scheduled task to maintain and no transfer script for someone to own.

If polling is not a good fit, enable the Remote Metadata Index for the mount. Files.com scans the remote folder at the interval you choose, as often as every five minutes, and records added or removed files as actions. Those actions can trigger automations.

Existing SFTP scripts can still work

Moving to a Remote Server Mount does not require an immediate rewrite of every existing script.

Files.com accepts inbound SFTP connections on port 22 and supports SSH key authentication, the same way any hosted SFTP server would. An existing Posh-SSH or sftp.exe script can connect to yourcompany.files.com and upload to the mounted path /partners/brightway/inbound. Files.com proxies the operation to the partner's SFTP server. Your script speaks SFTP to Files.com, and Files.com speaks SFTP to the partner.

This still centralizes the partner-specific details. The partner's credentials, host key, and IP allowlist remain on the Files.com side. Your scripts need only one Files.com hostname and one set of credentials.

That becomes especially useful as the number of partners grows. Ten partners can be represented by ten mounts, /partners/brightway, /partners/greenfield, and so on, each with its own encrypted credentials and configuration, while every script follows the same path-based transfer pattern.

If a partner rotates its key or moves to a new server, you update one mount. You do not have to find and modify every scheduled task that exchanges files with that partner.

For SFTP servers inside a private network, the Files.com Agent provides access through an outbound connection, avoiding the need to open an inbound firewall port.

Use the .NET SDK for more than file transfers

The CLI is usually the simplest choice for uploads and downloads. For more advanced workflows, PowerShell can also use the Files.com .NET SDK through the FilesCom NuGet package.

After loading the assembly and supplying an API key through FilesConfiguration, your script can work with users, folders, permissions, share links, and other Files.com resources through typed .NET methods. The Files.com API and SDK reference covers the available operations.

The main advantage of this approach is not a shorter upload command. It is the separation of responsibilities: Files.com manages partner-specific SFTP connections, while your scripts use one consistent interface. As partners, credentials, and endpoints change, the automation around them can stay the same.

A production-ready PowerShell SFTP script

The transfer command is usually the easy part. A reliable script also needs to validate its input, create logs, return a nonzero exit code, and clean up after itself.

Here is a stronger Posh-SSH example:

$ErrorActionPreference = "Stop"

$computerName = "sftp.example.com"
$userName     = "alice"
$keyFile      = "C:\keys\sftp_ed25519"
$localFile    = "C:\exports\orders.csv"
$remoteFolder = "/inbound"
$logFile      = "C:\logs\sftp-upload.log"

$session = $null

function Write-Log {
    param(
        [Parameter(Mandatory)]
        [string] $Message
    )

    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp $Message" | Tee-Object -FilePath $logFile -Append
}

try {
    if (-not (Test-Path -LiteralPath $localFile -PathType Leaf)) {
        throw "Source file does not exist: $localFile"
    }

    if (-not (Test-Path -LiteralPath $keyFile -PathType Leaf)) {
        throw "Private key does not exist: $keyFile"
    }

    $emptyPassword = New-Object System.Security.SecureString
    $credential = [PSCredential]::new($userName, $emptyPassword)

    Write-Log "Connecting to $computerName."

    $session = New-SFTPSession `
        -ComputerName $computerName `
        -Credential $credential `
        -KeyFile $keyFile

    Write-Log "Uploading $localFile to $remoteFolder."

    Set-SFTPItem `
        -SFTPSession $session `
        -Path $localFile `
        -Destination $remoteFolder `
        -Force

    Write-Log "Upload completed successfully."
    exit 0
}
catch {
    Write-Log "ERROR: $($_.Exception.Message)"
    exit 1
}
finally {
    if ($null -ne $session) {
        Remove-SFTPSession -SFTPSession $session | Out-Null
        Write-Log "SFTP session closed."
    }
}

In a real production workflow, consider adding a retry policy for temporary network failures, an email or chat alert after the final failure, a remote filename that marks the upload as still in progress, file-size or checksum verification, archiving only after successful delivery, log retention and rotation, and a lock to prevent overlapping scheduled runs.

One common pattern is to upload using a temporary extension, then rename the file after the transfer completes. That prevents the receiving system from reading a partially uploaded file.

Running the script from Task Scheduler

Most scripts do not fail because SFTP is difficult. They fail because the environment inside Task Scheduler is different from the author's interactive PowerShell session.

These practices prevent the most common problems.

Use a dedicated account

Run the task under a dedicated service account with only the permissions it needs: read access to the source files, write access to any local download or log folders, read access to the private key, access to the required PowerShell module or executable, and permission to run as a batch job. Avoid using a personal administrator account.

Protect the private key

Never paste a password or private-key passphrase directly into the .ps1 file.

For an SSH key without a passphrase, restrict the key with NTFS permissions so only the task's service account and necessary administrators can read it.

For example:

icacls "C:\keys\sftp_ed25519" /inheritance:r
icacls "C:\keys\sftp_ed25519" /grant "DOMAIN\SftpServiceAccount:R"

Review the resulting permissions carefully before using the key.

If a password, key passphrase, or API token must be retrieved at runtime, use a supported secret store such as Windows Credential Manager or Microsoft's PowerShell SecretManagement module.

Use absolute paths

Scheduled tasks may start in C:\Windows\System32, not in the script's directory. Use full paths for scripts, batch files, private keys, logs, source and destination folders, and external executables. Do not depend on your interactive PATH unless you have confirmed it is also available to the task account.

A typical action might be:

Program:
powershell.exe

Arguments:
-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\jobs\upload.ps1"

If you use PowerShell 7, run pwsh.exe instead.

Test as the task account

A script working in your terminal proves only that it works under your account.

Host-key stores, SSH configuration, CLI settings, user profiles, module installations, network-drive mappings, and file permissions can all differ for the scheduled-task account.

Test the transfer in the same security context in which it will run.

Make failure visible

A scheduled transfer must never fail quietly.

At minimum, set $ErrorActionPreference = "Stop", check $LASTEXITCODE after external programs, exit with a nonzero status on failure, write timestamped logs, configure Task Scheduler to retry failed jobs, and send an alert when retries are exhausted.

A failed job is inconvenient. A job that failed silently while downstream systems continued using yesterday's file is much worse.

Which PowerShell SFTP option to choose

Choose Posh-SSH when you need PowerShell-native cmdlets, conditional logic around transfers, support for any standard SFTP server, and file listing, filtering, and post-transfer processing.

Choose Windows OpenSSH when you need no third-party PowerShell module, a small and predictable transfer job, compatibility with standard OpenSSH keys and configuration, and a simple command-line tool that is easy to troubleshoot.

Choose Files.com when you would rather not run SFTP inside PowerShell at all: mount the partner's SFTP server as a folder, let Files.com hold the credentials and the host key, and have the script talk to Files.com over HTTPS with the CLI. It is also the choice when the far end is already Files.com, when you want resumable or synchronized transfers, when transfer activity needs to land in a central audit log, or when the same automation has to run on Windows, macOS, and Linux.

There is no universal winner. The best tool is the simplest one that meets your operational and security requirements.

When scripts stop being the right answer

A few PowerShell transfer jobs are easy to manage. Dozens of scripts spread across servers, service accounts, scheduled tasks, and individual owners are not.

That is usually the point at which teams stop writing better scripts and move the transfers onto a platform that runs them. Files.com is the cloud-native File Orchestration Platform: one platform that replaces the stack of legacy tools IT teams run to move files, including the SFTP servers, the scripts that feed them, and the scheduled tasks holding it all together. It speaks every protocol, connects 50+ cloud and on-prem systems, automates every transfer, and keeps a complete audit trail.

For example, instead of keeping a Windows server alive solely to run a nightly upload, a Files.com automation can detect a new file and copy, rename, route, or deliver it without a separate script host, with retries and a delivery record built in.

PowerShell still has a role. It remains useful for business-specific logic and can call the Files.com CLI or SDK when needed. The difference is that PowerShell no longer has to provide the scheduling, transfer engine, retry policy, credential storage, and audit system all by itself.

If the SFTP server must remain in your own data center, the ExaVault appliance provides an on-premises SFTP target that the examples in this guide can use.

Start a free Files.com trial and point the CLI or your existing SFTP script at it. No credit card required, and an SFTP endpoint with SSH key support is live in minutes.

Frequently asked questions

Does PowerShell have built-in SFTP support?

PowerShell does not include native SFTP cmdlets.

Many Windows 10 and Windows 11 systems do include the OpenSSH sftp.exe client, which PowerShell can run as an external program. For cmdlets such as New-SFTPSession and Set-SFTPItem, install the Posh-SSH module.

How do I upload a file over SFTP with PowerShell?

With Posh-SSH, open a connection with New-SFTPSession, upload with Set-SFTPItem, and close the connection with Remove-SFTPSession:

$session = New-SFTPSession `
    -ComputerName "sftp.example.com" `
    -Credential $credential

Set-SFTPItem `
    -SFTPSession $session `
    -Path "C:\exports\orders.csv" `
    -Destination "/inbound"

Remove-SFTPSession -SFTPSession $session

For unattended jobs, use an SSH key and put session cleanup in a finally block.

How do I use an SSH key with Posh-SSH?

Pass the private key path to New-SFTPSession using -KeyFile:

New-SFTPSession `
    -ComputerName "sftp.example.com" `
    -Credential $credential `
    -KeyFile "C:\keys\sftp_ed25519"

If the key has a passphrase, supply it in the password portion of the PSCredential object.

What does -AcceptKey do?

-AcceptKey accepts and saves the host key presented by the server. It is convenient for an initial connection, but verify the fingerprint before trusting it.

Do not leave -AcceptKey in a production script simply to suppress host-key errors. An unexpected key change may indicate a rebuilt server, a DNS or routing problem, or an attempted interception.

How do I schedule a PowerShell SFTP script?

Create a Task Scheduler task that runs PowerShell under a dedicated account:

powershell.exe -NoProfile -NonInteractive -File "C:\jobs\upload.ps1"

Give that account access to the source files, private key, modules, and log directory. Test the script under the same account, make errors terminating, and return a nonzero exit code when the transfer fails.

Can Files.com connect to my partner's SFTP server for me?

Yes. A Files.com Remote Server Mount connects a folder on your Files.com site to any SFTP server in real time. Files.com authenticates with a password, a private key it can generate for you, or both, stores the partner's host key and disables the connection if that key changes, and passes every operation in the mounted folder through to the partner's server as it happens. Your script then uploads to Files.com over HTTPS with the CLI, or a scheduled Sync moves the files with no script at all.

Can I connect to Files.com over SFTP from PowerShell?

Yes. Files.com supports standard SFTP with SSH-key authentication, so you can use either Posh-SSH or Windows OpenSSH. If the folder you upload into is a Remote Server Mount, Files.com proxies the transfer on to the partner's SFTP server.

You can also use the Files.com CLI, which authenticates with an API key and transfers over HTTPS. The CLI is often a better fit when you need directory synchronization, resumable transfers, or centralized audit history.

FTP, SFTP, FTPS — in a Modern UI

Files.com is the cloud File Orchestration Platform. Bring your FTP clients; pick up a real web file manager, share links, automations, and SOC 2 / HIPAA-BAA compliance.