Edited at

C#でSSH.NETを使ってsftpサーバとやりとりをする


要約

C# SSH.NET の使用例です。

sshでsftpサーバに接続してファイルリストを取得します。

GitHubにサンプルコードを置いてあります。(https://github.com/unknown-ds/csharp_sftp)


環境

Windows 10

Visual Studio Express 2017 for Windows Desktop

Docker Desktop Community ver.2.0.0.3(31259)


参考

テスト用に以下のdocker-composeファイルを使わせて頂きました。ありがとうございます。:grinning:

テスト用途のSFTP/FTPS/FTPサーバーを単独で起動するdocker-compose.ymlの例


実装


フォルダ構成

プロジェクトフォルダ以下にsftpサーバ用dockerフォルダを作っています。この下にアクセステスト用フォルダremoteと鍵が入っているフォルダsshがあります。

└─[projectroot]

  ├─[docker]
  │ ├─[remote]
  │ │ └─test.txt   // テスト用テキストファイル
  │ └─[ssh]
  │   └─id_rsa // 秘密鍵
  └─docker-compose.yml


ライブラリ

NugetでSSH.NETをインストールします。

lib.png


テストクラス説明

コンストラクタで接続情報を用意し、Execute()でテストをしています。

基本的な流れとしては

1. Renci.SshNet.ConnectionInfoクラスを生成し、接続情報を用意する。

2. Renci.SshNet.SftpClientのインスタンスを生成して接続/切断を行う。

3. ファイルリストを取得する場合はListDirectoryで取得して処理。

4. ファイルのアップロード/ダウンロードはUploadFile/DownloadFileメソッドにストリームを渡して処理する。

となります。

// SFTP接続クラス

public class CSftp
{
// 接続情報
public ConnectionInfo ConnNfo { private set; get; }
// 接続ホスト名
public string HostName { private set; get; }
// ポート
public Int32 Port { private set; get; }
// ユーザー名
public string UserName { private set; get; }
// パスワード
public string Password { private set; get; }

// コンストラクタ
public CSftp()
{
HostName = "localhost"; // 接続先ホスト名
Port = 10221; // ポート
UserName = "sftp-with-rsa-key"; // ユーザー名
Password = "1001"; // パスワード

string KeyFile = @"..\..\docker\ssh\id_rsa"; // 秘密鍵
string PassPhrase = ""; // パスフレーズ

// パスワード認証
var _PassAuth = new PasswordAuthenticationMethod(UserName, Password);

// 秘密鍵認証
var _PrivateKey = new PrivateKeyAuthenticationMethod(UserName, new PrivateKeyFile[]{
new PrivateKeyFile(KeyFile, PassPhrase)
});

// 接続情報の生成
ConnNfo = new ConnectionInfo(HostName, Port, UserName,
new AuthenticationMethod[]{
_PassAuth, // パスワード認証
_PrivateKey, // 秘密鍵認証
}
);

}

// 実行
public void Execute()
{

using (var sftp = new SftpClient(ConnNfo))
{
// 接続
sftp.Connect();
// 確認
if (sftp.IsConnected)
{
// 接続に成功
Console.WriteLine("Connection success!!\n");
}
else
{
// 接続に失敗
Console.WriteLine("Connection failed!!\n");
return;
}

// ファイルリスト表示
printFiles(sftp, "/remote");

// ファイル内容表示
printTxtFile(sftp, "/remote/test.txt");

// ファイルアップロード
uploadFile(sftp, "/remote", "../../Program.cs");

// 切断
sftp.Disconnect();
}

}

// ファイル表示
private void printFiles(
SftpClient _sftp, // sftpクライアント
string _Path // パス
)
{
// 指定パスを調べる
foreach (var file in _sftp.ListDirectory(_Path))
{
if (file.Name.StartsWith(".")) continue;

if (file.IsDirectory)
{
// ディレクトリなら再帰して調べる
printFiles(_sftp, file.FullName);
}
else
{
// 表示
Console.WriteLine($"{file.FullName}\t\t{file.LastAccessTime}\t{file.LastWriteTime}");
}
}
}

// 指定テキストファイルの表示
private void printTxtFile(
SftpClient _sftp, // sftpクライアント
string _FilePath // ファイルパス
)
{
var _CurDir = Path.GetDirectoryName(_FilePath).Substring(1);
var _FileName = Path.GetFileName(_FilePath);

// カレントディレクトリ変更
_sftp.ChangeDirectory(_CurDir);

foreach (var file in _sftp.ListDirectory("./"))
{
if (file.IsDirectory) continue;
if (file.Name != _FileName) continue;

// 読み込み
Int64 _Size = file.Length;
var _Buf = new byte[_Size];
using (var _St = new MemoryStream(_Buf, 0, (int)_Size))
{
_sftp.DownloadFile(file.FullName, _St);
}

// SJIS変換
string _str = Encoding.GetEncoding(932).GetString(_Buf);
// 内容表示
Console.WriteLine();
Console.WriteLine($"------------------{file.Name}");
Console.WriteLine($"{_str}");
Console.WriteLine("------------------");
}
}

// ファイルのアップロード
private void uploadFile(
SftpClient _sftp, // sftpクライアント
string _UploadPath, // アップロードパス
string _UploadFile // アップロードファイル名
)
{
// カレントディレクトリ変更
_sftp.ChangeDirectory(_UploadPath);
// アップロード先パス
var _RemotePath = _UploadPath + "/" + Path.GetFileName(_UploadFile);

using (var _uploadStream = File.OpenRead(_UploadFile))
{
_sftp.UploadFile(_uploadStream, _RemotePath, true);
}
}
}


注意点

日本語ファイル名を扱う際は、ConnectionInfoクラスのEncodingプロパティにエンコード情報を設定する。


実行例

docker-compose up -dでsftpサーバを立ち上げます。

テストコードを実行し接続に成功すると以下のような出力になります。

ファイルリストの表示とtest.txtの内容表示、Program.csのアップロードが行われます。

Connection success!!

/remote/test.txt 2019/04/16 11:17:52 2019/04/16 15:29:18
/remote/Program.cs 2019/04/16 16:40:02 2019/04/16 16:40:02

------------------test.txt
テスト用のテキストです。
------------------


SSH接続してコマンドで行う

シェルコマンドでやりとりする場合は、SshClientクラスインスタンスを生成してやり取りをすることができます。

using (var sshclient = new SshClient(ConnNfo))

{
sshclient.Connect();

using (var cmd = sshclient.CreateCommand("ls -lah"))
{
Console.WriteLine(cmd.Execute());
Console.WriteLine($"ExitStatus:{cmd.ExitStatus}");
}
sshclient.Disconnect();
}

シェル実行できない場合は(サーバ側で制限されている場合は)

This service allows sftp connections only.

という風にレスポンスが返ってきます。(ExistStatus は 1 です。)