Killing external process when application dies - c#

I've been working on a small piece that calls an external executable (ffmpeg in my case)
And then I wrote a test and used test runner in a debug mode, now if I stop debugging (terminate) it still runs ffmpeg. I tried to kill the process in the finalizer and with IDisposable - it still runs. How can I make sure that the process never will be left like that, and if the caller dies or fails or gets stopped by any means, the ffmpeg executable guaranteed to be killed.
I run the process as usual (nothing fancy)
var processInfo = new ProcessStartInfo(ffmpegPath)
{
UseShellExecute = false,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
};
using (var ffmpegProc = new Process())
{
ffmpegProc.EnableRaisingEvents = true;
ffmpegProc.StartInfo = processInfo;
ffmpegProc.Start();
ffmpegProc.WaitForExit();
}

You should use JobObject
With JobObject you can add child processes. So if the main process is killed or closed the os will terminate all child processes.
source: http://www.xtremevbtalk.com/showpost.php?p=1335552&postcount=22
Another solution is to pass to child object the parent PID.
Any child have to check for parent PID existence and if not found kill itself

There is nothing you can do when your process is terminated. There is no code run in your process after someone kills it with TerminateProcess call.
In case of more graceful cases (i.e. unhandled exception) you may have global handler that does something close to what you want.

Related

Batch script calling robocopy in Process won't terminate

If process.Kill() is called from another thread or even another program, the process never comes out of WaitForExit() if the batch script used robocopy.exe until it is finished as if it wasn't killed.
Robocopy.exe is called from the batch script. Every other script or program ends as you'd expect.
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "batch.bat";
startInfo.UseShellExecute = false;
startInfo.CreateNoWindow = true;
startInfo.RedirectStandardOutput = true;
startInfo.OutputDataReceived += CaptureHandler;
startInfo.RedirectStandardError = true;
startInfo.ErrorDataReceived += CaptureHandler;
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
process.WaitForExit();
The batch script looks like:
#echo off
call "robocopy.exe" "somedir" "somedest" /mir /fp /ndl /njh /njs /ns
I have a feeling it has to do with the output handlers.
I tried using process.CancelErrorRead and process.CancelOutputRead() as well after the Kill() call and before, no luck.
Oddly, if you use process.WaitForExit(timeout) overload, it will return true immediately after Kill() from the other thread. However, it's lying. The process is still running! If you try process.WaitForExit() again, as per the MSDN doc, it will still wait for the process to finish despite HasExited saying true.
To ensure that asynchronous event handling has been completed, call the WaitForExit() overload that takes no parameter after receiving a true from this overload.
https://msdn.microsoft.com/en-us/library/ty0d8k56(v=vs.110).aspx
You are successfully killing the batch processor (cmd.exe) but doing so won't kill robocopy, which is a separate process.
It doesn't seem to be documented, but when we look at the .NET source code it turns out that the Process.WaitForExit() method doesn't just wait for the process to exit, it also waits for end-of-file on the standard output and standard error streams. In this scenario, that means that it waits for robocopy to finish even after the batch processor has been killed.
(The overload of Process.WaitForExit with a timeout does not have this extra logic.)
I think this constitutes a bug in the .NET framework. At the very least, it should be documented.
As a workaround, you can use .HasExited and/or the version of WaitForExit with a timeout to determine whether the process has exited or not. Of course, in your scenario you might prefer to wait for grandchild processes, in which case your code is already behaving as desired.
I ran into the same problem. In my case, dropping the /mt switch from the RoboCopy argument list seemed to fix the issue.
Having followed up on Harry Johnston's helpful answer, I found that the process completes normally when you avoid RedirectStandardOutput = true. If this isn't an acceptable solution I found that using robocopy's /LOG:"C:\logs\robocopy.txt" switch to send its standard output to an external log file also works (although you lose the ability to get the file/directory log output from the process object itself).
Looks like right now the only way to do this without the application knowing to terminate Robocopy.exe specifically is to do kill the children of the script process before killing the script itself:
Kill process tree programmatically in C#
/// <summary>
/// Kill a process, and all of its children, grandchildren, etc.
/// </summary>
/// <param name="pid">Process ID.</param>
private static void KillProcessAndChildren(int pid)
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher
("Select * From Win32_Process Where ParentProcessID=" + pid);
ManagementObjectCollection moc = searcher.Get();
foreach (ManagementObject mo in moc)
{
KillProcessAndChildren(Convert.ToInt32(mo["ProcessID"]));
}
try
{
Process proc = Process.GetProcessById(pid);
proc.Kill();
}
catch (ArgumentException)
{
// Process already exited.
}
}

