Don't execute next line until Process.Start is complete - c#

I'm writing a simple C# console application which opens up four instances of Windows Explorer and uses Pinvoke's MoveWindow to place them around the screen. My problem is that the Process.Start command seems to run too slowly, and the MoveWindow function cannot find the process unless I deliberately slow the program down. Here is my code:
Process.Start(new ProcessStartInfo()
{
FileName = "explorer",
Arguments = location, //Defined elsewhere (for testing just ".")
UseShellExecute = false
});
int[] pos = GetPositions(position); //Little function which gets the positions I want for this window
System.Threading.Thread.Sleep(500);
IntPtr hnd = GetForegroundWindow();
bool ok = MoveWindow(hnd, pos[0], pos[1], pos[2], pos[3], false);
See how I'm having to Sleep for an (arbitrary) half second. It's not ideal, because I don't know the specifications of the machine it will be running on. It could end up being either inefficient on a fast machine or could break on a slower machine. Is there a more concrete way of waiting until a process has started where I don't have to arbitrarily wait for a set time?
Also, on a semi-related note, you may notice a bit of cowboy code which just gets the foreground window to grab the window I want moved. This is because I had an absolute mare trying to pick out specific Window Explorer processes. From what I can tell they just don't work the same way as anything else (like when I was testing with notepad or IE) and just passing a handle doesn't work. Anyone who's familiar with this problem, feel free to contribute, but if not don't spend any time on it - clunky as this solution is, it works, and for a little application I don't want to spend hours bashing my head against the keyboard trying to figure out the proper way of doing it.

In theory, you would be able to call Process.WaitForInputIdle() which waits for a message loop to be created and be up and running in the newly started process. However, this does not work with all applications (and most likely not with Windows Explorer).
A cheap trick is to poll for the main window of the process to be created (source):
public static bool WaitForMainWindow(this Process process)
{
while (!process.HasExited && process.MainWindowHandle == IntPtr.Zero)
{
Thread.Sleep(10);
}
return !process.HasExited;
}
You might want to make this more robust by adding a timeout:
public static bool WaitForMainWindow(this Process process, uint timeout)
{
var start = DateTime.Now;
while (!process.HasExited && process.MainWindowHandle == IntPtr.Zero)
{
Thread.Sleep(10);
if ((DateTime.Now - start).TotalMilliseconds >= timeout)
{
return false;
}
}
return !process.HasExited;
}

You can try something like this
while(!process.HasExited)
{
//process running
}
//Done

Related

Get the window handle of the Windows setup process

