Is there a way to launch a C# application with the following features?
It determines by command-line parameters whether it is a windowed or console app
It doesn't show a console when it is asked to be windowed and doesn't show a GUI window when it is running from the console.
For example, myapp.exe /help would output to stdout on the console you used, but myapp.exe by itself would launch my Winforms or WPF user interface.
The best answers I know of so far involve having two separate exe and use IPC, but that feels really hacky.
What options do I have and trade-offs can I make to get the behavior described in the example above? I'm open to ideas that are Winform-specific or WPF-specific, too.
Make the app a regular windows app, and create a console on the fly if needed.
More details at this link (code below from there)
using System;
using System.Windows.Forms;
namespace WindowsApplication1 {
static class Program {
[STAThread]
static void Main(string[] args) {
if (args.Length > 0) {
// Command line given, display console
if ( !AttachConsole(-1) ) { // Attach to an parent process console
AllocConsole(); // Alloc a new console
}
ConsoleMain(args);
}
else {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
private static void ConsoleMain(string[] args) {
Console.WriteLine("Command line = {0}", Environment.CommandLine);
for (int ix = 0; ix < args.Length; ++ix)
Console.WriteLine("Argument{0} = {1}", ix + 1, args[ix]);
Console.ReadLine();
}
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[System.Runtime.InteropServices.DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
}
}
I basically do that the way depicted in Eric's answer, additionally I detach the console with FreeConsole and use the SendKeys command to get the command prompt back.
[DllImport("kernel32.dll")]
private static extern bool AllocConsole();
[DllImport("kernel32.dll")]
private static extern bool AttachConsole(int pid);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
static void Main(string[] args)
{
if (args.Length > 0 && (args[0].Equals("/?") || args[0].Equals("/help", StringComparison.OrdinalIgnoreCase)))
{
// get console output
if (!AttachConsole(-1))
AllocConsole();
ShowHelp(); // show help output with Console.WriteLine
FreeConsole(); // detach console
// get command prompt back
System.Windows.Forms.SendKeys.SendWait("{ENTER}");
return;
}
// normal winforms code
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
Write two apps (one console, one windows) and then write another smaller app which based on the parameters given opens up one of the other apps (and then would presumably close itself since it would no longer be needed)?
I've done this by creating two separate apps.
Create the WPF app with this name: MyApp.exe. And create the console app with this name: MyApp.com. When you type your app name in the command line like this MyApp or MyApp /help (without .exe extension) the console app with the .com extension will take precedence. You can have your console application invoke the MyApp.exe according to the parameters.
This is exactly how devenv behaves. Typing devenv at the command line will launch Visual Studio's IDE. If you pass parameters like /build, it will remain in the command line.
NOTE: I haven't tested this, but I believe it would work...
You could do this:
Make your app a windows forms application. If you get a request for console, don't show your main form. Instead, use platform invoke to call into the Console Functions in the Windows API and allocate a console on the fly.
(Alternatively, use the API to hide the console in a console app, but you'd probably see the console "flicker" as it was created in this case...)
As far as I am aware there is a flag in the exe that tells it whether to run as console or windowed app. You can flick the flag with tools that come with Visual Studio, but you cann't do this at runtime.
If the exe is compiled as a console, then it will always open a new console if its not started from one.
If the the exe is an application then it can't output to the console. You can spawn a separate console - but it won't behave like a console app.
I the past we have used 2 separate exe's. The console one being a thin wrapper over the forms one (you can reference an exe as you would reference a dll, and you can use the [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] attribute to trust the console one, so you don't have to expose more than you need to).
I would create a solution that is a Windows Form App since there are two functions you can call that will hook into the current console. So you can treat the program like a console program. or by default you can launch the GUI.
The AttachConsole function will not create a new console. For more information about AttachConsole, check out PInvoke: AttachConsole
Below a sample program of how to use it.
using System.Runtime.InteropServices;
namespace Test
{
/// <summary>
/// This function will attach to the console given a specific ProcessID for that Console, or
/// the program will attach to the console it was launched if -1 is passed in.
/// </summary>
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool FreeConsole();
[STAThread]
public static void Main()
{
Application.ApplicationExit +=new EventHandler(Application_ApplicationExit);
string[] commandLineArgs = System.Environment.GetCommandLineArgs();
if(commandLineArgs[0] == "-cmd")
{
//attaches the program to the running console to map the output
AttachConsole(-1);
}
else
{
//Open new form and do UI stuff
Form f = new Form();
f.ShowDialog();
}
}
/// <summary>
/// Handles the cleaning up of resources after the application has been closed
/// </summary>
/// <param name="sender"></param>
public static void Application_ApplicationExit(object sender, System.EventArgs e)
{
FreeConsole();
}
}
One way to do this is to write a Window app that doesn't show a window if the command line arguments indicate it shouldn't.
You can always get the command line arguments and check them before showing the first window.
The important thing to remember to do after AttachConsole() or AllocConsole() calls to get it to work in all cases is:
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw =
new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
}
I have found that works with or without VS hosting process. With output being sent with System.Console.WriteLine or System.Console.out.WriteLine before call To AttachConsole or AllocConsole. I have included my method below:
public static bool DoConsoleSetep(bool ClearLineIfParentConsole)
{
if (GetConsoleWindow() != System.IntPtr.Zero)
{
return true;
}
if (AttachConsole(ATTACH_PARENT_PROCESS))
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
ConsoleSetupWasParentConsole = true;
if (ClearLineIfParentConsole)
{
// Clear command prompt since windows thinks we are a windowing app
System.Console.CursorLeft = 0;
char[] bl = System.Linq.Enumerable.ToArray<char>(System.Linq.Enumerable.Repeat<char>(' ', System.Console.WindowWidth - 1));
System.Console.Write(bl);
System.Console.CursorLeft = 0;
}
return true;
}
int Error = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
if (Error == ERROR_ACCESS_DENIED)
{
if (log.IsDebugEnabled) log.Debug("AttachConsole(ATTACH_PARENT_PROCESS) returned ERROR_ACCESS_DENIED");
return true;
}
if (Error == ERROR_INVALID_HANDLE)
{
if (AllocConsole())
{
System.IO.StreamWriter sw = new System.IO.StreamWriter(System.Console.OpenStandardOutput());
sw.AutoFlush = true;
System.Console.SetOut(sw);
System.Console.SetError(sw);
return true;
}
}
return false;
}
I also called this when I was done in case I needed command prompt to redisplay when I was done doing output.
public static void SendConsoleInputCR(bool UseConsoleSetupWasParentConsole)
{
if (UseConsoleSetupWasParentConsole && !ConsoleSetupWasParentConsole)
{
return;
}
long LongNegOne = -1;
System.IntPtr NegOne = new System.IntPtr(LongNegOne);
System.IntPtr StdIn = GetStdHandle(STD_INPUT_HANDLE);
if (StdIn == NegOne)
{
return;
}
INPUT_RECORD[] ira = new INPUT_RECORD[2];
ira[0].EventType = KEY_EVENT;
ira[0].KeyEvent.bKeyDown = true;
ira[0].KeyEvent.wRepeatCount = 1;
ira[0].KeyEvent.wVirtualKeyCode = 0;
ira[0].KeyEvent.wVirtualScanCode = 0;
ira[0].KeyEvent.UnicodeChar = '\r';
ira[0].KeyEvent.dwControlKeyState = 0;
ira[1].EventType = KEY_EVENT;
ira[1].KeyEvent.bKeyDown = false;
ira[1].KeyEvent.wRepeatCount = 1;
ira[1].KeyEvent.wVirtualKeyCode = 0;
ira[1].KeyEvent.wVirtualScanCode = 0;
ira[1].KeyEvent.UnicodeChar = '\r';
ira[1].KeyEvent.dwControlKeyState = 0;
uint recs = 2;
uint zero = 0;
WriteConsoleInput(StdIn, ira, recs, out zero);
}
Hope this helps...
No 1 is easy.
No 2 can't be done, I don't think.
The docs say:
Calls to methods such as Write and WriteLine have no effect in Windows applications.
The System.Console class is initialized differently in console and GUI applications. You can verify this by looking at the Console class in the debugger in each application type. Not sure if there's any way to re-initialize it.
Demo:
Create a new Windows Forms app, then replace the Main method with this:
static void Main(string[] args)
{
if (args.Length == 0)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
Console.WriteLine("Console!\r\n");
}
}
The idea is that any command line parameters will print to the console and exit. When you run it with no arguments, you get the window. But when you run it with a command line argument, nothing happens.
Then select the project properties, change the project type to "Console Application", and recompile. Now when you run it with an argument, you get "Console!" like you want. And when you run it (from the command line) with no arguments, you get the window. But the command prompt won't return until you exit the program. And if you run the program from Explorer, a command window will open and then you get a window.
I have worked out a way to do this including using stdin, but I must warn you that it is not pretty.
The problem with using stdin from an attached console is that the shell will also read from it. This causes the input to sometimes go to your app but sometimes to the shell.
The solution is to block the shell for the duration of the apps lifetime (although technically you could try to block it only when you need input). The way I choose to do this is by sending keystrokes to the shell to run a powershell command that waits for the app to terminate.
Incidentally this also fixes the problem of the prompt not getting back after the app terminates.
I have briefly attempted to get it to work from the powershell console as well. The same principles apply, but I didn't get it to execute my command. It might be that powershell has some security checks to prevent running commands from other applications. Because I don't use powershell much I didn't look into it.
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
private const uint STD_INPUT_HANDLE = 0xfffffff6;
private const uint STD_OUTPUT_HANDLE = 0xfffffff5;
private const uint STD_ERROR_HANDLE = 0xfffffff4;
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(uint nStdHandle);
[DllImport("Kernel32.dll", SetLastError = true)]
public static extern int SetStdHandle(uint nStdHandle, IntPtr handle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern int GetConsoleProcessList(int[] ProcessList, int ProcessCount);
[DllImport("user32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Attach to existing console or create new. Must be called before using System.Console.
/// </summary>
/// <returns>Return true if console exists or is created.</returns>
public static bool InitConsole(bool createConsole = false, bool suspendHost = true) {
// first try to attach to an existing console
if (AttachConsole(-1)) {
if (suspendHost) {
// to suspend the host first try to find the parent
var processes = GetConsoleProcessList();
Process host = null;
string blockingCommand = null;
foreach (var proc in processes) {
var netproc = Process.GetProcessById(proc);
var processName = netproc.ProcessName;
Console.WriteLine(processName);
if (processName.Equals("cmd", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"powershell \"& wait-process -id {Process.GetCurrentProcess().Id}\"";
} else if (processName.Equals("powershell", StringComparison.OrdinalIgnoreCase)) {
host = netproc;
blockingCommand = $"wait-process -id {Process.GetCurrentProcess().Id}";
}
}
if (host != null) {
// if a parent is found send keystrokes to simulate a command
var cmdWindow = host.MainWindowHandle;
if (cmdWindow == IntPtr.Zero) Console.WriteLine("Main Window null");
foreach (char key in blockingCommand) {
SendChar(cmdWindow, key);
System.Threading.Thread.Sleep(1); // required for powershell
}
SendKeyDown(cmdWindow, Keys.Enter);
// i haven't worked out how to get powershell to accept a command, it might be that this is a security feature of powershell
if (host.ProcessName == "powershell") Console.WriteLine("\r\n *** PRESS ENTER ***");
}
}
return true;
} else if (createConsole) {
return AllocConsole();
} else {
return false;
}
}
private static void SendChar(IntPtr cmdWindow, char k) {
const uint WM_CHAR = 0x0102;
IntPtr result = PostMessage(cmdWindow, WM_CHAR, ((IntPtr)k), IntPtr.Zero);
}
private static void SendKeyDown(IntPtr cmdWindow, Keys k) {
const uint WM_KEYDOWN = 0x100;
const uint WM_KEYUP = 0x101;
IntPtr result = SendMessage(cmdWindow, WM_KEYDOWN, ((IntPtr)k), IntPtr.Zero);
System.Threading.Thread.Sleep(1);
IntPtr result2 = SendMessage(cmdWindow, WM_KEYUP, ((IntPtr)k), IntPtr.Zero);
}
public static int[] GetConsoleProcessList() {
int processCount = 16;
int[] processList = new int[processCount];
// supposedly calling it with null/zero should return the count but it didn't work for me at the time
// limiting it to a fixed number if fine for now
processCount = GetConsoleProcessList(processList, processCount);
if (processCount <= 0 || processCount >= processList.Length) return null; // some sanity checks
return processList.Take(processCount).ToArray();
}
I have a console application that i would be scheduling to run via Windows Task Scheduler. Now one of my requirements is to have it run either as foreground or background process based on app.config setting. Is it possible to achieve?
You can run a console application in the background through code by using this code
string path = "C:\\myfile.bat";
string args = "";
ProcessStartInfo procInfo = new ProcessStartInfo(path, args);
procInfo.CreateNoWindow = false;
procInfo.UseShellExecute = true;
procInfo.WindowStyle = ProcessWindowStyle.Hidden;
Process procRun = Process.Start(procInfo);
procRun.WaitForExit();
To run this in the foreground change the WindowStyle line to
procInfo.WindowStyle = ProcessWindowStyle.Normal;
--- EDIT ---
I just remembered, you could just use the Windows API to hide the window.
You will require these namespaces:
using System.Linq;
using System.Runtime.InteropServices;
Then you can import these two functions:
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool ShowWindow(IntPtr hWnd, uint nCmdShow);
[DllImport("kernel32.dll")]
static extern IntPtr GetConsoleWindow();
GetConsoleWindow() will get the window handle to the current console window.
ShowWindow() is a WinAPI function that lets you show/hide/minimize/maximize/etc. windows by sending their window handles to it.
In your Main() method you can now just do like this whenever the -silent argument is passed:
static void Main(string[] args)
{
if (args.Length >= 1 && args.Any(s => s.Equals("-silent", StringComparison.OrdinalIgnoreCase))) //Case-insensitively iterate through all arguments and look for the arg "-silent".
{
ShowWindow(GetConsoleWindow(), 0); //0 is equal to SW_HIDE, which means hide the window.
}
}
So in the Task Scheduler, just make it start your application with the argument -silent, and it shouldn't display the window.
Hope this helps!
--- Old answer ---
In order to be able to start your application as a foreground/background application without the aid of a second application you must have some way of indicating to your app that you want it to start without a window.
Once your application starts, you could make it check for a certain argument that tells wether you want it to be in the background or not. Then you could make your application start a new instance of itself without no window, automatically closing the current instance afterwards.
Using System.Reflection.Assembly.GetExecutingAssembly() you are able to obtain the name and full path of the current executable via the CodeBase property. You can then just pass that along to an instance of the ProcessStartInfo class with the proper settings to not create a window.
This is an example of a Main() method doing so:
static void Main(string[] args)
{
if (args.Length >= 1 && args.Any(s => s.Equals("-silent", StringComparison.OrdinalIgnoreCase))) //Case-insensitively iterate through all arguments and look for the arg "-silent".
{
ProcessStartInfo psi = new ProcessStartInfo(System.Reflection.Assembly.GetExecutingAssembly().CodeBase); //Start this application again.
psi.CreateNoWindow = true; //Create no window (make it run in the background).
psi.WindowStyle = ProcessWindowStyle.Hidden; //Create no window (make it run in the background).
psi.WorkingDirectory = Process.GetCurrentProcess().StartInfo.WorkingDirectory; //Use the same working directory as this process.
Process.Start(psi); //Start the process.
return; //Stop execution, thus stopping the current process.
}
//...your normal code here...
}
Note that this code must be on the very top of your Main() method.
The only problem with this code is that in order to close the application (if it doesn't close itself) you must forcibly kill the process either via the task manager or a second application (yes, sadly).
foreach (Process myApp in Process.GetProcessesByName("myApplication")) //Your executable's name, without the ".exe" part.
{
myApp.Kill();
}
The new instance is also not in control by the Task Scheduler, meaning you cannot stop it from there either.
This isn't exactly a "task" but could be references as one. Right click on your project and go to properties. Inside of properties there should be an option inside of Application Tab -> Output type -> [switch it to "Windows Application"]:
using System;
using System.Windows.Forms;
namespace TestConsoleApp
{
public class Program
{
private const string GuiWinForm = "gui";
private const string GuiConsole = "console";
private const string BackgroundProcess = "bgp";
private const string Cmd = "cmd";
[DllImport("kernel32", SetLastError = true)]
private static extern bool AttachConsole(int dwProcessId);
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
private static void LoadForm()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool AllocConsole();
private static void CreateNewConsole()
{
AllocConsole();
Console.WriteLine("New Console App");
Console.ReadLine();
}
private static void LoadConsole()
{
// *** Gets Command Window if it is already open ***
var ptr = GetForegroundWindow();
int u;
GetWindowThreadProcessId(ptr, out u);
var process = Process.GetProcessById(u);
if (process.ProcessName == Cmd)
AttachConsole(process.Id);
// *** Creates a new Command Prompt if the foreground is not a console ***
else
CreateNewConsole();
}
// Get the args from either command line or from config file
[STAThread]
private static void Main(string[] args)
{
var mode = args.Length > 0 ? args[0] : Gui;
switch (mode)
{
case Gui:
LoadForm();
break;
/* If for some reason you just cannot stand the
idea of using a winform for this task/process */
case GuiConsole:
LoadConsole();
break;
case BackgroundProcess:
// Code that will perform task
break;
}
}
}
}
I'm using a small C# executable to launch a java jar. I want to recover the exit code returned by the jar so i can restart the jar if i need to.
However the c# application keeps showing a black console window and i can't get rid of it, does anyone know how to fix this?
I'm using t he following C# code to start the process
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "jre/bin/java.exe";
p.StartInfo.Arguments = "-Djava.library.path=bin -jar readermanager.jar";
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.Start();
p.waitForExit();
return p.ExitCode;
The console window only keeps visible when using the waitForExit(); method. Without it (and withoud p.ExitCode) the console windows closes. I also tried setting the StartInfo.WindowStyle to Hidden and Minimized but both don't have any effect on the window.
Just change the output type of your C# program to be a "Windows Application" instead of a "Console Application". A C# Windows application doesn't really care if you actually display any windows.
From How to run a C# console application with the console hidden
System.Diagnostics.ProcessStartInfo start =
new System.Diagnostics.ProcessStartInfo();
start.FileName = dir + #"\Myprocesstostart.exe";
start.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
But if that does not work, how about this: http://social.msdn.microsoft.com/Forums/eu/csharpgeneral/thread/ea8b0fd5-a660-46f9-9dcb-d525cc22dcbd
using System.Runtime.InteropServices;
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName,string lpWindowName);
[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
IntPtr hWnd = FindWindow(null, "Window caption here");
if(hWnd != IntPtr.Zero)
{
//Hide the window
ShowWindow(hWnd, 0); // 0 = SW_HIDE
}
if(hWnd != IntPtr.Zero)
{
//Show window again
ShowWindow(hWnd, 1); //1 = SW_SHOWNORMA
}
I think starting a process minimized should be simple but I had no luck with outlook. How can I start Outlook minimized?
My attempt was this:
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
static void Main(string[] args)
{
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.FileName = "OUTLOOK.EXE";
IntPtr hWnd = Process.Start(startInfo).Handle;
bool state = false;
if (!hWnd.Equals(IntPtr.Zero))
state = ShowWindowAsync(hWnd, 2);
// window values: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548(v=vs.85).aspx
Console.WriteLine(state.ToString());
Console.Read();
}
Have you tried using ProcessStartInfo.WindowStyle, setting it to ProcessWindowStyle.Minimized?
I have found out that if you wait until Outlook have started and you send the command below the window will minimize to tray. Now the only thing to accomplish in order to minimize outlook is to loop till it is ready :-)
var hWnd = Process.Start(startInfo);
ShowWindowAsync(hWnd.MainWindowHandle, 2);
I have solved it but I like to hear your comments if you think the solution can be improved.
I also have posted the solution on my blog with some more details at http://jwillmer.de/blog/2012/08/01/how-to-start-outlook-minimized-with-c/
[DllImport("user32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
// console application entry point
static void Main()
{
// check if process already runs, otherwise start it
if(Process.GetProcessesByName("OUTLOOK").Count().Equals(0))
Process.Start("OUTLOOK");
// get running process
var process = Process.GetProcessesByName("OUTLOOK").First();
// as long as the process is active
while (!process.HasExited)
{
// title equals string.Empty as long as outlook is minimized
// title starts with "öffnen" (engl: opening) as long as the programm is loading
string title = Process.GetProcessById(process.Id).MainWindowTitle;
// "posteingang" is german for inbox
if (title.ToLower().StartsWith("posteingang"))
{
// minimize outlook and end the loop
ShowWindowAsync(Process.GetProcessById(process.Id).MainWindowHandle, 2);
break;
}
//wait awhile
Thread.Sleep(100);
// place for another exit condition for example: loop running > 1min
}
}
You can use this
this.Application.ActiveExplorer ().WindowState = Outlook.OlWindowState.olMinimized;
It minimizing your corrent outlook window
(this = ThisAddIn class)
I have written a service which starts a java.exe or ruby.exe (I know there are some solutions but I need my own service for some reasons).
The service works find so far, I gather my configs from the registry, then start the services. When the service get stopped I get my processes and send a .Kill().
So far so good.
But I found out, that the .Kill() is a problem due the fact that the ruby.exe (I use thin to start a service) or the java.exe (I start a SOLR with it) listen on a tcp socket port. If this port get in use and I kill the process windows will block the ports for 72 seconds (by design).
If I do a solr:start and a thin -start from a shell command shell and stop it using the Ctrl+C the processes terminate and the port is available to use instantly.
So my guess is: If I manage to send a ctrl-c to the process it terminate correctly.
So I found this thread How to Run an exe from windows service and stop service when the exe process quits? where a proof of concept is posted.
But by starting a process from a window service I don't have a windowHandle.
I start my service like this:
m_process.StartInfo = new ProcessStartInfo
{
FileName = "java"
, Arguments = arguments
, UseShellExecute = true
, WorkingDirectory = workDirectory
, CreateNoWindow = false
};
m_process.Start();
Where arguments contais the jetty data to start the SOLR or in the ruby case I use "ruby.exe thin start ...".
Now on stopping the service I tried:
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32")]
public static extern int SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll", SetLastError = true)]
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
foreach (int i in m_processList)
{
MyLogEvent(Process.GetProcessById(i).MainModule.ModuleName);
MyLogEvent(Process.GetProcessById(i).MainWindowTitle);
try
{
IntPtr ptr = FindWindow(null, Process.GetProcessById(i).MainWindowTitle);
{
SetForegroundWindow(ptr);
Thread.Sleep(1000);
InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.CONTROL, VirtualKeyCode.CANCEL);
// SendKeys.Send("^{BREAK}");
Thread.Sleep(1000);
}
//Process.GetProcessById(i).Kill();
}
catch(Exception ex)
{
MyLogEvent(ex.ToString());
Process.GetProcessById(i).Kill();
}
}
But as I don't have a WindowTitle, I guess I don't even have a window I can't allocate the process like this.
So does anyone have a idea how I can allocate process and send the stop signal to it?
I can live with just killing the process, but this just a service restart impossible without waiting a long time.
Thanks for any hints, tips and solutions.
GenerateConsoleCtrlEvent might work
m_process.StartInfo = new ProcessStartInfo
{
FileName = "java"
, Arguments = arguments
, UseShellExecute = true
, WorkingDirectory = workDirectory
, CreateNoWindow = false
};
var process = m_process.Start();
When time to kill child app...
GenerateConsoleCtrlEvent(CTRL_C_EVENT, process.Id);
member declarations
public const UInt32 CTRL_C_EVENT = 0;
[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent,
uint dwProcessGroupId);