How can a process be blocking another process (that it spawned)?

I have written a utility program that is used to start and stop a specific process. Now, in testing it, it somehow seems to be blocking the process that it spawns!
It uses named system events (see System.Threading.EventWaitHandle). After starting the process, it waits for the event to be set:
private static int StartRavenDB(string fileName, string workingDirectory, string arguments)
{
var process = new Process
{
StartInfo =
{
FileName = fileName,
WorkingDirectory = workingDirectory,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true
}
};
process.Start();
var eventWaitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, "StartStopRavenDBUtility");
eventWaitHandle.Reset();
eventWaitHandle.WaitOne();
process.StandardInput.WriteLine("q");
process.WaitForExit();
return process.ExitCode;
}
Now, the RavenDB process that starts is a web server listening on localhost:8080.
Shortly after starting that process using the above utility, the process does not respond to web requests. It keeps timing out. As soon as I kill the utility process, everything starts to work normal.
For the record, I'm 100% the EventWaitHandle is not set yet - the RavenDB process is there, but it doesn't behave as it should.
I don't know what is happening or why, it's a completely separate process. What causes this problem?
You should subscribe to the OutputDataReceived event or at least read the redirected standard output, to avoid blocking the thread. From the documentation:
These dependencies can cause deadlock conditions. When the caller
reads from the redirected stream of a child process, it is dependent
on the child. The caller waits for the read operation until the child
writes to the stream or closes the stream. When the child process
writes enough data to fill its redirected stream, it is dependent on
the parent. The child process waits for the next write operation until
the parent reads from the full stream or closes the stream. The
deadlock condition results when the caller and child process wait for
each other to complete an operation, and neither can continue. You can
avoid deadlocks by evaluating dependencies between the caller and
child process.

How to wait until MSTSC.exe exits

