Transfer files with SCP
using SshSharp.Scp; adds SCP transfers to SshClient as extension methods. They run the server's scp program over an exec
channel (scp -t to receive, scp -f to send), as the classic scp command does.
When to use
- The server has no SFTP subsystem: some embedded systems, appliances and restricted shells offer only
scp. - Otherwise prefer SFTP: it can list, resume and change files, and does not depend on a program on the server.
Files
// using SshSharp.Scp;
await client.UploadFileAsync("report.csv", "/srv/reports/report.csv", cancellationToken: cancellationToken);
await client.DownloadFileAsync("/var/log/app.log", "app.log", cancellationToken: cancellationToken);
// An existing directory as the target keeps the other side's file name, as scp does.
await client.UploadFileAsync("report.csv", "/srv/reports/", cancellationToken: cancellationToken);
Streams
var json = Encoding.UTF8.GetBytes("""{ "feature": true }""");
using (var source = new MemoryStream(json))
{
await client.UploadAsync(source, "/etc/myapp/flags.json", cancellationToken: cancellationToken); // the length comes from the stream
}
using var destination = new MemoryStream();
await client.DownloadAsync("/etc/hostname", destination, cancellationToken: cancellationToken);
UploadAsync takes the length from the stream; for a stream that cannot seek, pass length:. SCP sends the length before the
data, so it must be known in advance.
Directories, permissions and progress
var options = new SshScpOptions
{
Preserve = true, // scp -p
FileMode = UnixFileMode.UserRead | UnixFileMode.UserWrite, // 0600 for files whose mode is not preserved
Progress = new Progress<SshScpProgress>(p => Console.Write($"\r{p.Path}: {p.BytesTransferred:N0}/{p.TotalBytes:N0}")),
};
await client.UploadDirectoryAsync("site", "/var/www/site", options, cancellationToken); // scp -r
await client.DownloadDirectoryAsync("/var/log/app", "logs", options, cancellationToken);
As with scp -r, the directory becomes the target when that does not exist, or goes inside it when it does.
Notes and pitfalls
- Errors. A failed transfer (missing file, permission denied, no
scpon the server) throwsSshExceptionwithSshFailureReason.TransferFailedand the server's own message. - Quoting. Remote paths are passed to the server's shell as one single-quoted word, which is safe with POSIX shells. A
Windows server whose default shell is
cmd.exeor PowerShell does not read single quotes that way. - Hostile servers. Every name the server sends must be one path component, and a single-file download accepts exactly one file, so a server cannot write outside the target or add files (CVE-2019-6111). Set-id and sticky bits are never applied.
- No resume, no listing. SCP has neither; use SFTP for those.
ScpCommand.SshScpOptions.ScpCommandnames the program to run on the server, whenscpis not on itsPATH.
See also
- File transfer (SCP) (in-depth guide)
- Transfer files with SFTP
- SshScpExtensions in the API reference