I'm using plink from c# to connect to Linux servers and run some programs. Both the c# console program and the plink.exe are on the same windows machine.
The problem is when I connect to a Linux server for the first time, plink asks me if I want to accept and store the SSH key from the Linux server. I always want to respond yes to this because all the servers are in my LAN and there is no security issue.
I'm using c# Process type, pass the correct argument to plink, redirect the output and start. Now the problem is when plink prompts, the process.StandardOutput.ReadToEnd(); hangs and I have no way of figuring out whether I'm prompted by plink to accept the key or actually logged into the Linux server.
string output = string.Empty;
string error = string.Empty;
string arguments = #" -ssh -pw password root#12.12.12.12 ./Install_xxx.bin";
using (Process process = new Process())
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "plink";
psi.Arguments = arguments;
psi.ErrorDialog = false;
psi.UseShellExecute = false;
psi.RedirectStandardError = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.CreateNoWindow = true;
process.StartInfo = psi;
process.Start();
output = process.StandardOutput.ReadToEnd();
error = process.StandardError.ReadToEnd();
}
Thanks
Your Deadlock problem is explained in the Process docu. When the process waits for input it cannot close stdin so it will block. StandardOutput will not be closed because the process is still waiting for your input from stdin: Deadlock.
You can either use the asynchronous apis to read line by line or you use for the other stream another thread.
Instead of using an external executable to make your SSH connection, why not use a SSH library and do it (more reliably) through code?
sharpSsh (most recent version here) is a C# port of Jsch, a BSD-licensed Java library. I've used it in my C# projects very successfully; you'll be able to programmatically handle all aspects of the SSH connection, including the key negotiation.
In order to seamlessly connect to a remote server via PLINK, the first time and on subsequent times, you have to manually coordinate your SSH keys. That is, use PUTTYGEN to create your .ppk private key file that PLINK will use, and store the corresponding public key in the ~/.ssh/authorized_keys file on the server beforehand.
You also need to coordinate the sshd's host keys, so use PUTTYGEN to generate a set of private key files ssh_host_rsa_key and ssh_host_dsa_key under /etc/ssh (look at the existing files to figure out which key formats to export to), and then run ssh-keygen -y -f /etc/ssh/ssh_host_rsa_key > /etc/ssh/ssh_host_rsa_key.pub to create the corresponding public key files (do the same to create the ssh_host_dsa_key.pub file).
If you have more than one server you want to PLINK to, and don't mind using the same key to connect to all of them (this will depend on your local IT security policy), then just copy the same key files onto each server.
THEN, on your Windows machine that you're using PLINK, manually connect to any server using PUTTY or PLINK, and interactively accept the host key. Once you do that, PUTTY/PLINK stores that key in the Windows registry, under HKCU\Software\SimonTatham\PuTTY\SshHostKeys. You'll notice a entry like rsa2#22:host, where host is your server's host name.
Now, for the crux of the procedure, you need to copy the REG_SZ hex value 0x23,... into your C# app, where you'll have to manually use the Microsoft.Win32.RegistryKey classes to write this value for all other servers using the same rsa2#22:host naming scheme before you shell out to PLINK (whether you consider this registry value to be security-sensitve is up to you, so protect it somehow in your C# app if you have to).
Like others have mentioned, you may want to consider using an SSH library to get more programmatic feedback on your connection process, but to use PLINK, this is what you need to do.
Related
I'm using C# with SSH.NET and have been able to get a client SSH connection working fine to executing commands across SSH. I can connect to a Linux install I have on a hypervisor VM with no issues, read back the StdOut and StdErr, etc.
However, each command is like a completely new session - it always starts from /home/user, even if you finish from another directory with the last command. You can string multiple commands together with semicolons:
pwd; cd ..; pwd
/home/user
/home
but then if I execute:
pwd
/home/user
I'm back at the home directory for that user.
I'm not disconnecting and closing the session between the commands. Here is some example code for several commands in a row, using changes in path to illustrate the issue:
public void MultiCommandTest()
{
string output1, output2;
var host = Config.SshHostName;
var port = Config.SshHostPort;
var user = Config.SshUsername;
var pass = Config.SshPassword;
var auth = new PasswordAuthenticationMethod(user, pass);
var info = new ConnectionInfo(host,port,user,auth);
using (var ssh = new SshClient(info))
{
ssh.Connect();
var cmd1 = ssh.CreateCommand("pwd; cd ..; pwd");
output1 = cmd1.Execute();
var cmd2 = ssh.CreateCommand("pwd; cd ..; pwd");
output2 = cmd2.Execute();
ssh.Disconnect();
}
string output = output1 + Environment.NewLine + output2;
System.Windows.Forms.Clipboard.SetText(output);
System.Windows.Forms.MessageBox.Show(output);
}
I realize there is a difference between an interactive and non-interactive session when connecting to Bash, etc., but don't know enough about linux, etc. to know if that could be part of the solution (i.e. maybe it has to be resolved in the login side with .profile, .bashrc, or similar?).
Question:
So - is there a way to have a shell-like connection with SSH (and SSH.NET in particular)? Or do I just need to serialize any sets of commands and send them all at once, always starting from scratch for any new command?
You can implement a shell session using:
SshClient.CreateShellStream (ShellStream class) — It gives you one Stream interface, that you can both write and read. This interface is useful, when you want to interact with the shell directly.
or SshClient.CreateShell (Shell class) — Where you yourself provide separate streams for input, output and error output. This interface is useful, when you want to link/pipe the input and output to existing streams (like standard input and output or files). Though you can still interact directly, if you use PipeStream. See also SSH.NET doesn't process my shell input commands.
But using "shell" is really not recommended for automating commands execution. A shell is an interactive feature, that does not have a deterministic behavior.
You better use an exec channel, see How to run commands on SSH server in C#?
For the record, this was resolved by using SSH to connect and open a tunnel, then using telnet to do the interactive part. SSH.NET was not mature enough on the interactive side.
A requirement has arisen that I need to start a Node.js server from a C# application, this is as simple as running a server.js script within the Node.js console. However, I'm not entirely certain how exactly to achieve that.
Here's what I've looked into so far:
In the Node.js installation, there's a file called C:\Program Files (x86)\nodejs\nodevars.bat, this is the command prompt window for Node.js. To start the server, I could possibly be using the following steps:
Execute the nodevars.bat file.
SendKeys to the new process console window to start the server.
This approach feels a bit fragile. There's no guarantee that the target user will have their Node.js installation in the same place, also sending keys to a process may not be an ideal solution.
Another method could be:
Write a batch file that executes nodevars.bat.
Execute the batch file from the C# application.
This seems like a better approach. However, the only problem here is that the nodevars.bat opens in a new console window.
So to the question(s), is there a way I can start a node.js server script using functionality built into the node.js installation? Perhaps sending arguments to the node.exe?
If it is to serve multiple users, i.e. as a server, then you can use the os-service package, and install a Windows service. You can then start and stop the service using the standard API.
If you are to start the server as a "single purpose" server, i.e. to serve only the current user, then os-service is the wrong approach. (Typically when using this approach you will specify a unique port for the service to use, which will only be used by your application).
To start a batch file or other Console application, from C#, without showing a console window, use the standard method, but be sure to specify:
ProcessStartInfo psi = new ProcessStartInfo();
psi.UseShellExecute = false; // This is important
psi.CreateNoWindow = true; // This is what hides the command window.
psi.FileName = #"c:\Path\to\your\batchfile.cmd";
psi.Arguments = #"-any -arguments -go Here"; // Probably you will pass the port number here
using(var process = Process.Start(psi)){
// Do something with process if you want.
}
There are a few different ones but I recommend the os-service package.
So I am having issues with the "net use" command in C#. Basically, I am using the code written here. The code works great, however I have multiple ids that need to be used sequentially. Unfortunately, when trying to connect to another ID, the connection remains in "net use " in Windows, so this exception is thrown:
Win32Exception: Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed.
Now to me, the obvious thing would be to execute the command prompt programmatically to delete the connection when I am done using it. Here is the code that I am running to delete the connection:
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/K net use delete \\IPAddrofserver";
process.StartInfo = startInfo;
process.Start();
I only use /k so I can see if the command works. After this code executes, it says "The network connection cannot be found." However, if I manually open the start menu, and type net use, I can see and delete the connection. I think this may be related to the fact that when running the command prompt programmatically, I notice its being given administrative privilege as opposed to running it under my user token, but I cannot be sure. Any help regarding this would be greatly appreciated.
EDIT: Adding in the command prompt deletion code during the WnetCancelConnection2 actually operates correctly, and deletes the connection from net use. However, checking net use manually outside of the program reveals that the history of the connection still exists and is open. Whoami command reveals the same user. Any reason why there is a discrepancy between what happens programmatically and what happens when I check manually?
You probably need to start your process with elevated privileges. See the accepted answer here for how to do this. You could also choose to call the relevant Windows API directly, to avoid spawning other processes and dealing with those complications.
I am attempting to verify the security of an application. The scenario is this:
A C# WinForms application is run by a limited user via Terminal Services (no desktop, just the app). One of the things this C# app can do is execute a batch file that runs a lengthy process with elevated privileges. I am afraid that the limited user may be able to interrupt the batch script (vua Ctrl+C or some other method) and gain access to the underlying elevated shell.
I have tried to do this myself with various combos of Ctrl+C and Ctrl+Break, etc. All I can get is the "Teminate batch job? (Y/N)" prompt, and if you choose terminate, then control is immediately returned to the C# app (which is good). I have not found a way to break this but it seems dangerous to me.
Does anyone know of a way to break out of a C# instantiated batch script and access the underlying shell without returning to the C# app?
No, don't think there is one. But if you're really worried, why not set the CreateNoWindow property on the ProcessStartInfo object you are presumably using to true to prevent user interaction at all?
Not quite an answer to your described scenario but a different way to look at it.
If possible, I would have a "jobs server" who sole responsibility is to run the jobs your Terminal Services-run apps create. Then you would communicate the job (or just it parameters) via WCF to the server. The users would have no access to the server and very little control of the jobs (possibly just a cancel option and success/failure status reports).
You could do something like this (with a Textbox on your app)
ProcessStartInfo info = new ProcessStartInfo();
info.Arguments = "/C ping 127.0.0.1";
info.WindowStyle = ProcessWindowStyle.Hidden;
info.CreateNoWindow = true;
info.FileName = "cmd.exe";
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
using (Process process = Process.Start(info))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
textBox1.Text += result;
}
}
Then you can see the results of the batch without the users being able to actually see the window at all, that way it's only visible as a process so they can't interupt it.
In my local network ,I have more than 10 pc.I need to take care all of the pc.I want to know all pc’s hardware informations.I also want to control those pc,Suppose ,at this moment I want to restart one of my client pc.Is it possible in C#.if have any question plz ask.Thanks in advance
I use bellow syntax to execute command.
try
{
// create the ProcessStartInfo using "cmd" as the program to be run,
// and "/c " as the parameters.
// Incidentally, /c tells cmd that we want it to execute the command that follows,
// and then exit.
System.Diagnostics.ProcessStartInfo procStartInfo =
new System.Diagnostics.ProcessStartInfo("cmd", "/c " + "shutdown /r /m \\172.16.1.3 /t 1 /");
// The following commands are needed to redirect the standard output.
// This means that it will be redirected to the Process.StandardOutput StreamReader.
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now we create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
string result = proc.StandardOutput.ReadToEnd();
// Display the command output.
Console.WriteLine(result);
}
catch (Exception objException)
{
// Log the exception
}
Using the above code I get the message "The network path was not found."
Pls check the url.
http://support.microsoft.com/kb/317371
If you want to make a program which u can able to get the remote system information. You have to use Microsoft's Remoting.Here we can able to create an object in the remote system and we can able to control it.
It is possible to get System's information by executing the System.Diagnostics.ProcessStartInfo.
It is possible to get system information using "systeminfo" .It is possible to take the output using C#
Pls chk the this.
I hope this will be useful for you.
I don't think this is a C# question, cause this can be done much more elegant with things like Group Policy Editor, System Management Server, System Center Operations Manager, etc.
To do some simple tasks on a remote machine you can take a look into the PsTools.
With those requirements my first stop would be WMI. There's for example the Win32_OperatingSystem class with its Reboot and Shutdown methods and the Win32_Processor with all kinds of information about the CPU.
This MSDN section shows you how to use it from .Net: Getting Started Accessing WMI Data
This MSDN section has quite a lot of short VBScript samples for doing various things using WMI, and even if the code is different, at least you can see which WMI classes/methods/properties you should be looking at: WMI Tasks for Scripts and Applications
Please note RB's comment though, you'll need to have the correct permissions for it to work.
Edit: Forgot that since you'll want to connect to other computers, this sample will be useful: How To: Connect to a Remote Computer