"Multi-Step" commands while using CMD.exe in C# - c#

I'm trying to use "multi-step" command in a c# script, for example the command "net user usrname *" contains 3 steps to enter a password and then validate, i don't know if it is possible to send extra arguments while the Process is running
My code:
Process p = new Process();
p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/C " + command;
p.StartInfo.WorkingDirectory = startupFolder;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.UseShellExecute = false;
p.Start();
string output = p.StandardOutput.ReadToEnd();
string error = p.StandardError.ReadToEnd();

You would concatenate each command with "&". For example, "cmd /k echo Test 1 & echo test 2".
Edit:
I created a remote control/remote admin solution a while back that uses this same technique to allow you to run batch and PowerShell scripts against remote computers via the web portal. As shown in the below screenshot, it works.
The C# that executes the command can be found here: https://github.com/Jay-Rad/InstaTech_Client/blob/master/InstaTech_Service/Socket.cs#L614
if (cmdProcess == null || cmdProcess.HasExited)
{
var psi2 = new ProcessStartInfo("cmd.exe", "/k " + command);
psi2.RedirectStandardOutput = true;
psi2.RedirectStandardInput = true;
psi2.RedirectStandardError = true;
psi2.UseShellExecute = false;
psi2.WorkingDirectory = Path.GetPathRoot(Environment.SystemDirectory);
cmdProcess = new Process();
cmdProcess.StartInfo = psi2;
cmdProcess.EnableRaisingEvents = true;
cmdProcess.OutputDataReceived += async (object sender, DataReceivedEventArgs args) =>
{
jsonMessage.Status = "ok";
jsonMessage.Output = args.Data;
await SocketSend(jsonMessage);
};
cmdProcess.ErrorDataReceived += async (object sender, DataReceivedEventArgs args) =>
{
jsonMessage.Status = "ok";
jsonMessage.Output = args.Data;
await SocketSend(jsonMessage);
};
cmdProcess.Start();
cmdProcess.BeginOutputReadLine();
cmdProcess.BeginErrorReadLine();
}
else
{
cmdProcess.StandardInput.WriteLine(command);
}

Related

C# implicitly calls the CMD command Attrib with administrator privileges

