I am working on a .NET console app which needs to clean up resources on exit. The problem I'm running into is that I don't get any notification if the cmd parent is closed via the console window [X], via Task Manager/Process Explorer or programmatically with WM_CLOSE. I can live with not being able to handle Kill Process from Task Mgr. or ProcExp. WM_CLOSE of the parent console is the most likely way that this application will be closed before it's finished processing.
Here are the events I've tried to register for so far:
AppDomain.CurrentDomain.ProcessExit += CurrentDomainProcessExit;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainUnhandledException;
Console.CancelKeyPress += ConsoleCancelKeyPress;
Application.ApplicationExit += ApplicationApplicationExit;
Process parentProcess = ProcessInfo.GetParentProcess(
Process.GetCurrentProcess());
parentProcess.Disposed += ParentDisposed;
parentProcess.Exited += ParentExited;
Process grandParentProcess = ProcessInfo.GetParentProcess(parentProcess);
grandParentProcess.Disposed += GrandParentDisposed;
grandParentProcess.Exited += GrandParentExited;
These events fire properly when I send a CTRL+C from the console or the application finishes uninterrupted. But none of them fire when the parent app (cmd console) is closed. (The parent/grandparent processes aren't CLR so I'm not sure I would ever receive those Disposed/Exited events. Those are just shots in the dark.) I've looked at some pInvoke stuff but I would rather not go down that road if .NET is an option.
Is there a way to detect and handle a shutdown in these situations? I am open to any .NET, pInvoke/Win32/C/C++ solution. (Basically any way it can be done on the Windows platform.)
Thanks.
P.S. I'm still working with .NET 2.0 so I can't use anything introduced in .NET 3.0+
Your best bet is likely to use P/Invoke. The Windows API function SetConsoleCtrlHandler() may do what you expect.
Sample code below stolen from here (similar code available at MSDN, here).
class Program
{
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
// A delegate type to be used as the handler routine
// for SetConsoleCtrlHandler.
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
// An enumerated type for the control messages
// sent to the handler routine.
public enum CtrlTypes
{
CTRL_C_EVENT = 0,
CTRL_BREAK_EVENT,
CTRL_CLOSE_EVENT,
CTRL_LOGOFF_EVENT = 5,
CTRL_SHUTDOWN_EVENT
}
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
// Put your own handler here
return true;
}
static void Main(string[] args)
{
SetConsoleCtrlHandler(new HandlerRoutine(ConsoleCtrlCheck), true);
}
}
The Exited event should fire whenever you set EnableRaisingEvents = true on the Process, regardless of whether the process is .Net, native, or anything else. If you're actually seeing the Process exit without the Exited event firing, can you post a small reproduction scenario?
Also, the Disposed event is useless for your scenario. It only fires when you call Dispose() on the Process object instance in your process, and has nothing whatsoever to do with anything that happens within the OS process that the object refers to.
Part of your problem may be that the ProcessInfo class in your example is part of the System.Web namespace. It (to quote MSDN) "returns information about ASP.Net worker processes that are running under the ASP.Net Process Model." This would seem unlikely to return anything terribly useful to your command-line app.
You need to use WMI via the System.Management namespace. The following sample should do the trick and get you the System.Diagnostics.Process object for the current process's immediate parent. This example uses the Process.WaitForExit() method, but wiring up an event handler should work as well.
You should note, however, especially since your talking about a console app, that the immediate parent process is not necessarily the process that actually spawned the current process. If process A spawns process console app B directly with ProcessStartInfo specifying UseShellExecute=false, than A will be the immediate parent of B. However, if process A spawns process B (a console app) with ProcessStartInfo specifying UseShellExecute=true, then process A will not be the immediate parent of process B: there will be an intermediate process (cmd.exe) between A and B. And if you're involving *.cmd batch files or PowerShell code...it might be more complicated.
So you might need to run further up of the process tree to find the parent of interest.
Also, since you didn't spawn the parent process, you won't have access to the condition (exit) code of the parent process after it completes. Trying to access the parent process's ExitCode property throws an invalid operation exception.
using System;
using System.Collections;
using System.Diagnostics;
using System.Management;
namespace WaitOnParentProcessSample
{
class Program
{
static int Main( string[] argv )
{
using ( Process parentProcess = GetParentProcess() )
{
Console.WriteLine( "Waiting for parent process (pid:{0}) to exit..." , parentProcess.Id );
parentProcess.WaitForExit();
Console.WriteLine( "Parent Process Has exited. Condition code cannot be determined" );
}
return 0;
}
private static Process GetParentProcess()
{
Process parentProcess = null;
using ( Process currentProcess = Process.GetCurrentProcess() )
{
string filter = string.Format( "ProcessId={0}" , currentProcess.Id );
SelectQuery query = new SelectQuery( "Win32_Process" , filter );
using ( ManagementObjectSearcher searcher = new ManagementObjectSearcher( query ) )
using ( ManagementObjectCollection results = searcher.Get() )
{
if ( results.Count>0 )
{
if ( results.Count>1 )
throw new InvalidOperationException();
IEnumerator resultEnumerator = results.GetEnumerator();
bool fMoved = resultEnumerator.MoveNext();
using ( ManagementObject wmiProcess = (ManagementObject)resultEnumerator.Current )
{
PropertyData parentProcessId = wmiProcess.Properties["ParentProcessId"];
uint pid = (uint)parentProcessId.Value;
parentProcess=Process.GetProcessById( (int)pid );
}
}
}
}
return parentProcess;
}
}
}
Related
Background info
I am writing an integration test that spawns a child process (c# console app). The test is counting some rows in the database after the process is spun up and after the process is closed. The process is closed via process.Kill()
When the process is killed in this manner, it doesn't hit the Stop method within the process. I need to call this stop method to stop threads and remove entries from the database in order for the test to pass.
Original Code
The console app process that I am spawning in my test:
static void Main(string[] args)
{
TaskManager tm = new TaskManagerProcess();
if (Environment.UserInteractive ||
(args.EmptyForNull().Any(a => a.Equals("-RunInteractive", StringComparison.OrdinalIgnoreCase) || a.Equals("/RunInteractive"))))
{
tm.ConsoleStart(args);
Console.WriteLine("Press [Enter] to shut down, any other key to mark");
while (true)
{
ConsoleKeyInfo key = Console.ReadKey(true);
if (key.Key == ConsoleKey.Enter)
break;
Console.WriteLine("========================================================");
Console.Out.Flush();
}
Console.WriteLine("Shutting down...");
tm.ConsoleStop();
}
else
{
ServiceBase.Run(tm);
}
}
}
The test code:
//count before starting child proc
int preCount;
//count after process is spun up
int runningsCount;
//count after stopped
int postCount;
//Get an initial count of the logged in modules before svc host is started
user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
preCount = user.LoggedInModules.Count;
Process proc = Helper.StartProcess(ConnectionBundle);
//Give process time to spin up leaders and workers
await Task.Delay(TimeSpan.FromSeconds(30));
//Get a count of modules after process is spun up
user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
runningCount = user.LoggedInModules.Count;
//Write a line terminator to the child svc host process -
//this allows it to shutdown normally
Helper.ProcessInput.WriteLine();
Helper.ProcessInput.Close();
Helper.KillProcess(proc);
await Task.Delay(TimeSpan.FromSeconds(5));
//Get count of logged in modules after process is closed
user = ApiMethod.GetLoggedInUsers().Where(x => x.RecId == userRecID).FirstOrDefault();
postCount = user.LoggedInModules.Count;
Helper is a static class that sets up the process start info(including args) and starts the process. In helper I've redirected the StandardInput and added a property ProcessInput which is set to the StandardInput of the created process.
My goal is to send input of "Enter" from the test to the spawned process so that it will break from the loop and call tm.ConsoleStop()
TaskManagerProcess is a private custom class that controls the process. It does not inherit from System.Diagnostics.Process. As an alternate approach, my test could interact with TaskManagerProcess directly. However, I can't make TaskManagerProcess public and I need to run TaskManagerProcess in its own AppDomain because calling ConsoleStop is disposing objects in the API that I need to finish the test.
Things I've Tried
[DllImport("Kernel32")]
private static extern bool SetConsoleCtrlHandler(CloseProcDelgate handler, bool add);
I tried adding a call to Kernel32.SetConsoleCtrlHandler (and the necessary delegate) to call ConsoleStop when the process is exited. This doesn't seem to work when the process is killed via process.Kill()
With the original process code, I noticed an exception when I wrote to the StandardInput. The exception message told me to use Console.Read instead of Console.ReadKey(). This actually works intermittently! I can sometimes get a breakpoint on int cKey = Console.Read() (with debugger attached to child process) but other times it doesn't hit the breakpoint.
while (true)
{
//Changing this to Console.Read instead of Console.ReadKey
//Allows us to send redirected input to process?
int cKey = Console.Read();
if ((ConsoleKey)cKey == ConsoleKey.Enter)
break;
Console.WriteLine("========================================================");
Console.Out.Flush();
}
Finally, I tried interacting with TaskManagerProcess directly. I made the private class internal, and marked the internals visible to my test assembly. I cannot make the class public.
When I go this route, calling tm.ConsoleStop() blows away some objects in my API so I can't check the count after this method is called. For this reason, I thought I would create a new AppDomain and call AppDomain.CreateInstanceAndUnwrap() on the TaskManagerProcess class. However, I get an exception here, I believe its due to the the fact that the class is internal.
I am really stuck at this point! Any help is appreciated and thanks for taking the time to read this!
Edit
I created a demo project here
that shows what I am trying to do and has both approaches in the Test method.
Initially I thought I couldn't call AppDomain.CreateInstanceAndUnwrap() because the TaskManagerProcess class was internal. However, after playing with my demo project, I think I just can't load the assembly.
I'm guessing here, but I believe your TaskManagerProcess is a service application. If it is not, please ignore this. If it is, be advised of including details like this in your question. Debugging service applications can be complicated, believe me, I've been there. But before proceed, more advise.
Test the methods in your modules, no whole running programs, as Michael Randall just said.
Unless absolutely necessary, don't do tests against a database. Mock whatever you need to test your code.
You should go back to your alternate approach of interact with TaskManagerProcess directly. From the code of your console app, the only working method I see called is tm.ConsoleStart(args), the rest inside the loop is console writing and reading. So you can't change the acces level of that class, again, I've been there. What I have done in the past to overcome this is to use conditional compilation to create a kind of public facade in my private or internal modules.
Suppose you have:
internal class TaskManagerContainer
{
private class TaskManagerProcess
{
internal void Start()
{
// stuff
}
private void DoSomething(int arg)
{
// more stuff
}
}
}
Change it like this:
#define TEST
// Symbol TEST can also be defined using the GUI of your IDE or compiler /define option
internal class TaskManagerContainer
{
//
#if TEST
public class TaskManagerProcess
#else
private class TaskManagerProcess
#endif
{
internal void Start()
{
// stuff
}
private void DoSomething(int arg)
{
// more stuff
}
#region Methods Facade for Testing
#if TEST
public void Start_Test()
{
Start();
}
private void DoSomething_Test(int arg)
{
DoSomething(arg);
}
#endif
#endregion
}
}
I really hope it will help you making the methods visible to the test assembly and it won't blow objects in you API.
I think I got it with a brute force approach.
while (!testProcess.HasExited)
{
testProcess.StandardInput.WriteLine();
}
Thanks everyone for the input!
My app checks at startup if any other instance of the same is running already, if yes then it will close all other instances. For this I tried using Process.GetProcessByName("AppName") function and store all the process with AppName in processes[] array. Now i want to find the PID of current instance so that i can close all other instances of my app (which obviously have same name but different PIDs). But i am unable to find that even after lot of googling. Also how can i find the PID of an instance of my app which i have created with Process.Start("AppName.exe") function called from inside AppName.exe
OK, given problems with my other solution, see the following
In order to hook in between processes, you need some form of IPC. To use the simplicty of shared handles between EventWaitHandles, you could make each program listen for a cancellation flag.
public static EventWaitHAndle CancellationEvent =
new EventWaitHandle(
false,
EventResetMode.AutoReset,
"MyAppCancel");
private object lockObject = new object();
And later...
Task.Run(() =>
{
while(true)
{
CancellationEvent.WaitOne();
lock(lockObject)
if(!thisIsCalling) // static bool to prevent this program from ending itself
Environment.Exit(0);
}
}
And then call the cancellation like so
lock(lockObject)
{
thisIsCalling = true;
CancellationEvent.Set();
thisIsCalling = false;
}
Why don't you just check equality with your current process?
var processes = Process.GetProcessByName("AppName");
foreach (var p in processes)
{
if (p != Process.GetCurrentProcess())
p.CloseMainWindow();
}
If you're interested in closing other instances of your app, why not do the opposite and prevent multiple instances from opening in the first place? Using EventWaitHandle can do this thusly:
bool created;
var eve = new System.Threading.EventWaitHandle(
false,
EventResetMode.AutoReset,
"MyAppHandle",
out created);
if(!created)
{
eve.Set();
Environment.Exit(-1); // Always use an exit error code if you're expecting to call from the console!
}
The handle parameter, "MyAppHandle" in this case, will be shared across the entire system, thus meaning not only will the out created paramete be false on secondary instaces, but you can use eve.Set() to cause the handle to fire acorss application. Set up a listening thread and this can allow a message loop to display a message when you attempt to open second instance.
Task.Run(() =>
{
while(true)
{
eve.WaitOne();
// Display an error here
}
}
I am trying to automate multiple parallel instances of Office InfoPath 2010 via a windows service. I understand automating Office from a service is not supported however it is a requirement of my customer.
I can automate other Office applications in a parallel fashion, however InfoPath behaves differently.
What I have found is that there will only ever be one instance of the INFOPATH.EXE process created, no matter how many parallel calls to CreateObject("InfoPath.Application") are made. In contrast to this, multiple instances of WINWORD.EXE can be created via the similar mechanism CreateObject("Word.Application")
To reproduce this issue, a simple console application can be used.
static void Main(string[] args) {
// Create two instances of word in parallel
ThreadPool.QueueUserWorkItem(Word1);
ThreadPool.QueueUserWorkItem(Word2);
System.Threading.Thread.Sleep(5000);
// Attempt to create two instances of infopath in parallel
ThreadPool.QueueUserWorkItem(InfoPath1);
ThreadPool.QueueUserWorkItem(InfoPath2);
}
static void Word1(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void Word2(object context) {
OfficeInterop.WordTest word = new OfficeInterop.WordTest();
word.Test();
}
static void InfoPath1(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
static void InfoPath2(object context) {
OfficeInterop.InfoPathTest infoPath = new OfficeInterop.InfoPathTest();
infoPath.Test();
}
The InfoPathTest and WordTest classes (VB) are in another project.
Public Class InfoPathTest
Public Sub Test()
Dim ip As Microsoft.Office.Interop.InfoPath.Application
ip = CreateObject("InfoPath.Application")
System.Threading.Thread.Sleep(5000)
ip.Quit(False)
End Sub
End Class
Public Class WordTest
Public Sub Test()
Dim app As Microsoft.Office.Interop.Word.Application
app = CreateObject("Word.Application")
System.Threading.Thread.Sleep(5000)
app.Quit(False)
End Sub
End Class
The interop classes simply create the automation objects, sleep and then quit (although in the case of Word, I have completed more complex tests).
When running the console app, I can see (via Task Manager) two WINWORD.EXE processes created in parallel, and only a single INFOPATH.EXE process created. In fact when the first instance of InfoPathTest calls ip.Quit, the INFOPATH.EXE process terminates. When the second instance of InfoPathTest calls ip.Quit, a DCOM timeout exception is thrown - it appears as though the two instances were sharing the same underlying automation object, and that object no longer exists after the first call to ip.Quit.
At this stage my thoughts were only a single INFOPATH.EXE is supported per user login. I expanded the windows service to start two new processes (a console application called InfoPathTest), each running under a different user account. These new processes would then attempt to automate INFOPATH.EXE
Here's where it gets interesting, this actually works, but only on some machines, and I cannot figure out why that is the case.
And the service code (with help from AsproLock):
public partial class InfoPathService : ServiceBase {
private Thread _mainThread;
private bool isStopping = false;
public InfoPathService() {
InitializeComponent();
}
protected override void OnStart(string[] args) {
if (_mainThread == null || _mainThread.IsAlive == false) {
_mainThread = new Thread(ProcessController);
_mainThread.Start();
}
}
protected override void OnStop() {
isStopping = true;
}
public void ProcessController() {
while (isStopping == false) {
try {
IntPtr hWinSta = GetProcessWindowStation();
WindowStationSecurity ws = new WindowStationSecurity(hWinSta, System.Security.AccessControl.AccessControlSections.Access);
ws.AddAccessRule(new WindowStationAccessRule("user1", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AddAccessRule(new WindowStationAccessRule("user2", WindowStationRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ws.AcceptChanges();
IntPtr hDesk = GetThreadDesktop(GetCurrentThreadId());
DesktopSecurity ds = new DesktopSecurity(hDesk, System.Security.AccessControl.AccessControlSections.Access);
ds.AddAccessRule(new DesktopAccessRule("user1", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AddAccessRule(new DesktopAccessRule("user2", DesktopRights.AllAccess, System.Security.AccessControl.AccessControlType.Allow));
ds.AcceptChanges();
ThreadPool.QueueUserWorkItem(Process1);
ThreadPool.QueueUserWorkItem(Process2);
} catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(String.Format("{0}: Process Controller Error {1}", System.Threading.Thread.CurrentThread.ManagedThreadId, ex.Message));
}
Thread.Sleep(15000);
}
}
private static void Process1(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = #"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = #"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user1";
process2.Start();
process2.WaitForExit();
}
private static void Process2(object context) {
SecureString pwd2;
Process process2 = new Process();
process2.StartInfo.FileName = #"c:\debug\InfoPathTest.exe";
process2.StartInfo.UseShellExecute = false;
process2.StartInfo.LoadUserProfile = true;
process2.StartInfo.WorkingDirectory = #"C:\debug\";
process2.StartInfo.Domain = "DEV01";
pwd2 = new SecureString(); foreach (char c in "password") { pwd2.AppendChar(c); };
process2.StartInfo.Password = pwd2;
process2.StartInfo.UserName = "user2";
process2.Start();
process2.WaitForExit();
}
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetProcessWindowStation();
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetThreadDesktop(int dwThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern int GetCurrentThreadId();
}
The InfoPathTest.exe process simply calls the InfoPathTest.Test() method detailed above.
In summary, this works, but only on certain machines. When it fails, the second INFOPATH.EXE process is actually created, but immediately quits with an exitcode of 0. There is nothing in the event logs, nor any exceptions in the code.
I've looked at many things to try and differentiate between working / non working machines, but I'm now stuck.
Any pointers appreciated, especially if you have other thoughts on how to automate multiple InfoPath instances in parallel.
I'm guessing you'd get similar behavior if you tried to do the same thing with Outlook, which would mean Microsoft thinks it is a bad idea to run multiple copies.
If that is so, I see two options.
Option one is to make your Infopath automation synchronous, running one instance at a time.
Option two, and I have NO idea if it would even work, would be to see if you can launch virtual machines to accomplish youe InfoPath work.
I hope this can at least spark some new train of though that will lead to success.
I’ve encountered a very similar issue with Outlook. The restriction of allowing only a single instance of the application to be running does not apply per user, but per interactive login session. You may read more about it in Investigating Outlook's Single-Instance Restriction:
Outlook was determining whether or not another instance was already running in the interactive login session. […] During Outlook's initialization, it checks to see if a window named "Microsoft Outlook" with class name "mspim_wnd32" exists, and if so, it assumes that another instance is already running.
There are ways of hacking around it – there is a tool for launching multiple Outlook instances on the Hammer of God site (scroll down) – but they will probably involve intercepting Win32 calls.
As for your code only working on certain machines: That’s probably due to a race condition. If both processes manage to start up fast enough simultaneously, then they won’t detect each other’s window, and assume that they’re the only instance running. However, if the machine is slow, one process would open its window before the other, thereby causing the second process to detect the first process’s window and shut itself down. To reproduce, try introducing a delay of several seconds between launching the first process and the second – this way, only the first process should ever succeed.
I am trying towrite a simple program that has two methods, one that starts a process and one that takes down the same process.
as in:
public Process StartProc(string procname)
{
Process proc = new Process();
proc.StartInfo.FileName = procname;
proc.Start();
return proc;
}
public void StopProc(Process proc)
{
proc.Close();
}
Is it possible to do this like that?
Yes, the method you are after is called Kill, not Close:
public void StopProc(Process proc)
{
proc.Kill();
}
This will forcibly close the process - when possible it is preferable to signal the application to close such as by requesting that the application close the main window:
public void StopProc(Process proc)
{
proc.CloseMainWindow();
}
This allows the application to perform clean-up logic (such as saving files), however may allow the process to continue running if it chooses to ignore the request and will do nothing if the process does not have a main window (for example with a console application).
For more information see the documentation on the Process.CloseMainWindow method.
I think you are looking for Process.Kill().
You don't really need a StopProc() method, you can just write proc.Kill() directly.
However, it is not generally recommended that you terminate processes in such a brutal way. Doing so can leave shared objects in an undefined state. If you can find a way to co-operatively close the process that is to be preferred.
By starting the process, you can get the unique Id of that process and then you can kill it like this:
public static int Start(string processName)
{
var process =
Process.Start(processName);
return
process.Id;
}
public static void Stop(int processId)
{
var process =
Process.GetProcessById(processId);
process.Kill();
}
I have a C# Window Service that runs a batch file (.bat) which in turn, executes a java app. The service runs the .bat file (cmd.exe) with no problem. However, when I try to stop the window service, the cmd.exe process does not die. A new cmd process is stacked if I start the service again.
How do I kill the running cmd.exe process?
Code:
private const string BATCH_FILE_PATH_APPKEY = "Service_Batch_File_Path";
private const string BATCH_FILE_DEFAULT = "Service.bat";
private static Process _proc;
private static bool _hasStarted = false;
public AService()
{
InitializeComponent();
_proc = new Process();
}
protected override void OnStart(string[] args)
{
try
{
string appDirectory = System.Windows.Forms.Application.ExecutablePath;
appDirectory = appDirectory.Substring(0, appDirectory.LastIndexOf("\\"));
string workingDirectory = appDirectory;
string batchFilePath = string.Empty;
batchFilePath = workingDirectory + "Service.bat";
// Make sure it exists
_proc.StartInfo = new ProcessStartInfo(batchFilePath);
_proc.StartInfo.CreateNoWindow = true;
_proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
_proc.Start();
_hasStarted = true;
}
catch (System.Exception ex)
{
eventLog1.WriteEntry(ex.ToString() + "\n\nStack Trace:\n" + ex.StackTrace);
OnStop();
}
}
protected override void OnStop()
{
if (_hasStarted)
_proc.CloseMainWindow();
//_proc.Close();
}
TIA,
Alex.
_proc.Kill();
See the details in MSDN: Process.Kill Method
You may find the following link very useful: Process.Close() is not terminating created process,c#
_proc.Kill () will work.... but it will orphan your java app. I have done something similar where a third process was started. You will also need to know which java process to kill. To do this, you can use the ParentProcess Performance Counter.
Here are some details on using the ParentProcess performance counter.
Also, version of Windows do you plan to deploy this on? WindowsServer2008 seems to have a cmd.exe and a conhost.exe. That may pose a problem for you (again one that can probably be resolved by knowing the parent process.
Have you tried _proc.Kill() in your service's closedown processing? This is async and you should then call WaitForExit to give it a decent chance to go away. Log any failures or exceptions in this logic for investigation.
You should also (for cleanliness) reinstate the _proc.Close() after calling WaitForExit(), to ensure Dispose() gets called properly for the Process. I know your service is about to exit, but this is a good habit and means you are less likely to leak if you decide to manage the child Process more dynamically in future.