I'm trying to automate configuring remote hosts, we have hundreds of these devices, we normally do it through USB programming, but if I could get a script to connect to these devices and do it programmatically, it would free up time.
These devices run some type of linux os, i'm not sure exactly, but they do have SSH enabled and confirm server host keys when you first connect to them via utility like PuTTY.
For now, i'm just trying to initiate an SSH session with the device. I've done quite a bit of research, and have come up with this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Renci.SshNet;
using Renci.SshNet.Common;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
//Connection information
string user = "admin";
string pass = "********";
string host = "IP Address";
//Set up the SSH connection
using (var client = new SshClient(host, user, pass))
{
//Accept Host key
client.HostKeyReceived += delegate (object sender, HostKeyEventArgs e)
{
e.CanTrust = true;
};
//Start the connection
client.Connect();
var output = client.RunCommand("show device details");
client.Disconnect();
Console.WriteLine(output.ToString());
Console.ReadLine();
}
}
}
}
The problem is this doesn't seem to execute the command listed. The console window comes up, and I can access the same device by WebGUI and see the log file, it shows a connection being made, but when I break the execution and see the variable values the output variable shows null.
If I let the execution sit, with the console window open (just shows a blinking cursor in the upper left), the connection times out after 10 minutes and connection is lost, which I also see happen in the device log.
Why would does this not seem to execute the runcommand and store the results in the output variable?
When you execute the RunCommand() method on an object of type Renci.SshNet.SshClient, it does not return the result as a variable.
Instead, it returns an object of the Renci.SshNet.SshCommand type.
The issue is that, it looks like you can't fit this resultant SshCommand object into a var.
This Renci.SshNet.SshCommand, returned when you execute RunCommand(), will contain several properties and methods.
The properties are:
CommandText
CommandTimeout
ExitStatus
OutputStream
ExtendedOutputStream
Result
Error
They're all useful, but as everything else seems to be working, the only relevant one you want is "Result".
The "Result" property will contain a String, which will be the host stream result of the command you provided to RunCommand().
As you mention the device's logfile has logged a successful connection being made, it looks like the connection is successful. So you'd just have to make the proper tweak to grab the Result, as described above, and you should be good to go.
Addendum:
The following line in the original post's code:
var output = client.RunCommand("show device details");
Should be replaced with this code:
var output = client.RunCommand("show device details").Result;
This will assign the Result property (which is a String) to the output var, which will give the desired outcome.
Related
I am doing SSH to a Linux machine and again from there want to SSH to another Linux machine to carry out few Perforce tasks.
using (SshClient ssh = new SshClient("ip address","username", "pwd"))
{
ssh.Connect();
command = ssh.CreateCommand("ssh hostname");
result = command.Execute();
Console.WriteLine(result);
}
Where the ssh hostname is a password less ssh. How can I control the second SSH session and pass commands to it?
Even explored the CreateShell function, but seems like it is not suggested for automation.
In general, trying to automate ssh command is a bad design.
You better use a port forwarding (aka SSH tunnel) to implement the "hop".
var firstClient =
new SshClient(firstHostName, firstUserName, firstPassword);
firstClient.Connect();
var port = new ForwardedPortLocal("127.0.0.1", secondHostName, 22);
firstClient.AddForwardedPort(port);
port.Start();
var secondClient =
new SshClient(port.BoundHost, (int)port.BoundPort, secondUserName, secondPassword);
secondClient.Connect();
var command = secondClient.CreateCommand("ls");
var result = command.Execute();
Console.WriteLine(result);
There are some cases, when automating the ssh is acceptable (while still not ideal). E.g. because there's an authentication to the second host set up on the first one. I.e. there might be private key in the .ssh folder and you are not allowed to transfer that key to your client machine.
Even then, try talking to the system Administrator to find a better solution. The private key is still accessible using the credentials contained in your application, so it's not protected any better, had the private key itself been contained directly in the application.
Anyway, ssh can accept a command on its command line, like:
command = ssh.CreateCommand("ssh hostname command");
result = command.Execute();
Console.WriteLine(result);
This is the situation. I have a Windows machine and a Linux machine. There is a shared drive between these two machines (which is mapped to Q:). I am trying to figure out how to create an SSH session at the Q: drive (shared drive) from C#. I am trying to use the SharpSsh library to do this.
This is what I have so far, however, it is giving me an error:
try
{
ssh = new SshStream(host, username, password);
Console.WriteLine("OK ({0}/{1})", ssh.Cipher, ssh.Mac);
Console.WriteLine("Server version={0}, Client version={1}", ssh.ServerVersion, ssh.ClientVersion);
Console.WriteLine("-Use the 'exit' command to disconnect.");
Console.WriteLine();
//Sets the end of response character
ssh.Prompt = "#";
//Remove terminal emulation characters
ssh.RemoveTerminalEmulationCharacters = true;
//Reads the initial response from the SSH stream
Console.Write(ssh.ReadResponse()); // Blocking here
while (true)
{
string command = Console.ReadLine();
if (command.ToLower().Equals("exit"))
break;
//Write command to the SSH stream
ssh.Write(command);
//Read response from the SSH stream
Console.Write(ssh.ReadResponse());
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
if(ssh != null)
{
ssh.Close();
}
I have added the Tamir.SharpSSH.dll as a reference to the project, and I am using it in the project. There are two other dll's that are included with SharpSSH, do I need to add them as well to the references? The examples I have seen only have the Tamir.SharpSSH.dll as a reference.
I am not sure how to initiate the connection in the correct location, and how to submit commands properly to the ssh.
UPDATE
I realized I needed to close the SSH connection before ending the program. The error does not exist anymore, however, I am still not getting any information from my "ls" command.
UPDATE
I updated the code with what I have now. It seems like the ssh.ReadResponse() is blocking, which leads me to believe the server is not responding. Is that correct?
I'm seeing an initial delay of 2-5 seconds between the time that I execute DirectorySearcher FindOne() and the first network packet I see go out to the LDAP server. After the initial execution, subsequent executions complete instantly for about 45 seconds. After that period of fast executions, the next execution will be delayed and again all subsequent executions will complete instantly. It seems like there's some sort of caching going on but I haven't been able to find any resources confirming that or describing what is causing the initial delay.
We noticed this on a client Windows 2008 server and then reproduced on our own Windows 2008 and Windows 7 boxes.
Here's what my simple .NET 4.0 C# app looks like. The delay occurs between the "Started" and "Finished" messages.
Any idea why this delay occurs on the initial FindOne() execution? Any help is much appreciated!
using System;
using System.Collections.Generic;
using System.Text;
using System.DirectoryServices;
namespace LdapTest
{
class Program
{
static void Main(string[] args)
{
string[] fetchAttributes;
fetchAttributes = new string[] { "{string[0]}" };
using (DirectoryEntry searchRoot = new DirectoryEntry("LDAP://localserver/ou=lab,dc=ourdomain,dc=com", "cn=binduser,ou=Services,dc=ourdomain,dc=com", "Password", AuthenticationTypes.ReadonlyServer))
{
using (DirectorySearcher searcher = new DirectorySearcher(searchRoot, "(sAMAccountName=UserName)", fetchAttributes, SearchScope.Subtree))
{
Console.WriteLine("Started");
SearchResult result = searcher.FindOne();
Console.WriteLine("Finished");
}
}
}
}
According to the LDAP ADsPath MSDN article, you should specify the ServerBind flag if your binding LDAP path points to a server to avoid unnecessary network traffic. It also recommends giving the full DNS name of the server. In addition, the ReadonlyServer flag is meaningless when pointing to a server. So my first suggestion is to replace the ReadonlyServer flag with ServerBind (and preferably give the full DNS name), or remove the server part of the string (in your example, make it LDAP://ou=lab,dc=ourdomain,dc=com or LDAP://ourdomain.com/ou=lab,dc=ourdomain,dc=com).
The other thing to look at is that you're providing the username by distinguished name. If you look at the core API that DirectoryEntry uses, IADsOpenDSObject::OpenDSObject, it requires that the lpReserved flag [the AuthenticationTypes parameter in DirectoryEntry] is zero [None] or includes the ADS_USE_SSL [SecureSocketsLayer] flag when passing a distinguished name for the username. Note that the SecureSocketsLayer flag requires that Active Directory requires that a certificate server is installed before you can use this flag. You might want to pass the username in a different format.
Finally, this MDSN page says that without any authentication flags, the username and password is sent cleartext. You should add the Secure flag.
I have a report that gets updated every week that needs pushed out to 30+ PC's and the number is rising each week. I am trying to figure out how to take an Access Database and push the files out from a location on our server to all of each of those PC's but what I can't wrap my head around is how to do it. I was going to use a copy command and then use the value of the PC ID in a string and enter it into the file paths. This is a little bit more advanced than I am used to working with. Here's what I have so far:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'database1DataSet1.PCID_List' table. You can move, or remove it, as needed.
this.pCID_ListTableAdapter.Fill(this.database1DataSet1.PCID_List);
}
private void button_Close_Click(object sender, EventArgs e)
{
this.Close();
}
static void button_Run_Click(object sender, EventArgs e)
{
string reportLocationCopy = #"Location TBD";
string repLoc = #"Location TBD";
if (File.Exists(repLoc))
{
// If file already exists in destination, delete it
if (File.Exists(reportLocationCopy))
File.Delete(reportLocationCopy);
File.Copy(repLoc, reportLocationCopy);
}
}
}
}
What I would do is this:
Open a database connection that selects your required information. Then use a sqldata adapter/datatable to draw the number of rows. Then I would write a foreach statement that uses these PCs and sends the updated file. So off the top of my head...
//select query here and your connectionstring
//then fill table as seen below
SqlDataAdapter sDA = new SqlDataAdapter(query, connectionString);
DataTable table = new DataTable();
sDA.Fill(table);
foreach (DataRow row in table.Rows)
{
string pc = (row["PCs"].ToString());
//send files
}
This would be an option:
As the numbers of client PC are growing every week, why don`t you try writing web-service. It will provide the ease and central management.
Now you have two case:
If you are working on intranet, locally host your WCF service on IIS.
If you have website then create web-service for that, then your internet based remote client can access your data.
You might need three things:
WCF service
IIS hosting
Client Application
Blue sky idea here, this may not be possible for security reasons. Can you push the database files to a Dropbox account, which the other machines also reference? This would provide quick synchronization of the machines as the database files are updated.
For what it's worth...
If this is in an Enterprise, your sysadmins will have tools that can push to each PC. I've done similar things to push application releases, and they can script whatever you need.
It might be worthwhile to post the question in a Sys Admin forum like serverfault or everythingsysadmin.
If your really dead set on managing the distribution, you can have your application call a Web Service or check a shared drive on startup for a newer version of the file and download it (either ftp or straight copy).
Let the client do the lookup to avoid issues like the PC not being available. It also allows the client to cancel the load if they are in a hurry. Nothings worse that having an update forced on you when you need something quickly!
HI
As a daily check I have to check the SQL connection to all our servers. At the moment this is done by manually logging on through SQL server managment studio. I was just wondering if this can be coded in C# so that I can run it first thing in a morning and its checks each server and the instance for a valid SQL connection and reports back to say if the connection is live or not.
Thanks Andy
Here's a little console app example that will cycle through a list of connections and attempt to connect to each, reporting success or failure. Ideally you'd perhaps want to extend this to read in a list of connection strings from a file, but this should hopefully get you started.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Text;
namespace SQLServerChecker
{
class Program
{
static void Main(string[] args)
{
// Use a dictionary to associate a friendly name with each connection string.
IDictionary<string, string> connectionStrings = new Dictionary<string, string>();
connectionStrings.Add("Sales Database", "< connection string >");
connectionStrings.Add("QA Database", "< connection string >");
foreach (string databaseName in connectionStrings.Keys)
{
try
{
string connectionString = connectionStrings[databaseName];
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
Console.WriteLine("Connected to {0}", databaseName);
}
}
catch (Exception ex)
{
// Handle the connection failure.
Console.WriteLine("FAILED to connect to {0} - {1}", databaseName, ex.Message);
}
}
// Wait for a keypress to stop the console closing.
Console.WriteLine("Press any key to finish.");
Console.ReadKey();
}
}
}
Look at the SqlConnection Class. It includes a basic sample. Just put a loop around that sample to connect to each server, and if any server fails to connect it throws an exception.
Then just set it up as a scheduled task in Windows.
A nice way to report the status might be with an email, which can easily be sent out with SmtpClient.Send (the link has a nice simple sample.
Why c#? You could make a simple batch file that does this using the osql command.
osql -S servername\dbname -E -Q "select 'itworks'"
You can also have a look at this method:
SqlDataSourceEnumerator.Instance.GetDataSources()
It gives you a list of SQL servers available on the network.
You know, they make monitoring tools that let you know if your sql server goes down . . .