C# + SSH.NET + SFTPでファイル転送をやってみました。SSH.NETってのは.NET向けののSSHライブラリです。

SFTPするにあたってNugetで探してみた結果、前述のSSH.NETがダウンロード数が最も多く、最近もリリースがあったのでこいつを使うことにしました。SSH.NETのNugetはこちら

環境


今回書いたコードとほぼ同等のコードは以下の環境で動作することを確認しています。

  • .NET 4.6.1
  • mono 4.6.2

コード


公式のドキュメントはちょっぴりプアー(失礼)ですが、「SSH.NET usaga」とかで検索すると英語圏で使用している方のサンプルコードがいろいろ見つかります。なお、日本語の記事は…コードまで載ってるのはないですね…「この記事がパイオニアだ!!」

早速コードを見ましょう。
ざっと書いてみたコードはこんな感じです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.IO;
using Renci.SshNet;

class SftpSample
{
static void Main(string[] args)
{

//秘密鍵使う場合
//例としてユーザーディレクトリ配下の.sshディレクトリ内のsecret_key.pemを秘密鍵として使う
string secretKeyPath = Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh", "secret_key.pem");
var authMethod = new PrivateKeyAuthenticationMethod("username", new PrivateKeyFile(secretKeyPath, "finger_print"));

//パスワードの場合
var authMethod = new PasswordAuthenticationMethod("username", "password");

//接続情報のインスタンス生成
var connectionInfo = new ConnectionInfo("IP or domain", 22, "username", authMethod);

using (var client = new SftpClient(connectionInfo))
{
//接続してアップロードディレクトリに移動
client.Connect();
client.ChangeDirectory("/home/username");
using (var fs = System.IO.File.OpenRead("test.txt"))
{
//ファイルのアップロード(trueで上書き)
client.UploadFile(fs, "test.txt", true);
}
client.Disconnect();

//接続してダウンロードディレクトリに移動
client.Connect();
client.ChangeDirectory("/home/username");
using (var fs = System.IO.File.OpenWrite("test.txt"))
{
//ファイルのダウンロード
client.DownloadFile("test.txt", fs);
}
client.Disconnect();
}
}
}

コードに大体の解説を書いてますが、少し補足すると下記の流れになると思います。

  • 認証方法のインスタンスを生成

    • 秘密鍵を用いた認証もしくはパスワード認証
    • 秘密鍵を使用する場合はppk形式は指定できない(例外が発生する)
  • 接続情報インスタンス生成

    • 生成した認証方法インスタンスをコンストラクタの引数で渡す
  • sftpClientインスタンス生成

    • 接続情報インスタンスをコンストラクタの引数に渡す
  • やりたい処理を書く

    • ダウンロード・アップロード

ちなみに例として秘密鍵を用いた認証の場合にユーザーディレクトリのパスを取得するメソッドSystem.Environment.GetFolderPathを使用していますが、当然Linux(mono)を用いた場合でも問題なくユーザーのディレクトリ配下のパスを取得してくれます。

ディレクトリ配下のファイルをすべてダウンロードしたい

実際に使用する場合は「ディレクトリごとダウンロードしたい」とかになるんじゃないでしょうか?

しかし、SSH.NETの場合はディレクトリ一式のダウンロードはできない(?)ようなので、ディレクトリ内の全てのファイルのパスを取得して個別にダウンロードする必要があります。
ざっと、コードを書くと下記のようになると思います。

1
2
3
4
5
6
7
8
9
10
client.Connect();
var files = client.ListDirectory("/home/username/test").Where(x => x.Name != "." && x.Name != "..");
foreach (var file in files)
{
using (var fs = System.IO.File.OpenWrite(file.Name))
{
client.DownloadFile(file.FullName, fs);
}
}
client.Disconnect();

上記の例では/home/username/testのディレクトリ内のファイルを一つずつダウンロードしています。
ただし、ディレクトリ内にサブディレクトリがあった場合にどうなるかは試してないのでわかりません。

感想


思ってたより簡単にできました。
ただ、ユーザー名とかファイル名とか同じ値を何回か記述しないといけないのはめんどくさいな~と思いました。一時変数に放り込んだらいいと思いますが…