SSH.NET
https://github.com/sshnet/SSH.NET/
https://github.com/djhmateer/console-app-ssh for demo code from this article.
- SSH to a linux VM from C#
- SFTP files to and from a linux VM
- Stream stdout back to C# so can view what is happening
SSH to a linux vm
using Renci.SshNet;
var host = $"webfacesearchgpu123.westeurope.cloudapp.azure.com";
var username = "dave";
var password = "secret";
// 1.run a command and get stdout back
using var client = new SshClient(host, username, password);
client.Connect();
// stderr is &2
var cmd = client.CreateCommand("echo 12345; echo 654321 >&2");
var result = cmd.Execute();
Console.Write($"stdout: {result}");
var reader = new StreamReader(cmd.ExtendedOutputStream);
var stderr = reader.ReadToEnd();
Console.Write($"stderr: {stderr}");
client.Disconnect();
SFTP files to vm ie Upload
Upload a file to the remote vm:
var fileName = "lots-of-images.zip";
var localFilePath = $@"c:\temp\{fileName}";
//var remoteFilePath = "/tmp/test.txt";
var remoteFilePath = $@"/home/dave/facesearch/facesearch_cloud/{fileName}";
using var client = new SftpClient(host, username, password);
try
{
client.Connect();
using var s = File.OpenRead(localFilePath);
// there is an Async BeginUploadFile
// explore https://github.com/sshnet/SSH.NET/tree/develop/src/Renci.SshNet.Tests/Classes
client.UploadFile(s, remoteFilePath);
}
catch (SshOperationTimeoutException ex)
{
var message = $"SshOperationTimeoutException in sftp {ex}";
Log.Error(ex, "SshOperationTimeoutException in sftp");
await Task.Delay(5000, cancellationToken);
}
catch (SocketException ex)
{
var message = $"SocketException in sftp {ex}";
Log.Error(ex, "SocketException in sftp");
await Task.Delay(5000, cancellationToken);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
finally
{
client.Disconnect();
}
My vm takes longer than 30s to come up, so I’m catching the timeout and have retry logic
https://github.com/Azure/azure-cli/issues/5275 there is talk about a raw vm copy which would be nice.
Sftp files from vm ie Download
using var sftp = new SftpClient(host, username, password);
try
{
sftp.Connect();
//var remoteDirectory = "/tmp";
// view remote files
//var files = sftp.ListDirectory(remoteDirectory);
//foreach (var file in files)
//{
// Console.WriteLine(file.Name);
//}
// download file from remote
string pathRemoteFile = "/tmp/myScript.txt";
// Path where the file should be saved once downloaded (locally)
string pathLocalFile = @"c:\temp\myScript.txt";
using (Stream fileStream = File.OpenWrite(pathLocalFile))
{
sftp.DownloadFile(pathRemoteFile, fileStream);
}
}
catch (Exception e)
{
Log.Error(e, "Exception in SFTP download");
}
finally
{
sftp.Disconnect();
}
Stream results back from an SSH session
using var client = new SshClient(host, username, password);
client.Connect();
using var shellStream = client.CreateShellStream("Tail", 0, 0, 0, 0, 1024);
shellStream.DataReceived += ShellDataReceived;
var prompt = $"dave@osrfacesearchgpu{number}vm:~$";
var promptFS = $"dave@osrfacesearchgpu{number}vm:~/facesearch$";
// make sure the prompt is there - regex not working yet?
//var output = shellStream.Expect(new Regex(@"[$>]"));
var output = shellStream.Expect(prompt);
shellStream.WriteLine("cd facesearch");
shellStream.Expect(promptFS);
shellStream.WriteLine("./facesearch.py");
shellStream.Expect(promptFS);
client.Disconnect();
static void ShellDataReceived(object sender, Renci.SshNet.Common.ShellDataEventArgs e)
{
Console.Write(Encoding.UTF8.GetString(e.Data));
}
Showing the concept of streaming the VM’s stdout back to C#. Also waiting (Expecting) for something to happen ie a command prompt before sending a command to the vm.
Timeouts and Exceptions and Async
Notice the keepaliveinternal below. For long running jobs I was noticing in the syslog on the target machine an entry like:
session-4.scope succeeded
To stop this happening put in the keepalive
using var client = new SshClient(host, username, password);
// need this otherwise will timeout after 10 minutes or so
client.KeepAliveInterval = TimeSpan.FromMinutes(1);
try
{
client.Connect();
//using var shellStream = client.CreateShellStream("Tail", 0, 0, 0, 0, 1024);
using var shellStream = client.CreateShellStream("Tail", 0, 0, 0, 0, 1024);
var counter = 0;
shellStream.DataReceived += async (o, e) =>
{
var responseFromVm = Encoding.UTF8.GetString(e.Data).Trim();
if (responseFromVm != "")
{
Log.Information(responseFromVm);
var result = writer.TryWrite(DateTime.Now.ToLongTimeString() + " " + responseFromVm);
if (!result) Log.Error("can't write to channel");
};
See my blog article writing to a channel from an event for more detailed information