using docker programmatically and capturing the standardoutput - c#

I would like to run some docker commands programmatically inside a process, and capture the standard output of docker. The commands do run without problems, but I cannot capture the standard output. For example, consider the following code. I would like to the read the docker info command, but all I get is a null string. Do you if there is a way to interact with docker in this way? Thanks
private static void VerifyDocker()
{
var processStartInfo = new ProcessStartInfo();
var messagesBuilder = new StringBuilder();
var arguments = "info --format '{{json .}}'";
using (var process = new Process())
{
processStartInfo.FileName = "docker";
processStartInfo.Arguments = arguments;
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardOutput = true;
process.OutputDataReceived += (sender, e) => messagesBuilder.Append(e.Data);
process.StartInfo = processStartInfo;
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
process.CancelOutputRead();
var message = messagesBuilder.ToString();
}
}

Yes it's a good idea to capture both, and also use double quotes instead of single quotes for the parameter passed to --format.
private static void VerifyDocker()
{
var processStartInfo = new ProcessStartInfo();
var messagesBuilder = new StringBuilder();
var errorMessagesBuilder = new StringBuilder();
var arguments = "info --format \"{{json .}}\"";
using (var process = new Process())
{
processStartInfo.FileName = "docker";
processStartInfo.Arguments = arguments;
processStartInfo.CreateNoWindow = true;
processStartInfo.UseShellExecute = false;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardError = true;
process.OutputDataReceived += (sender, e) => messagesBuilder.Append(e.Data);
process.ErrorDataReceived += (sender, e) => errorMessagesBuilder.Append(e.Data);
process.StartInfo = processStartInfo;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
process.CancelOutputRead();
process.CancelErrorRead();
var message = messagesBuilder.ToString();
var errrors = errorMessagesBuilder.ToString();
}
}

Related

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();
}

"Multi-Step" commands while using CMD.exe in 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);
}

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);

redirecting process output on richtext box in c#

I want to redirect output of process on richtext box in c# from application.
But the problem is output of process is displyed after a long time till process finishes its work.
here is my code-
StringBuilder outputBuilder = new StringBuilder();
ProcessStartInfo processStartInfo = new ProcessStartInfo();
processStartInfo.CreateNoWindow = true;
processStartInfo.RedirectStandardOutput = true;
processStartInfo.RedirectStandardInput = true;
processStartInfo.UseShellExecute = false;
//processStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
processStartInfo.WorkingDirectory = strHMIModelAppFolder;
processStartInfo.FileName = ConfigurationHandlerInstance.GetHMIModelApplication();
Process process = new Process();
process.StartInfo = processStartInfo;
process.EnableRaisingEvents = true;
process.OutputDataReceived += new DataReceivedEventHandler
(
delegate(object objSender, DataReceivedEventArgs eventArgs)
{
outputBuilder.Append(eventArgs.Data);
outputBuilder.Append(Environment.NewLine);
this.SetText(outputBuilder.ToString());
}
);
process.Exited += new EventHandler(ProcExited);
process.Start();
process.BeginOutputReadLine();
//process.WaitForExit();
//while (!process.HasExited)
//{
// Application.DoEvents();
//}
// use the output
string output = outputBuilder.ToString();
this.SetText(output);
You need to read in the output while it is generated, like this:
proc.Start();
while (!proc.StandardOutput.EndOfStream) {
string line = proc.StandardOutput.ReadLine();
outputBuilder.Append(line);
outputBuilder.Append(Environment.NewLine);
this.SetText(outputBuilder.ToString());
}

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