I'm trying to run a command in the command prompt and run it as a service. This command starts the queue listener from Laravel. I want to run it as a service so this queue listener always runs in the background. When the listener outputs some lines I want to capture these and send an email. I already tried my code running it as a process and it's working, but when I try to run the code as a service it doesn't start.
Process process();
protected override void OnStart(string[] args)
{
process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.Arguments = "/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024";
process.StartInfo.FileName = "cmd.exe";
process.StartInfo.WorkingDirectory = "C:/xampp/htdocs/phpproject";
process.OutputDataReceived += new DataReceivedEventHandler(p_OutputDataReceived);
process.Start();
process.BeginOutputReadLine();
process.WaitForExit();
}
private void p_OutputDataReceived(object sendingProcess,
DataReceivedEventArgs outLine)
{
// Collect the command output.
if (!String.IsNullOrEmpty(outLine.Data.ToString()))
{
sendMail(process.StandardOutput.ToString());
}
}
Edit:
When I comment the process.WaitForExit() line the service runs and the queue listener does its work!.
But the next problem I have is the service never hits the eventhandler. It did when I was running it only as a process. Any clue why this isn't working as a service?
You can try something like this;
private Process process = null;
private DataReceivedEventHandler TheDataReceievedEventHandler;
private void startProcess()
{
ProcessStartInfo processStartInfo = new ProcessStartInfo(#"cmd.exe", #"/C php artisan queue:listen --tries=3 --timeout=0 --memory=1024")
{
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = #"C:/xampp/htdocs/phpproject",
};
if ((process = Process.Start(processStartInfo)) != null)
{
process.EnableRaisingEvents = true;
process.Exited += new EventHandler(ExitedHandler);
TheDataReceievedEventHandler = new DataReceivedEventHandler(StandardOutputHandler);
process.OutputDataReceived += TheDataReceievedEventHandler;
process.BeginOutputReadLine();
process.ErrorDataReceived += TheDataReceievedEventHandler;
process.BeginErrorReadLine();
}
}
private void ExitedHandler(object sender, EventArgs e)
{
throw new NotImplementedException(); // the service you're trying to run closed it self.
}
private void StandardOutputHandler(object sender, DataReceivedEventArgs e)
{
Console.WriteLine(e.Data);
}
The problem is if the service you are trying to run closes it self, it will stop outputting and the process will be closed. Similar to CMD behavior each time you send a command to CMD it will close it self after you receive the error or output.
So if for example; I want to use CMD to see my task-list, i will have to build a loop were i run a CMD process on a regular interval because after each command it will close it self.
Edit
If you can't stop the program using its own logic you will need to kill the process of the program itself. You can do that using the following code;
try // If you have no administrator privilege, try will fire.
{
foreach (Process proc in Process.GetProcessesByName("name process")) // You can get the name by looking in your task manager.
{
proc.Kill();
}
}
catch(Exception ex)
{
// Add error handling
}
Related
All,
I am attempting to execute a series of batch files via a C# winforms app. In this early stage, with a test batch file, I am unable to get the process execution to respect the timeout in my batch file unless i set UseShellExecute = true, which is something i am trying to avoid. My goal is to execute the script file and redirect the output to the GUI as shown in the code here:
Process process;
public void ExecuteScript(string workingDirectory, string batchFileName)
{
if (process != null)
process.Dispose();
process = new Process();
process.StartInfo.WorkingDirectory = workingDirectory;
process.StartInfo.FileName = workingDirectory + batchFileName;
process.StartInfo.Arguments = "";
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardInput = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += proc_OutputDataReceived;
process.Start();
process.BeginOutputReadLine();
process.Exited += OnProcessExit;
}
private void OnProcessExit(object sender, EventArgs e)
{
Console.WriteLine("the script has ended");
}
private void proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
this.Invoke((Action)(() =>
{
textBox1.AppendText(Environment.NewLine + e.Data);
}));
(sender as Process)?.StandardInput.WriteLine();
}
my batch file looks like this:
#echo off
echo This is a running script
timeout /t 10
echo Done sleeping. Will Exit
exit
Is there an appropriate combination of settings i can call to prevent the command window from appearing, while still redirecting the output, and executing the script appropriately?
The problem with your code is that the timeout command is not supported when stdin is redirected. This is a good example of why one should always redirect both stdout and stderr. An error message is actually emitted from the batch file, but because you weren't capturing the stderr stream, you didn't see the error message. All too many questions here on Stack Overflow involving Process scenarios that "don't work" could be easily solved had the person looked at the stderr output.
A work-around to this limitation of the timeout command is to use the waitfor command instead, using a known-nonexistent signal name with a timeout value, e.g. waitfor /t 10 placeholder.
Here is a console program that is entirely self-contained and which demonstrates both the failure of the timeout command when stdin is redirected, as well as the work-around of waitfor:
const string batchFileText =
#"#echo off
echo Starting batch file
timeout /t 5
waitfor /t 5 placeholder
echo Timeout completed
exit";
static void Main(string[] args)
{
string batchFileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid() + ".bat");
try
{
File.WriteAllText(batchFileName, batchFileText);
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = batchFileName,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
};
Process process = new Process
{
EnableRaisingEvents = true,
};
process.OutputDataReceived += Process_OutputDataReceived;
process.ErrorDataReceived += Process_ErrorDataReceived;
process.Exited += Process_Exited;
process.StartInfo = psi;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
}
finally
{
File.Delete(batchFileName);
}
}
private static void Process_Exited(object sender, EventArgs e)
{
WriteLine("Process exited");
}
private static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stdout: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
private static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
WriteLine($"stderr: {DateTime.Now:HH:mm:ss.sss}: {e.Data}");
}
}
Note that the waitfor command writes a message to stderr if the timeout occurs (which it always will in this case). You may or may not want that to show up in the captured stderr stream. If not, you can redirect the stderr of that command specifically by using 2>nul. E.g. waitfor /t 10 placeholder 2>nul.
I've been looking around google and SO for many hours now as I feel this question must have been asked, but I cannot find the right answers.
Many of the most similar questions ask how to send A command or series of them, at start up of the process. The difference I'm looking for help with is being able to continually send and receive commands to the process once I've started it. Essentially nesting an actual command prompt inside a WPF window.
This is how I've been trying to run the process. But I can't figure out how tell the process to wait for a input response.
public void startProcess()
{
using (Process process = new Process())
{
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.WorkingDirectory = "C:\\";
process.StartInfo.FileName = #"cmd.exe"; //
process.OutputDataReceived += ProcessOutputDataHandler;
process.ErrorDataReceived += ProcessErrorDataHandler;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
using (sWriter = process.StandardInput)
{
if (sWriter.BaseStream.CanWrite)
{
sWriter.WriteLine(#"cd C:\Users\username\Desktop\SomeFolder\ ");
sWriter.WriteLine(#"Run_Script.pl");
}
}
process.WaitForExit();
}
}
private void btnSend_Click(object sender, RoutedEventArgs e)
{
// this method won't work, but this is the general idea I'm trying to figure out to implement.
if (sWriter.BaseStream.CanWrite)
{
sWriter.WriteLine(txtInput.Text);
}
}
public void ProcessOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
// log is a output textbox that is updated on a timer
txtLog.Dispatcher.BeginInvoke(new Action(() => { log += outLine.Data; }), null);
}
public void ProcessErrorDataHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
txtLog.Dispatcher.BeginInvoke(new Action(() => { log += outLine.Data; }), null);
}
I tagged perl on this just because I'm trying to run a perl script with this.
I have a Process that needs to update the console in realtime based on the output. But its not working. The console just opens and closes, and the Process runs in the background. I can not figure out what I am doing wrong. Here is my code:
private static StringBuilder sortOutput = null;
static void Main(string[] args)
{
Process process;
process = new Process();
process.StartInfo.FileName = "C:\\ffmbc\\ffmbc.exe";
//process.StartInfo.Arguments = "-i new5830df.mxf -an ";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
sortOutput = new StringBuilder("");
process.OutputDataReceived += new DataReceivedEventHandler(OutputHandler);
process.Exited += new EventHandler(myProcess_Exited);
process.StartInfo.RedirectStandardInput = true;
process.Start();
process.BeginOutputReadLine();
}
private static void OutputHandler(object sender, DataReceivedEventArgs outLine)
{
string line;
line = (outLine.Data.ToString());
Console.WriteLine(line);
}
private static void myProcess_Exited(object sender, System.EventArgs e)
{
Console.WriteLine("Proccess Finished");
}
Make sure to call process.WaitForExit() to block until the process exits.
Your Main() has exited, so the console closes. You need to wait until the called program completed, before you exit your Main function.
hi i'm trying to build a parser for my System to Manage my Tekkit Server i am using C# but i have RedirectStandardOutput on my Tekkit Server process and there is a method set-up to then send that output to my console after adding to a List but it's not adding to a List<string>
Here is my code:
public void StartServer(string maxMem, string minMem, string path)
{
ThreadStart server = new ThreadStart(delegate() { StartServerThread(maxMem, minMem, path); });
server.Invoke();
}
private void StartServerThread(string maxMem, string minMem, string TekkitPath)
{
try
{
TekkitServer.StartInfo.FileName = "java";
TekkitServer.StartInfo.Arguments = String.Format("-Xmx{0} -Xms{1} -jar \"" + TekkitPath + "\" -nojline nogui", maxMem, minMem);
TekkitServer.StartInfo.UseShellExecute = false;
TekkitServer.StartInfo.RedirectStandardInput = true;
TekkitServer.StartInfo.RedirectStandardOutput = true;
TekkitServer.OutputDataReceived += new DataReceivedEventHandler(TekkitServer_OutputDataReceived);
IsStarted = TekkitServer.Start();
TekkitServerInput = TekkitServer.StandardInput;
}
catch (Exception)
{
}
}
void TekkitServer_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
/*B*/recordedData.Add(e.Data);
Console.Out.WriteLine(e.Data);
}
Where /*B*/ is a break point, the breakpoint is never activating
By default, the standard output is directed at the console window.
If you need to do something with it, you need to redirect it, hence, you need to set RedirectStandardOutput = true; for the event to be fired.
Edit: This is my working code (with error handling and logging omitted):
public int ExecuteCommand(CommandParameters parameters)
{
Process process = new Process();
process.StartInfo.RedirectStandardInput = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.OutputDataReceived += StdOutputHandler;
process.ErrorDataReceived += StdErrorHandler;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = ...;
process.StartInfo.Arguments = ...;
process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();
process.WaitForExit(parameters.Timeout);
return process.ExitCode;
}
private void StdOutputHandler(object sendingProcess, DataReceivedEventArgs outdata)
{
if (!string.IsNullOrEmpty(outdata.Data))
{
OutputMessages.Add(outdata.Data);
}
}
Most likely the missing link in your code is the BeginOutputReadLine that actually gets the handler method on it's way.
Also, I use a fresh Process object and that I wait on it to finish it's job, so no interference with previous calls is possible.
I'm trying to write a Console wrapper WPF gui that simply runs a selection of .bat files, I'd like to be able to view any output from the .bat files "live" (as if it were running in cmd).
I've looked into OutputDataReceived and event handlers which append text and then sent this to the screen, however it still waits until the Process has finished before anything appears on the screen.
How do I get the output from the .bat to appear in "real time"?
Snippits of my code so far (this is in a form):
The form has one button (go) and one multi-line text field (textArea).
private void go_Click(object sender, EventArgs e)
{
ExecuteCommand();
}
public void ExecuteCommand()
{
int ExitCode;
ProcessStartInfo ProcessInfo;
Process Process;
//ProcessInfo = new ProcessStartInfo("cmd.exe", "/C z:\foo.bat");
ProcessInfo = new ProcessStartInfo(#"z:\foo.bat"); ;
ProcessInfo.CreateNoWindow = true;
ProcessInfo.UseShellExecute = false;
ProcessInfo.RedirectStandardError = true;
ProcessInfo.RedirectStandardOutput = true;
Process = new Process();
Process.StartInfo = ProcessInfo;
Process.OutputDataReceived += new DataReceivedEventHandler(OutputToTextArea);
Process.Start();
// Start the asynchronous read of the sort output stream.
Process.BeginOutputReadLine();
Process.WaitForExit();
ExitCode = Process.ExitCode;
Process.Close();
}
private int numOutputLines = 0;
private void OutputToTextArea(object sendingProcess, DataReceivedEventArgs outLine)
{
// Collect the sort command output.
if (!String.IsNullOrEmpty(outLine.Data))
{
numOutputLines++;
this.AppendToTextArea("[" + numOutputLines.ToString() + "] - " + outLine.Data + Environment.NewLine);
}
}
private void AppendToTextArea(string s)
{
if (this.textArea.InvokeRequired)
{
// It's on a different thread, so use Invoke.
this.BeginInvoke (new MethodInvoker(() => textArea.AppendText(s)));
} else {
textArea.AppendText(s);
}
}
Where my foo.bat is just a for loop:
ECHO OFF
FOR /L %%i IN (1,1,10) DO (
echo %%i
ping -n 2 127.0.0.1 >nul
)
Well yeah, you're currently blocking the main thread (which is the UI thread) as you wait for process exit in ExecuteCommand, which is directly called from the UI thread (in go_Click).
Just start a new thread (or use a ThreadPool) (Winforms example):
private void button1_Click(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(this.ExecuteCommand));
}
public void ExecuteCommand(object state)
{
...
}
If you're using WPF, you probably want to use a BackgroundWorker.
If you wish to keep it simple you can just start a command prompt with the /K argument and pass in the batch file.
string arguments = #"z:\foo.bat";
Process.Start("cmd.exe", "/K " + arguments);
The cmd.exe /K opens and command prompt and runs your foo.bat but remains on the screen.