I have created a management application that also allows to quickly access a remote desktop session to remote machines. I need to wait until the process ends, so I can close the VPN connection to the remote server. Everything works fine, except waiting for the process to end.
The following code is being used to start the MSTSC process and wait until it ends:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe"),
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
The Exited event is raised almost immediately after the program starts. When I replace mstsc.exe with notepad.exe everything works as expected. I thought that MSTSC might fork itself and abort the initial process.
But it is possible to wait for MSTSC to end using the following command (from the commandline):
start /wait mstsc.exe
This command doesn't return until I exit the remote desktop session. Given that information I replaced my code with this:
var process = new Process
{
StartInfo = new ProcessStartInfo("cmd.exe"),
Arguments = "/c start /wait mstsc.exe",
EnableRaisingEvents = true
};
process.Exited += (o, e) => Console.WriteLine("Process stopped.");
process.Start();
Console.ReadLine();
This would run CMD.exe and it will issue the start /wait mstsc.exe command. If that ends, the CMD process ends as well and I'm fine (with a nasty workaround, but okay). Unfortunately, this doesn't happen. The CMD process terminates immediately. Somebody knows what I am doing wrong?
process.WaitForExit();
Won't work because mstsc on start opens new copy of itself and closes original.
process.WaitForExit();
process = Process.GetProcessesByName(process.ProcessName).First();
process.WaitForExit();
Will work but it's awful workaround.
Update 1:
It seems that mstsc closes original process but NOT it's output stream!
So you can wait for process StandardOutput to close.
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
process.StandardOutput.ReadToEnd(); //This will wait for stream to close.
Or if you don't want to block current thread:
var process = new Process
{
StartInfo = new ProcessStartInfo("mstsc.exe") { UseShellExecute = false, RedirectStandardOutput = true }
};
process.Start();
var outputResultPromise = process.StandardOutput.ReadToEndAsync();
outputResultPromise.ContinueWith(o=> Console.WriteLine("Stream closed"));
Console.ReadLine();
Here is the link at MSDN about starting mstsc,
It might be answer to your problem with mstsc closing immediately after running (raising Exited event). Try changing in Visual Studio target platform to AnyCPU.
Let's say your machine is 64bit Windows, your app is 32bit. The app runs 32bit mstsc. 32bit mstsc detects that Windows is 64bit, tries to close itself and run 64bit mstsc (Exited event is raised at that moment even though mstsc starts GUI window).
Changing target platform solved my issue.
There are multiple MSTSC processes running, so it's difficult to wait for one. What I don't understand is that CMD.EXE can do it when I use the start /wait command.
this worked with me:
process.Start();
Thread.Sleep(2000);
while(getNumProcesses() > 0)
process.WaitForExit();
private static int getNumProcesses()
{
Process[] myProcesses = Process.GetProcessesByName("mstsc");
return myProcesses.Length;
}
You cannot wait for mstsc.exe process. Say exactly, you cannot simply wait for end of remote desktop. When I observed mstsc.exe process by Process Monitor, mstsc passed his work to svchost, mstsc.exe ended, but remote desktop was still run.
But I wrote script for remoting application.
Script remoteCmd.cmd starts remoteApplication, remote machine creates a temp file ( \\tsclient\c..\temp\xxx) and remoteCmd.cmd waits until temp file exists.
See
https://github.com/turzik/WindowsScripts/tree/master/remoteApp
You need to call WaitForExit() after you call Start():
process.Start();
process.WaitForExit();
This overload causes the current thread to wait indefinitely to wait until the process exits. There's also an overload that allows you to specify the number of milliseconds you'd like to wait.

Wait properties windows dialog before continue script

I had a piece of code to display the properties window of a file,
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"C:\Users\nyongrand\Desktop\Internet Download Manager.lnk";
psi.Verb = "properties";
Process process = Process.Start(psi);
process.WaitForExit(); //This give me exception, Object reference not set to an instance of an object.
what I want is to wait until the window properties is closed, because if my code closed the properties window will be closed as well, I need a solution between, my code is able to wait for the properties window closed, or my code can exit without closing the properties window.
The exception you're getting means that process is null at the time you try to call its WaitForExit member method. So the question you should be asking is why.
Start with the documentation for the overload of the Process.Start function that you're calling to see what it actually returns. Sure enough, it returns a Process object, but only under certain conditions:
Return Value
Type: System.Diagnostics.Process
A new Process component that is associated with the process resource, or null if no process resource is started (for example, if an existing process is reused).
And, from the "Remarks" section:
Note: If the address of the executable file to start is a URL, the process is not started and null is returned.
So, if an existing process is re-used, the Process.Start method will return null. And you cannot call methods on null.
Try replacing
Process process = Process.Start(psi);
with
Process process = new Process();
if(process.Start(psi))
{
process.WaitForExit();
}
else
{
//Do something here to handle your process failing to start
}
The problem you face with your code is that Process.Start() returns a Boolean. It's not a factory for Process objects.

Getting a Process to terminate

I have a process object setup like the following:
Process p = new Process();
p.StartInfo.FileName = command;
p.StartInfo.UseShellExecute = true;
p.StartInfo.Arguments = String.Format(
commandArguments,
destinationLocation,
sourceLocation,
sourceDirName,
(string.IsNullOrEmpty(revisionNotes.Text)) ? "" : revisionNotes.Text);
(where undefined values are supplied externally to this code and are valid). The process in question launches and properly executes with p.Start(); but i need to catch it on termination. The console window flashes up briefly and goes away which would seem to indicate that the process is done, but none of the relevant events are fired (OutputDataRecieved, Exited, etc) and it's like the process never ends. (I'm trying to execute a lua script with some parameters if that's relevant). Can someone help me get this process to stop correctly?
WaitForExit
Have you set the EnableRaisingEvents property of the process to True? You won't catch the Exited event without it.

Categories