I am trying to automate Windows 10 installation by first mounting the .iso file on the drive. And then using c# to start windows 10 installation by using this below code which passes the keys to the installation application
[DllImport("user32.dll")]
static extern int SetForegroundWindow(IntPtr point);
public static void Main(String[] args){
Process p1 = Process.Start("h:\\setup.exe");
IntPtr h = p1.MainWindowHandle;
SetForegroundWindow(h);
Thread.Sleep(30000);
SendKeys.SendWait("{ENTER}");
Thread.Sleep(1000);
SendKeys.SendWait("{ENTER}");
Thread.Sleep(1000);
SendKeys.SendWait("{ENTER}");
}
But the problem is that the setup window is not taking the signal of the ENTER key in the code. The setup window is starting with this code. After that, nothing is happening.
Process p1 = Process.Start("h:\\setup.exe");
I tried using notepad instead of setup.exe in the code which is taking all the ENTER keys. Please tell me if anyone has a solution. Thank you
Disclaimer:
I would advise against automating a Windows setup using something like
SendKeys as you can't guarantee a consistent behavior and could
easily mess things up. You may consider looking for different
approaches as suggested by lan Kemp in the comments. This answer
only shows you how to get the handle of the setup window correctly.
You may use it at your own risk.
Update:
Apparently, the Windows 10 setup executable ("Setup.exe") starts another process called "SetupPrep.exe" which starts a third process called "SetupHost.exe" (the one you're after). So, what you can do is start the main process, wait for the target process to start and obtain a MainWindowHandle before executing the remaining code:
Process p1 = Process.Start("H:\\setup.exe");
Process targetProcess;
do
{
Thread.Sleep(500);
targetProcess = Process.GetProcessesByName("SetupHost").FirstOrDefault();
} while (targetProcess == null || targetProcess.MainWindowHandle == IntPtr.Zero);
IntPtr h = targetProcess.MainWindowHandle;
// ...
This should solve your problem, however, it's not a wise idea to use SendKeys for this purpose. Please refer to the disclaimer above.
Original answer:
Did you make sure that h does actually have a value (other than IntPtr.Zero)? Because it probably doesn't have the actual window handle since you don't give the process enough time to start and obtain a window handle.
Try something like this:
Process p1 = Process.Start("h:\\setup.exe");
while (p1.MainWindowHandle == IntPtr.Zero)
{
Thread.Sleep(500);
}
IntPtr h = p1.MainWindowHandle;
// ...

C# - Loop foreach until true

New to C#. But due to circumstances at work I have to "learn on the fly."
Been struggling the last 2 days with my code and I consumed as many questions here and articles on MSDN as I could but I think they confused me further.
I launch app A using my code. App A launches app B (I cannot launch app B, I'm beyond that).
What I want to do w/ my code is the moment app B's MainWindowTitle is available, hide the window.
So far, I can only accomplish this w/ a Thread.Sleep(xxx); before the code you see below.
I want to avoid using timers.
What I'm trying to do is loop the code below until it is true.
When app A launches app B, it takes a few seconds for the MainWindowTitle to become available. But the code runs so fast that it's not available yet and the code is done.
IntPtr hWnd = IntPtr.Zero;
foreach (Process procList in Process.GetProcess())
{
if (procList.MainWindowTitle.Contains("SAP Logon"))
{
hWnd = procList.MainWindowHandle;
}
}
ShowWindow(hWnd, 0);
That code only works if I precede it with something like:
Thread.Sleep(10000);
before the entire block of code. And the only reason it works is b/c it allows enough time to pass for the Window to open and contain the title I'm looking for.
I have tried while loops.
Outside the 'foreach'
Outside the 'if'
Around the 'foreach' (that locked up the system really quickly...) hah!
Around the 'if'
I feel like one of the following should work, but it doesn't, or I'm completely screwing it up.
while (!procList.MainWindowTitle.Contains("SAP Logon")) { } // ! at the beginning OR
while (procList.MainWindowTitle.Contains("SAP Logon") == null) { } // equaling null OR
while (procList.MainWindowTitle.Contains("SAP Logon") < 0) { } // etc., etc.,
while (procList.MainWindowTitle.DOESNOTContain("SAP Logon")) { } // I know this is wrong but it almost seems like what I need...
Anyone have any suggestions? My brain is scrambled eggs and this is the last bit that I need to finish this app.
If my only alternative IS Thread.Sleep(), so be it, but I would prefer to not use it.
One last thing: I have to target .net 2.0.
Thank you kindly!
Your idea of using the while loop should work. You could try something like this:
IntPtr hWnd = IntPtr.Zero;
bool isFound = false;
while(!isFound)
{
foreach (Process procList in Process.GetProcess())
{
if (procList.MainWindowTitle.Contains("SAP Logon"))
{
isFound = true;
hWnd = procList.MainWindowHandle;
}
}
Thread.Sleep(100); // You may or may not want this
}
ShowWindow(hWnd, 0);
Instead of checking for the caption of the app in every single process, you can check using just the name of the EXE itself. I would also put in a timeout for good measure. For example, with Notepad you'd do:
Process[] ps;
DateTime timeout = DateTime.Now.AddSeconds(30);
do
{
System.Threading.Thread.Sleep(100);
ps = Process.GetProcessesByName("notepad"); // <--- no path, AND no extension (just the EXE name)
} while (ps.Length == 0 && timeout > DateTime.Now);
if (ps.Length > 0)
{
ShowWindow(ps[0].MainWindowHandle, 0);
}
else
{
MessageBox.Show("Process Not Found within Timeout Period", "Process Failed to Spawn");
}
Have you tried or seen this:
.NET Events for Process executable start
This should do what you are looking for...

Why does closing a console that was started with AllocConsole cause my whole application to exit? Can I change this behavior?

What I want to have happen is that the console window just goes away, or better yet that it is hidden, but I want my application to keep running. Is that possible? I want to be able to use Console.WriteLine and have the console serve as an output window. I want to be able to hide and show it, and I don't want the whole app to die just because the console was closed.
EDIT
Code:
internal class SomeClass {
[DllImport("kernel32")]
private static extern bool AllocConsole();
private static void Main() {
AllocConsole();
while(true) continue;
}
}
EDIT 2
I tried the accepted solution here [ Capture console exit C# ], per the suggestion in the comments on this question. The example code is bugged in that the DLLImport needs to be "kernel32.dll" or "kernel32", not "Kernel32". After making that change, I'm getting a message to my handler for CTRL_CLOSE_EVENT when I click the X on the console window. However, calling FreeConsole and/or returning true doesn't prevent the application from terminating.
Ah, yes, this is one of the caveats of using the Windows console subsystem. When the user closes the console window (regardless of how the console was allocated), all of the processes that are attached to the console are terminated. That behavior makes obvious sense for console applications (i.e., those that specifically target the console subsystem, as opposed to standard Windows applications), but it can be a major pain in cases like yours.
The only workaround that I know of is to use the SetConsoleCtrlHandler function, which allows you to register a handler function for Ctrl+C and Ctrl+Break signals, as well as system events like the user closing the console window, the user logging off, or the system shutting down. The documentation says that if you're only interested in ignoring these events, you can pass null for the first argument. For example:
[DllImport("kernel32")]
static extern bool SetConsoleCtrlHandler(HandlerRoutine HandlerRoutine, bool Add);
delegate bool HandlerRoutine(uint dwControlType);
static void Main()
{
AllocConsole();
SetConsoleCtrlHandler(null, true);
while (true) continue;
}
That works perfectly for Ctrl+C and Ctrl+Break signals (which would have otherwise caused your application to terminate as well), but it doesn't work for the one you're asking about, which is the CTRL_CLOSE_EVENT, generated by the system when the user closes the console window.
Honestly, I don't know how to prevent that. Even the sample in the SDK doesn't actually allow you to ignore the CTRL_CLOSE_EVENT. I tried it in a little test app, and it beeps when you close the window and prints the message, but the process still gets terminated.
Perhaps more worryingly, the documentation makes me think it is not possible to prevent this:
The system generates CTRL_CLOSE_EVENT, CTRL_LOGOFF_EVENT, and CTRL_SHUTDOWN_EVENT signals when the user closes the console, logs off, or shuts down the system so that the process has an opportunity to clean up before termination. Console functions, or any C run-time functions that call console functions, may not work reliably during processing of any of the three signals mentioned previously. The reason is that some or all of the internal console cleanup routines may have been called before executing the process signal handler.
It's that last sentence that catches my eye. If the console subsystem starts cleaning up after itself immediately in response to the user attempting to close the window, it may not be possible to halt it after the fact.
(At least now you understand the problem. Maybe someone else can come along with a solution!)
Unfortunately there's nothing you can do to really alter this behaviour.
Console windows are "special" in that they're hosted by another process and do not allow sub-classing. This limits your ability to modify their behaviour.
From what I know, your two options are:
1. Disable the close button altogether. You can do this with the following code fragment:
HWND hwnd = ::GetConsoleWindow();
if (hwnd != NULL)
{
HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
if (hMenu != NULL) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
}
2. Stop using consoles altogether, and implement your own text output solution.
Option #2 is the more complicated option but would provide you the greatest control. I found an article on CodeProject that implements a console-like application using a rich edit control to display the text (rich edit controls have the ability to stream text like the console, so they are well suited to this sort of application).
On closing the console window obtained using AllocConsole or AttachConsole, the associated process will exit. There is no escape from that.
Prior to Windows Vista, closing the console window would present a confirmation dialogue to the user asking him whether the process should be terminated or not but Windows Vista and later do not provide any such dialogue and the process gets terminated.
One possible solution to work around this is avoiding AttachConsole altogether and achieving the desired functionality through other means.
For instance in the case described by OP, console window was needed to output some text on Console using Console static class.
This can be achieved very easily using inter-process communication. For example a console application can be developed to act as an echo server
namespace EchoServer
{
public class PipeServer
{
public static void Main()
{
var pipeServer = new NamedPipeServerStream(#"Com.MyDomain.EchoServer.PipeServer", PipeDirection.In);
pipeServer.WaitForConnection();
StreamReader reader = new StreamReader(pipeServer);
try
{
int i = 0;
while (i >= 0)
{
i = reader.Read();
if (i >= 0)
{
Console.Write(Convert.ToChar(i));
}
}
}
catch (IOException)
{
//error handling code here
}
finally
{
pipeServer.Close();
}
}
}
}
and then instead of allocating/attaching a console to the current application, the echo server can be started from within the application and Console's output stream can be redirected to write to the pipe server.
class Program
{
private static NamedPipeClientStream _pipeClient;
static void Main(string[] args)
{
//Current application is a Win32 application without any console window
var processStartInfo = new ProcessStartInfo("echoserver.exe");
Process serverProcess = new Process {StartInfo = processStartInfo};
serverProcess.Start();
_pipeClient = new NamedPipeClientStream(".", #"Com.MyDomain.EchoServer.PipeServer", PipeDirection.Out, PipeOptions.None);
_pipeClient.Connect();
StreamWriter writer = new StreamWriter(_pipeClient) {AutoFlush = true};
Console.SetOut(writer);
Console.WriteLine("Testing");
//Do rest of the work.
//Also detect that the server has terminated (serverProcess.HasExited) and then close the _pipeClient
//Also remember to terminate the server process when current process exits, serverProcess.Kill();
while (true)
continue;
}
}
This is just one of the possible solutions. In essence the work around is to allot the console window to its own process so that it can terminate without affecting the parent process.
You can do this by disabling keyboard mouse input by external program called Keyfreez.
you can use it multiple times in your program where no user input required. And if any user input require u can add a process Takskkill /f /IM .
https://www.sordum.org/7921/bluelife-keyfreeze-v1-4-block-keyboard-and-mouse/
Hope this helps all of you

Process.CloseMainWindow() not working

I start the Windows On-Screen-Keyboard like that:
s_onScreenKeyboard = new Process();
s_onScreenKeyboard.StartInfo = new ProcessStartInfo("osk.exe");
s_onScreenKeyboard.EnableRaisingEvents = true;
s_onScreenKeyboard.Exited += new EventHandler(s_onScreenKeyboard_Exited);
s_onScreenKeyboard.Start();
This works fine, but when I try to stop it using the following code, it does not work, i.e. the OSK keeps running and the method returns false:
s_onScreenKeyboard.CloseMainWindow();
if (!s_onScreenKeyboard.HasExited)
{
if (!s_onScreenKeyboard.WaitForExit(1000))
{
s_onScreenKeyboard.Close();
//s_onScreenKeyboard.Kill();
}
}
When uncommenting s_onScreenKeyboard.Kill(); it is closed, but the problem is that osk.exe obviously uses another process called "msswchx.exe" which is not closed if I simply kill the OSK process. This way, I would end up with hundreds of these processes which is not what I want.
Another strange thing is that the CloseMainWindow() call worked at some time, but then it suddenly did not work anymore, and I do not remember what has changed.
Any ideas?
EDIT: I have found a solution myself. Please see my answer for details.
Background:
I am implementing an On-Screen-Keyboard for my application because it should work with a touchscreen. It is important that the keyboard layout matches the layout which is configured in Windows since the application will be shipped to many different countries. Therefore, instead of implementing a custom keyboard control with approx. 537 keyboard layouts (exaggerating a little here...), I wanted to utilize the Windows built-in On-Screen-Keyboard which adapts to the selected keyboard layout automatically, saving a lot of work for me.
I have found the/a solution myself:
When I successfully retrieve the MainWindowHandle after the process has been started, the call to CloseMainWindow() is also successful later on. I do not understand the reason for this, but the important thing is: it works!
BTW, for others having the same problem: The MainWindowHandle is not available immediately after starting the process. Obviously, it takes some milliseconds until the MainWindow is started which is why I use the following code to retrieve the handle:
DateTime start = DateTime.Now;
IntPtr handle = IntPtr.Zero;
while (handle == IntPtr.Zero && DateTime.Now - start <= TimeSpan.FromSeconds(2))
{
try
{
// sleep a while to allow the MainWindow to open...
System.Threading.Thread.Sleep(50);
handle = s_onScreenKeyboard.MainWindowHandle;
}
catch (Exception) { }
}
In this code I continuously get the MainWindowHandle every ~50ms as long as it is still equal to IntPtr.Zero. If the handle could not be retrieved after 2 seconds, I quit the loop to avoid an endless loop in case of error.
You need to wait untill the process finishes initialization, run
Process.WaitForInputIdle Method in order to do that.

Getting process ID from a shell executed file?

I am making a program for handheld PDAs using .net 2.0 compact framework and I have this one part which I'm not proud of and I was hoping for a more elegant solution.
Basically the problem is another process using my file in this case its Windows Media Player. I start the process by passing the file location to Process.Start but it seems the process returned is short lived and it is spawning another process? So I tried looking up how to get child process information but had some problems with that (i think no processes were being returned for some reason).
So i currently do this dodgy fix
string processName = item.Text;
Process proc = Process.Start(processName, null);
if (!proc.Start())
MessageBox.Show("Failed to start process", "Error", MessageBoxButtons.OK, MessageBoxIcon.Hand, MessageBoxDefaultButton.Button1);
else
{
IntPtr newWindow = IntPtr.Zero;
TimeSpan limit = TimeSpan.FromSeconds(3);
DateTime start = DateTime.Now;
DateTime now = start;
// do while the following:
// window is not null
// window is not ourself
// under 3 seconds
do
{
newWindow = Win32.GetForegroundWindow();
now = DateTime.Now;
// taking too long
if (now - start > limit)
break;
}
while (newWindow == IntPtr.Zero || newWindow == this.Handle);
if (newWindow != IntPtr.Zero && newWindow != this.Handle)
{
uint processID = 0;
if (Win32.GetWindowThreadProcessId(newWindow, out processID) != 0)
{
//const int stringSize = 1024;
//StringBuilder sb = new StringBuilder(1024);
//Win32.GetWindowText(newWindow, sb, stringSize);
m_processes.Add(new ProcessIDWithName(processID, processName));
}
}
}
As you can see I don't like it and it's unreliable however it does work for now (i needed a solution whether it was bad or not).
Why do I need the process ID? Because windows media player is keeping the file open on me and I cannot move/delete the file and therefore I need to kill the process off before I do so. I could do a similar fix with FindWindow but I was thinking more generically as it might not be a media file opened in windows media player.
So basically I would like a better solution if possible!
Also if you wondering why I'm not using a Stopwatch its because it doesn't seem to exist in .net 2.0 cf, also I don't need accuracy to that extent.
There are loads of questions that pop up here.
Why aren't you executing media player itself instead of shellexecuting the name of the target file?
How do you know when the media is done playing in order to close the file?
Why not use the toolhelp APIs to simply enumerate processes instead of the wacky GetForegroundWindow/GetWindowsThreadProcessId shenanigans?
Why aren't you just using the Media Player ActiveX control instead of this kludge so you'd actually have control over things?
If you intend to make this generic for any file (i.e. not just media, but maybe something like the Word viewer, etc) then you're really out of luck and need to rethink whatever it is you're trying to do (you've not told us what you're trying to achieve, only how you['ve decided to implement it). Applications don't normally close in WinMo, they typically just lose focus of get minimized, so you don't really know when a user is "done" with the file.
The application associated with the file may already be running, so terminating it yourself is an unfriendly thing to do.
The target application really is not designed to give you a callback when it's done with any particular file.
I have no experience with PDA programming, bu you can try to use Job objects (see http://msdn.microsoft.com/en-us/library/ms684847.aspx#job_object_functions). With respect of CreateJobObject you can create a new job. Then you create a suspended process and use AssignProcessToJobObject to assign the new process to th job object. Then you can resume the process.
The advantage of job object is, that you can receive full control of all child processes of the job. You can use TerminateJobObject to terminate all processes. If you create creates an I/O completion port to wait for the end of the direct started process and all it's child processes or monitor of all child processes created and much more. If you need I could post some code examples of links to code examples.

Categories