Use C# to copy WINDOWS log files to other disks. The main problem is that the CMD command with administrator privileges will not be called, and some of the things mentioned on the Internet will not work.
private Process proc = null;
public Command()
{
proc = new Process();
}
public void RunCmd(string cmd)
{
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = "C:\Windows\System32\cmd.exe";
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.RedirectStandardInput = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.Verb = "RunAs";
proc.Start();
proc.StandardInput.WriteLine(cmd);
proc.Close();
}
public void ChangeFile(string path1,string path2)
{
Command cmd = new Command();
cmd.RunCmd("attrib -s" + " " + path1);
Directory.CreateDirectory(path2);
cmd.RunCmd("Xcopy" + " " + path1 + " " + path2);
}
Hope someone can tell me how to fix it.
You can restart a process as an administrator like this:
ProcessStartInfo info = new ProcessStartInfo(#"C:\Windows\cmd.exe");
info.UseShellExecute = true;
info.Verb = "runas";
Process.Start(info);
For details, please refer to Run process as administrator from a non-admin application

C# PsExec unable to read complete output, Only first line of output is coming

I am trying to run "ipconfig" cmd on remote machine. In cmd prompt, I am getting the complete output. But through C# code, I am getting only first line of output.
public void RunCmd()
{
string command = "cmd.exe";
string arguments = #"/c C:\PSTools\PsExec.exe -accepteula -i \\xxxx -s -u xxxxx -p xxxx cmd /c ipconfig";
string workingDirectory = #"C:\PSTools\";
ProcessStartInfo procStartInfo = new ProcessStartInfo();
procStartInfo.FileName = command;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.RedirectStandardInput = true;
procStartInfo.UseShellExecute = false;
procStartInfo.CreateNoWindow = true;
if (arguments != null)
procStartInfo.Arguments = arguments;
if (workingDirectory != null)
procStartInfo.WorkingDirectory = workingDirectory;
Process process = process = new Process();
process.StartInfo = procStartInfo;
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler(
(s, e) =>
{
Console.WriteLine($"{e.Data}");
}
);
process.ErrorDataReceived += new DataReceivedEventHandler((s, e) =>
{
Console.WriteLine($"{e.Data}");
});
process.Start();
//process.StandardInput.AutoFlush = true;
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}

How to read process Output Message from process within the process?

I am running an exe through commandline and getting following output.
C:\Users\sysadmin>C:\Users\sysadmin\Desktop\New_folder\Setup\PatchInstaller.exe
--mode=silent
C:\Users\sysadmin Begin Setup UI mode: Silent Error :
Another instance running, Only a single instance can be run at a time.
Exit Code: 11
i am running this through System.daignostics.process.
My issue is PatchInstaller.exe calling another process and the output of that nested process is what is visible with cmd. but the same result and exit code i am not able to get through Process object of PatchInstaller.exe.
Is there any way of getting output of process running within process?
Following is the code i have tired...
string command = #"C:\Users\sysadmin\Desktop\Setup\PatchInstaller.exe";
string result = string.Empty;
System.Diagnostics.ProcessStartInfo procStartInfo = new ProcessStartInfo();
procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command + " --mode=silent);
System.Diagnostics.Process proc = new Process();
procStartInfo.ErrorDialog = false;
procStartInfo.UseShellExecute = false;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = true;
procStartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
if (!string.IsNullOrEmpty(domain) && !string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pwd))
{
procStartInfo.Domain = domain;
procStartInfo.UserName = user;
System.Security.SecureString ss = new System.Security.SecureString();
foreach (char c in pwd) { ss.AppendChar(c); }
procStartInfo.Password = ss;
}
proc = System.Diagnostics.Process.Start(procStartInfo);
proc.ErrorDataReceived += delegate(object sender, System.Diagnostics.DataReceivedEventArgs errorLine)
{
if (errorLine.Data != null) result += "error:" + errorLine.Data +;
};
proc.OutputDataReceived += delegate(object sender, System.Diagnostics.DataReceivedEventArgs outputLine)
{
if (outputLine.Data != null) result += outputLine.Data +;
};
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
Process[] pname = Process.GetProcessesByName("PatchInstaller");
Process[] processlist = Process.GetProcesses();
foreach (Process theprocess in processlist)
{
Console.WriteLine("Process: {0} ID: {1}", theprocess.ProcessName, theprocess.Id);
}
proc.WaitForExit();
I don't know much about ProcessStartInfo but I have used Process before and the way to get the information out of the standard output is shown as below, I assume it should be a similar way just by accessing the StandardOutput
Process cmd = new Process();
cmd.StartInfo.FileName = "cmd.exe";
cmd.StartInfo.RedirectStandardInput = true;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.CreateNoWindow = false;
cmd.StartInfo.UseShellExecute = false;
cmd.Start();
cmd.StandardInput.WriteLine(command);
cmd.StandardInput.Flush();
cmd.StandardInput.Close();
var output = cmd.StandardOutput.ReadToEnd();
cmd.WaitForExit();
This code worked for me:
const int MAX_EXIT_WAIT_TIME = 3000;
// Fill needed data
string username = "";
string password = "";
string domain = "";
string appName = "";
var dir = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
var appFullPath = Path.Combine(dir, appName);
ProcessStartInfo psi = new ProcessStartInfo(appFullPath);
psi.UserName = username;
var securePass = new System.Security.SecureString();
foreach (var c in password)
securePass.AppendChar(c);
psi.Password = securePass;
psi.Domain = domain;
psi.LoadUserProfile = false;
psi.WorkingDirectory = dir;
psi.Arguments = "";
psi.RedirectStandardOutput = true;
// Create Process object, but not start it!
var proc = new Process();
proc.StartInfo = psi;
StringCollection values = new StringCollection();
DataReceivedEventHandler outputDataReceived = (o, e) =>
{
lock (values)
values.Add(e.Data);
};
try
{
proc.OutputDataReceived += outputDataReceived;
// Only here we start process
if (!proc.Start())
throw new InvalidOperationException("Couldn't start app");
proc.BeginOutputReadLine();
proc.WaitForExit(MAX_EXIT_WAIT_TIME);
}
finally { proc.OutputDataReceived -= outputDataReceived; }
Console.WriteLine("Read {0} ", values.Count);
foreach (var item in values)
Console.WriteLine(" {0}", item);

Find Drive Letter Using Net Use

