I wrote a small program in C# NET a while back to keep a Java process running. I am about to deploy it to a bunch of servers and I am working on fixing up some of the code now. As it stands, I don't think I have this setup right.
What would be the best way to keep the process running that I am creating in my LaunchMinecraft() function? I want to have it so as long as my process is running, it continues to restart this process if it crashes.
static void Main(string[] args)
{
// Launch the Application
LaunchMinecraft("minecraft_server.jar", "512");
}
public static void LaunchMinecraft(string file, string memory)
{
string memParams = "-Xms" + memory + "M" + " -Xmx" + memory + "M ";
string args = memParams + "-jar " + file + " nogui";
ProcessStartInfo processInfo = new ProcessStartInfo("java.exe", args);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardInput = true;
processInfo.RedirectStandardError = true;
try
{
using (Process minecraftProcess = Process.Start(processInfo))
{
// Store Process Globally to access elsewhere
JavaProcess = minecraftProcess;
// Creates a Repeating Poll Timer (30 Seconds)
// Use this to keep the Minecraft's Process Affinity/Priority the same as the Daemon
PollTimer = new System.Timers.Timer();
PollTimer.Elapsed += new ElapsedEventHandler(Event_PollProcess);
PollTimer.Interval = 5000;
PollTimer.Start();
Console.WriteLine("Minecraft Process Started.");
// Setup Callback for Redirected Output/Error Streams
minecraftProcess.OutputDataReceived += new DataReceivedEventHandler(Handler_ProcessOutput);
minecraftProcess.ErrorDataReceived += new DataReceivedEventHandler(Handler_ProcessError);
// Steam Writer
streamWriter = minecraftProcess.StandardInput;
minecraftProcess.BeginOutputReadLine();
minecraftProcess.BeginErrorReadLine();
while (minecraftProcess.HasExited == false)
{
String strInput = Console.ReadLine();
SendProcessCmd(strInput);
}
minecraftProcess.WaitForExit();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
The Process class itself cant prevent your external program from shutting down, you have to use the Exited event as #Jalal mentiones or poll the HasExited property of the process.
Related
I am trying to capture the stdout of a process started in my code but still put the output to the console app that it is running.
My project is a .net core Windows application.
I attach a procedure thread to the process to get the stdout and in that thread I do a console write to also output it to the attached application. I suspect that it is trying to write it to my applications non existent console instead of the joined process. If so, is there a way to get the output and still have it written to the executed processes console?
Code -
private Process proc = null;
private void WriteStandardOutput()
{
using (StreamWriter writer = File.CreateText(exeOutputPath + "\\_out.txt"))
using (StreamReader reader = proc.StandardOutput)
{
writer.AutoFlush = true;
for (; ; )
{
string textLine = reader.ReadLine();
if (textLine == null)
break;
writer.WriteLine(textLine);
Console.Out.WriteLine(textLine);
}
}
if (File.Exists(exeOutputPath + "\\_out.txt"))
{
FileInfo info = new FileInfo(exeOutputPath + "\\_out.txt");
// if the error info is empty or just contains eof etc.
if (info.Length < 4)
info.Delete();
}
}
///////////////function code
int exitCode;
if (!File.Exists(exePath) && !exePath.Contains("cmd.exe"))
throw new Exception("No executable specified for RunExtApp - " + this.name);
if (exePath.Contains("cmd.exe"))
runParams = "/K " + runParams;
//Start the executable
extApp = new ProcessStartInfo();
extApp.Arguments = runParams;
extApp.FileName = exePath; // Path.GetFileName(exePath);
extApp.WorkingDirectory = Path.GetDirectoryName(exePath);
extApp.UseShellExecute = false;
extApp.RedirectStandardOutput = true;
extApp.RedirectStandardError = true;
// Do you want to show a console window?
//extApp.WindowStyle = Hidden.ProcessWindowStyle;
//extApp.CreateNoWindow = true;
// Run the external process & wait for it to finish
using (proc = Process.Start(extApp))
{
Thread stdOutThread = new Thread(new ThreadStart(WriteStandardOutput));
stdOutThread.IsBackground = true;
stdOutThread.Name = "StandardOutput";
stdOutThread.Start();
proc.WaitForExit();
stdOutThread.Join();
// Retrieve the app's exit code
exitCode = proc.ExitCode;
proc.Close();
if (exitCode > 0)
throw new Exception("Failed to run external code - " + exePath + ". exit code - " + exitCode.ToString());
}
Test Exe - .net framework console application
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleWriteTester
{
class Program
{
static void Main(string[] args)
{
for(int i = 0; i < 30; i ++)
{
Console.WriteLine("Line " + i.ToString());
System.Threading.Thread.Sleep(1000);
}
}
}
}
var origFilePath = "C:/MyOriginalFile.webm";
var processedFilePath = "C:/MyProcessedFile.webm";
RunFfmpeg($"-i \"{origFilePath}\" -af \"silenceremove=1:0.1:0.001, areverse, silenceremove=1:0.1:0.001, areverse\" \"{processedFilePath}\" -y");
// fails with IOException as the file presumably not been released by FFmpeg
System.IO.File.Delete(origFilePath);
When the file is deleted, the following exception is frequently (maybe 80% of the time) thrown:
IOException: The process cannot access the file 'C:\MyOriginalFile.webm' because it is being used by another process.
The call to create and run the FFmpeg process goes like this:
private List<string> RunFfmpeg(string arguments)
{
using (var process = new Process())
{
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.FileName = _hostingEnvironment.ContentRootPath + _settings.FfmpegPath;
process.StartInfo.Arguments = arguments;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
// ffmpeg only uses strerr for its output
var output = new List<string>();
process.ErrorDataReceived += new DataReceivedEventHandler((s, e) => {
if (e.Data != null)
output.Add(e.Data);
});
process.Start();
process.BeginErrorReadLine();
process.WaitForExit();
return output;
}
}
It appears that even though the process has completed when the file is deleted, FFmpeg is still processing or has otherwise not released it.
Is this behaviour expected? How do I go about ensuring that FFmpeg has finished processing the files before continuing?
If it were me I'd run this on a background thread and tap into the process.Exited event and try to delete the File in there.
The MSDN Documentation for the Process Exit Event uses this strategy and polls for 30 seconds till the Exited event fires and checks the ExitCode. Depending on the ExitCode, you could give it more time, check if the file is still locked or perform another action until process.ExitCode == 0
private Process myProcess = new Process();
private int elapsedTime;
private bool eventHandled;
public void RunFfmpeg(string arguments)
{
elapsedTime = 0;
eventHandled = false;
try
{
myProcess.StartInfo.FileName = _hostingEnvironment.ContentRootPath + _settings.FfmpegPath;
myProcess.StartInfo.Arguments = arguments;
myProcess.StartInfo.CreateNoWindow = true;
myProcess.EnableRaisingEvents = true;
myProcess.Exited += new EventHandler(myProcess_Exited);
myProcess.Start();
}
catch (Exception ex)
{
Console.WriteLine("An error occurred trying to print \"{0}\":" + "\n" + ex.Message, fileName);
return;
}
// Wait for Exited event, but not more than 30 seconds.
const int SLEEP_AMOUNT = 100;
while (!eventHandled)
{
elapsedTime += SLEEP_AMOUNT;
if (elapsedTime > 30000)
{
break;
}
Thread.Sleep(SLEEP_AMOUNT);
}
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
eventHandled = true;
Console.WriteLine("Exit time: {0}\r\n" +
"Exit code: {1}\r\nElapsed time: {2}", myProcess.ExitTime, myProcess.ExitCode, elapsedTime);
}
The code looks lengthy but it's a simple program.
I have built a console app (TakeScreenshots) that will take website screenshots from firefox, chrome & ie in that order & save them in a folder. When I manually run TakeScreenshots.exe, all 3 screenshots are saved.
Now, I have built another console app (MyApp) that will execute TakeScreenshots.exe. But in this way, only the firefox screenshot is saved and not of the other 2. There are no exceptions. It just says "Process Complete". I guess, MyApp is not waiting for the TakeScreenshots to complete.
How can I fix this.
[TakeScreenshots will later be placed in few remote computers & run by MyApp]
TakeScreenshots code:
private static string[] WebDriversList = ["firefox","chrome","internetexplorer"];
private static void TakeAPic()
{
string url = "http://www.google.com";
string fileNamePrefix = "Test";
string snapSavePath = "D:\\Pics\\";
foreach (string wd in WebDriversList)
{
IWebDriver NewDriver = null;
switch (wd.ToLower())
{
case "firefox":
using (NewDriver = new FirefoxDriver())
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
case "chrome":
using (NewDriver = new ChromeDriver(WebDriversPath))
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
case "internetexplorer":
using (NewDriver = new InternetExplorerDriver(WebDriversPath))
{
if (NewDriver != null)
{
CaptureScreenshot(NewDriver, url, fileNamePrefix, snapSavePath);
}
}
break;
}
if (NewDriver != null)
{
NewDriver.Quit();
}
}
}
private static void CaptureScreenshot(IWebDriver driver,string url,string fileNamePrefix,
string snapSavePath)
{
driver.Navigate().GoToUrl(url);
Screenshot ss = ((ITakesScreenshot)driver).GetScreenshot();
ICapabilities capabilities = ((RemoteWebDriver)driver).Capabilities;
ss.SaveAsFile(snapSavePath + fileNamePrefix + "_" + capabilities.BrowserName + ".png",
ImageFormat.Png);
}
MyApp code:
static void Main(string[] args)
{
ExecuteTakeScreenshot();
Console.WriteLine("PROCESS COMPLETE");
Console.ReadKey();
}
private static void ExecuteTakeScreenshot()
{
ProcessStartInfo Psi = new ProcessStartInfo("D:\\PsTools\\");
Psi.FileName = "D:\\PsTools\\PsExec.exe";
Psi.Arguments = "/C \\DESK101 D:\\Release\\TakeScreenshots.exe";
Psi.UseShellExecute = false;
Psi.RedirectStandardOutput = true;
Psi.RedirectStandardInput = true;
Process.Start(Psi).WaitForExit();
}
Update:
It was my mistake. Initially WebDriversPath was assigned "WebDrivers/". When I changed it to the actual path "D:\WebDrivers\", it worked. But I still dont understand how it worked when TakeScreenshots.exe was run manually and it doesn't when run from another console
In similar problems I have had success with waiting for input idle first. Like this:
Process process = Process.Start(Psi);
process.WaitForInputIdle();
process.WaitForExit();
You could try this. For me it was needed to print a pdf using Adobe Reader and not close it to early afterwards.
Example:
Process process = new Process();
process.StartInfo.FileName = DestinationFile;
process.StartInfo.Verb = "print";
process.Start();
// In case of Adobe Reader the following statement is needed:
process.WaitForInputIdle();
process.WaitForExit(2000);
process.WaitForInputIdle();
process.Kill();
I'm written a basic application that watches one network File Share directory and when a new file is created in that directory it then fires an external application that parses that file. I've also tested with a local directory and everything worked. I've tested it with debug code like so and the application will work:
#if DEBUG
Service1 mysService1 = new Service1();
mysService1.OnDebug(); //calls onStart(Null);
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#else
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
new Service1()
};
ServiceBase.Run(ServicesToRun);
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
#endif
Then when I switch to release, build and install the service nothing will happen. So since the path worked i figured this was down to permissions?
I went to Task Manager>Services>Services..> right clicked on my service>Properties>Log On> and have given it my credentials.
I've also gone to the root folder of where my application is on the network Right click>Security>Edit. Then I gave my account Modify, Read & Execute, Listing folder contents, and Read permissions and of course those permissions propagated to all of the folders under its hierarchy.
I even tried mapping it to the network drive Z and trying to access it that one.
With everything I tried the service still refuses to do anything. I've added more debugging code where I would check if the file was changed or deleted and write it down in text files. Once again it would work and detect those changes in debug but upon install nothing would happen.
I'm pretty sure this is probably still some kind of permission issue can anyone tell me what else I could do to remedy this issue?
EDIT:
There was a request for more code. Also note that my Utility class was able to produce a stack trace. It lead to an issue with System.IO.FileStream error started from this line of code in the FileWatcher.cs System.IO.File.AppendAllText(PathLocation() + "\logFile.txt", Environment.NewLine + " Started! " + DateTime.Now.ToString());
Service1.cs:
public Service1()
{
InitializeComponent();
}
public void OnDebug()
{
OnStart(null);
}
protected override void OnStart(string[] args)
{
try
{
FileWatcher f = new FileWatcher();
}
catch (Exception e)
{
new ErrorMailer(e, DateTime.Now.ToString());
}
}
FileWatcher.cs:
private FileSystemWatcher _fileWatcher;
static ProcessStartInfo start;
public FileWatcher()
{
System.IO.File.AppendAllText(PathLocation() + "\\logFile.txt", Environment.NewLine + " Started! " + DateTime.Now.ToString());
_fileWatcher = new FileSystemWatcher(PathLocation());
HasMailClerkBeenRun = false;
start = new ProcessStartInfo();
_fileWatcher.Created += new FileSystemEventHandler(_fileWatcher_Created);
_fileWatcher.Deleted += new FileSystemEventHandler(_fileWatcher_Deleted);
_fileWatcher.Changed += new FileSystemEventHandler(_fileWatcher_Changed);
_fileWatcher.EnableRaisingEvents = true;
}
{
string value = String.Empty;
value = #"Z:\MyAppDirectory\DirectoryFileWatcherIsWatching"; //#"\\FileShareName\RootDirectory\MyAppDirectory\DirectoryFileWatcherIsWatching";
return value;
}
void _fileWatcher_Changed(object sender, FileSystemEventArgs e)
{
System.IO.File.AppendAllText(PathLocation() + "\\logFile.txt", Environment.NewLine + "Started from the bottom now we changed! " + DateTime.Now.ToString());
}
void _fileWatcher_Deleted(object sender, FileSystemEventArgs e)
{
System.IO.File.AppendAllText(PathLocation() + "\\logFile.txt", Environment.NewLine + "Started from the bottom now we deleted! " + DateTime.Now.ToString());
}
void _fileWatcher_Created(object sender, FileSystemEventArgs e)
{
System.IO.File.AppendAllText(PathLocation() + "\\logFile.txt", Environment.NewLine + "Started from the bottom now we here! " + DateTime.Now.ToString());
LaunchExternalApp();
}
private void LaunchExternalApp()
{
start.UseShellExecute = false;
start.RedirectStandardError = true;
start.RedirectStandardInput = true;
start.RedirectStandardOutput = true;
start.CreateNoWindow = true;
start.ErrorDialog = false;
start.WindowStyle = ProcessWindowStyle.Hidden;
start.FileName =#"Z:\MyAppDirectory\AppExcutionLocation\MyApp.exe"
Thread thread1 = new Thread(new ThreadStart(A));
thread1.Start();
thread1.Join();
}
static void A()
{
using (Process proc = Process.Start(start))
{
proc.WaitForExit();
//HasMailClerkBeenRun = true;
// Retrieve the app's exit code
/// int exitCode = proc.ExitCode;
}
Thread.Sleep(100);
Console.WriteLine('A');
}
Check that the service is running under the same account as the interactive user account that you are using when checking the share or mapping the drive. If not, try switching it to use this account.
I have no idea why you are calling System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite), but I suspect that removing that line will solve your problem. If it doesn't, please post more code.
I hate to post about this again but I answered my own last post thinking I fixed it (which I didn't). Basically when my c# .NET application shuts down, I want to remove the running Java process that it created. The initial problem was that I was trying to save processID to a static class member variable (which obviously didnt work). I found a Global Class example online and used that instead, however it still isn't shutting down the process.
Debugging it isn't working properly. I guess it just creates a new instance of the application rather than running the one that I built, and even setting the working directory to the "Bin" directory doesn't work. So I am just having to run my .exe from the Bin directory at the moment.
namespace MinecraftDaemon
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting Minecraft Daemon...");
Arguments CommandLine = new Arguments(args);
// Hook ProcessExit Event
AppDomain.CurrentDomain.ProcessExit += new EventHandler(Current_ProcessExit);
if (CommandLine["file"] != null && CommandLine["memory"] != null)
{
// Launch the Application (Command Line Parameters)
LaunchMinecraft(CommandLine["file"], CommandLine["memory"]);
}
else
{
// Launch the Application (Default Parameters)
LaunchMinecraft("minecraft_server.jar", "1024");
}
}
public static void LaunchMinecraft(String file, String memoryValue)
{
String memParams = "-Xmx" + memoryValue + "M" + " -Xms" + memoryValue + "M ";
String args = memParams + "-jar " + file + " nogui";
ProcessStartInfo processInfo = new ProcessStartInfo("java.exe", args);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
try
{
using (Process minecraftProcess = Process.Start(processInfo))
{
GlobalClass.ProcessID = minecraftProcess.Id;
Console.WriteLine("Process ID is " + GlobalClass.ProcessID);
minecraftProcess.WaitForExit();
}
}
catch
{
// Log Error
}
}
static void Current_ProcessExit(object sender, EventArgs e)
{
// Loop the Current Windows Processes
foreach (Process winProcess in Process.GetProcesses())
{
Console.WriteLine("WinProcessID is " + winProcess.Id + " GlobalClass.ProcessID is " + GlobalClass.ProcessID);
// If this is our Process, shut it down
if (winProcess.Id == GlobalClass.ProcessID)
{
Process.GetProcessById(GlobalClass.ProcessID).Kill();
}
}
}
}
}
This was resolved by switching from catching the Event AppDomain.CurrentDomain.ProcessExit to using SetConsoleCtrlHandler();