What I'm trying to do
Launch PSExec to open CMD on a remote computer passing "Net Use * \Server\Share" command
Launch PSExec again and remove the share i just created.
I can't seem to figure out how to get the drive letter that the wild card used.
Process p = new Process();
p.StartInfo.UseShellExecute = false;
p.StartInfo.FileName = #"\\Server\PStools\PSExec.exe";
p.StartInfo.Arguments = #"\\ComputerName -e -s cmd.exe ""/C Net USE * \\Server\Share /Persistent:NO""";
p.Start();
The net use command with a wildcard will pick the first available drive letter in the sequence from Z to A. It reports the selected drive letter in the console output like so:
C:\>net use * \\server\share
Drive Z: is now connected to \\server\share.
The command completed successfully.
C:\>_
So what you need is to capture the output of the PSExec command and parse it to find the allocated drive letter.
I haven't tried this with PSExec as yet, but this is the code I use for capturing the output of commands via cmd.exe:
static class CommandRunner
{
static StringBuilder cmdOutput = new StringBuilder();
public static string Run(string command)
{
if (string.IsNullOrWhiteSpace(command))
return null;
using (var proc = new Process())
{
proc.StartInfo.FileName = "cmd.exe";
proc.StartInfo.Arguments = "/c " + command;
proc.StartInfo.LoadUserProfile = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.RedirectStandardError = true;
proc.StartInfo.UseShellExecute = false;
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += proc_DataReceived;
proc.ErrorDataReceived += proc_DataReceived;
try
{
proc.Start();
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
catch (Exception e)
{
cmdOutput.AppendLine("***Exception during command exection***");
cmdOutput.AppendLine(e.Message);
cmdOutput.AppendLine("*** ***");
}
}
return cmdOutput.ToString();
}
static void proc_DataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
cmdOutput.AppendLine(e.Data);
}
}
To get the output of a command on the local machine call it like this:
string output = CommandRunner.Run("net use");
Shouldn't be too hard to add a method that executes commands on a remote PC using PSExec instead of the local cmd.exe. Something similar to the following:
public static string Remote(string target, string command, string peFlags = "-e -s")
{
if (string.IsNullOrWhiteSpace(command))
return null;
using (var proc = new Process())
{
proc.StartInfo.FileName = #"C:\PSTools\PSExec.exe";
proc.StartInfo.Arguments = string.Format(#"\\{0}{1} cmd.exe ""/c {2}""", target, peFlags == null ? "" : " " + peFlags, command);
proc.StartInfo.LoadUserProfile = false;
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.RedirectStandardOutput = true;
proc.StartInfo.UseShellExecute = false;
proc.EnableRaisingEvents = true;
proc.OutputDataReceived += proc_DataReceived;
try
{
proc.Start();
proc.BeginOutputReadLine();
proc.WaitForExit();
}
catch
{ }
}
return cmdOutput.ToString();
}
NOTE: I removed the stderr redirection here because I want only the output of the remote program, not the various lines added to the output by PSExec.

Execute multiple command lines with the same process using .NET

I'm trying to execute multiple commands without create a new process each time. Basically, I want to start the DOS command shell, switch to the MySQL command shell, and execute a command. Here's how I am calling the procedure (also below). Also, how do I handle the "\"'s in the command?
ExecuteCommand("mysql --user=root --password=sa casemanager", 100, false);
ExecuteCommand(#"\. " + Environment.CurrentDirectory + #"\MySQL\CaseManager.sql", 100, true);
private void ExecuteCommand(string Command, int Timeout, Boolean closeProcess)
{
ProcessStartInfo ProcessInfo;
Process Process;
ProcessInfo = new ProcessStartInfo("cmd.exe", "/C " + Command);
ProcessInfo.CreateNoWindow = false;
ProcessInfo.UseShellExecute = false;
Process = Process.Start(ProcessInfo);
Process.WaitForExit(Timeout);
if (closeProcess == true) { Process.Close(); }
}
You can redirect standard input and use a StreamWriter to write to it:
Process p = new Process();
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = "cmd.exe";
info.RedirectStandardInput = true;
info.UseShellExecute = false;
p.StartInfo = info;
p.Start();
using (StreamWriter sw = p.StandardInput)
{
if (sw.BaseStream.CanWrite)
{
sw.WriteLine("mysql -u root -p");
sw.WriteLine("mypassword");
sw.WriteLine("use mydb;");
}
}
const string strCmdText = "/C command1&command2";
Process.Start("CMD.exe", strCmdText);
Couldn't you just write all the commands into a .cmd file in the temp folder and then execute that file?
As another answer alludes to under newer versions of Windows it seems to be necessary to read the standard output and/or standard error streams otherwise it will stall between commands. A neater way to do that instead of using delays is to use an async callback to consume output from the stream:
static void RunCommands(List<string> cmds, string workingDirectory = "")
{
var process = new Process();
var psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.RedirectStandardInput = true;
psi.RedirectStandardOutput = true;
psi.RedirectStandardError = true;
psi.UseShellExecute = false;
psi.WorkingDirectory = workingDirectory;
process.StartInfo = psi;
process.Start();
process.OutputDataReceived += (sender, e) => { Console.WriteLine(e.Data); };
process.ErrorDataReceived += (sender, e) => { Console.WriteLine(e.Data); };
process.BeginOutputReadLine();
process.BeginErrorReadLine();
using (StreamWriter sw = process.StandardInput)
{
foreach (var cmd in cmds)
{
sw.WriteLine (cmd);
}
}
process.WaitForExit();
}
I prefer to do it by using a BAT file.
With BAT file you have more control and can do whatever you want.
string batFileName = path + #"\" + Guid.NewGuid() + ".bat";
using (StreamWriter batFile = new StreamWriter(batFileName))
{
batFile.WriteLine($"YOUR COMMAND");
batFile.WriteLine($"YOUR COMMAND");
batFile.WriteLine($"YOUR COMMAND");
}
ProcessStartInfo processStartInfo = new ProcessStartInfo("cmd.exe", "/c " + batFileName);
processStartInfo.UseShellExecute = true;
processStartInfo.CreateNoWindow = true;
processStartInfo.WindowStyle = ProcessWindowStyle.Normal;
Process p = new Process();
p.StartInfo = processStartInfo;
p.Start();
p.WaitForExit();
File.Delete(batFileName);
ProcessStartInfo pStartInfo = new ProcessStartInfo();
pStartInfo.FileName = "CMD";
pStartInfo.Arguments = #"/C mysql --user=root --password=sa casemanager && \. " + Environment.CurrentDirectory + #"\MySQL\CaseManager.sql"
pStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process.Start(pStartInfo);
The && is the way to tell the command shell that there is another command to execute.
A command-line process such cmd.exe or mysql.exe will usually read (and execute) whatever you (the user) type in (at the keyboard).
To mimic that, I think you want to use the RedirectStandardInput property: http://msdn.microsoft.com/en-us/library/system.diagnostics.processstartinfo.redirectstandardinput.aspx
You could also tell MySQL to execute the commands in the given file, like so:
mysql --user=root --password=sa casemanager < CaseManager.sql
You need to READ ALL data from input, before send another command!
And you can't ask to READ if no data is avaliable... little bit suck isn't?
My solutions... when ask to read... ask to read a big buffer... like 1 MEGA...
And you will need wait a min 100 milliseconds... sample code...
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim oProcess As New Process()
Dim oStartInfo As New ProcessStartInfo("cmd.exe", "")
oStartInfo.UseShellExecute = False
oStartInfo.RedirectStandardOutput = True
oStartInfo.RedirectStandardInput = True
oStartInfo.CreateNoWindow = True
oProcess.StartInfo = oStartInfo
oProcess.Start()
Dim Response As String = String.Empty
Dim BuffSize As Integer = 1024 * 1024
Dim x As Char() = New Char(BuffSize - 1) {}
Dim bytesRead As Integer = 0
oProcess.StandardInput.WriteLine("dir")
Threading.Thread.Sleep(100)
bytesRead = oProcess.StandardOutput.Read(x, 0, BuffSize)
Response = String.Concat(Response, String.Join("", x).Substring(0, bytesRead))
MsgBox(Response)
Response = String.Empty
oProcess.StandardInput.WriteLine("dir c:\")
Threading.Thread.Sleep(100)
bytesRead = 0
bytesRead = oProcess.StandardOutput.Read(x, 0, BuffSize)
Response = String.Concat(Response, String.Join("", x).Substring(0, bytesRead))
MsgBox(Response)
End Sub
End Class
I'm using these methods:
public static Process StartCommand(params string[] commands) => StartCommand(commands, false);
public static Process StartCommand(IEnumerable<string> commands, bool inBackground, bool runAsAdministrator = true)
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
if(commands.Any()) p.StartInfo.Arguments = #"/C " + string.Join("&&", commands);
if (runAsAdministrator)
p.StartInfo.Verb = "runas";
if (inBackground)
{
p.StartInfo.CreateNoWindow = true;
p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
}
p.Start();
return p;
}
Enjoy...

Categories