10

When running a console application in Visual Studio, depending on your settings, it will add a prompt after the program exits:

Press any key to continue . . .

I have found how to detect if I am running under the debugger(use Debugger.IsAttached), but it isn't helpful. Press CTRL-F5 to Start Without Debugging sets this flag to false, yet still shows the prompt.

I want to detect this because I'd like to display my own message and wait for a keypress, but not double up keypress checks.

I don't want to muck with my general Visual Studio settings. If I can disable it for this project in a way that can be checked into source control, that would also work.

What mechanism is used to append this prompt, and how do I detect it?

Or how do I disable it per-project, and check this change into source control?

Eric J.
  • 147,927
  • 63
  • 340
  • 553
Merlyn Morgan-Graham
  • 58,163
  • 16
  • 128
  • 183

7 Answers7

7

Add the following code to the console application:

public static class Extensions {
    [DllImport("kernel32.dll")]
    static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);

    [DllImport("kernel32.dll")]
    static extern bool TerminateThread(IntPtr hThread, uint dwExitCode);

    public static Process GetParentProcess(this Process x) {
        return (
            from it in (new ManagementObjectSearcher("root\\CIMV2", "select * from Win32_Process")).Get().Cast<ManagementObject>()
            where (uint)it["ProcessId"]==x.Id
            select Process.GetProcessById((int)(uint)it["ParentProcessId"])
            ).First();
    }

    public static IEnumerable<Process> GetChildProcesses(this Process x) {
        return (
            from it in (new ManagementObjectSearcher("root\\CIMV2", "select * from Win32_Process")).Get().Cast<ManagementObject>()
            where (uint)it["ParentProcessId"]==x.Id
            select Process.GetProcessById((int)(uint)it["ProcessId"])
            );
    }

    public static void Abort(this ProcessThread x) {
        TerminateThread(OpenThread(1, false, (uint)x.Id), 1);
    }
}

And then modify your code like this:

class Program {
    static void Main(String[] args) {
        // ... (your code might goes here)

        try {
            Process.GetCurrentProcess().GetParentProcess().Threads.Cast<ProcessThread>().Single().Abort();
        }
        catch(InvalidOperationException) {
        }

        Console.Write("Press ONLY key to continue . . . ");
        Console.ReadKey(true);
    }
}

So, everything we are expecting is done now. I consider this as a workaround solution. It works under Windows XP SP3 and I guess that it would work with newer Windows operating systems. Under Visual Studio, applications are always a spawned process. In older Visual C++ 6.0, it spawned by the IDE by calling VCSPAWN.EXE; in Visual Studio 2010, your application runs with following command line when Start Without Debugging:

"%comspec%" /c ""your application filename" & pause"

So it is impossible to reach the goal in fully managed ways; because it was NOT under the application domain.

Here we use the managed way of WMI to enumerate the processes, and encapsulate the unmanaged WINAPIs to terminate the ProcessThreads, because the ProcessThread is not supposed to be normally aborted; it's provided like something for read-only.

As mentioned above, the application was spawned with the particular command line; it would have a single thread creates a single process signature, so we used the Single() method to retrieve that thread and terminate it.

When we start the application under an existing command prompt, it is just the same scenario of Start Without Debugging. Moreover, when Start Debugging, the application process is created by devenv.exe. It has a lot of threads, we known that and won't abort any thread, just prompt and wait for a key press. This situation is similar to starting application with double-clicking or from context menu. This way, the application process is created by the system shell, usually Explorer.exe and it also has a lots of threads.

In fact, if we can successfully abort the thread it implies that we have the permissions to kill the parent process. But we do NOT need to. We just need to abort the only thread, the process terminates automatically by system when it has no more threads. Killing the parent process by identifying that the calling process is %comspec%, is another way to do the same thing, but it's a dangerous procedure. Because the process spawning the application might have other threads which have any number of threads create a process matches %comspec%. You may kill a critical work of process with carelessness or just growing the complexity of checking whether the process is safe to kill. So I consider a single thread creates a single process as a signature of our parent process which is safe to kill/abort.

WMI is modern, some of WINAPIs might become deprecated in the future. But the real reason of this composition is for its simplicity. The old Tool Help Library is such complicated like the ways to convert ProcessThread to System.Threading.Thread. With LINQ and extension methods, we can make the code simpler and more semantical.

Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • Looks promising. Will accept some time after the bounty is assigned. – Merlyn Morgan-Graham Feb 28 '13 at 18:39
  • Ah.. Thank you. The bounty is start by me, can only award to others, I'm looking for a better answer. – Ken Kin Feb 28 '13 at 18:46
  • You define but never use the GetChildProcesses method. Probably a leftover of you own app's code which you copied here. Can I remove it? – Stefan Monov Jul 25 '16 at 13:40
  • Also, why do you call your `Abort` method *before* the `Write` and `ReadKey`? I'm calling it after them and it works as expected. If you call it before them, the app will never get around to the write and read. – Stefan Monov Jul 25 '16 at 14:25
  • And another thing. Your code works a bit slowly. After I press any key, there's about 500ms delay before the console closes. Would be nice to see that improved. – Stefan Monov Jul 25 '16 at 14:28
  • @StefanMonov Thanks for commenting. You are right, `GetChildProcesses` is not used here, just a demonstration of the reverse functionality against `GetParentProcess`. – Ken Kin Aug 22 '16 at 14:34
  • @StefanMonov I think the reason `Abort` is before the console manipulation is told in the answer, if it's not, well.. vs debugger itself will prompt and wait for a keypress, aborting the thread before your app manipulates the console suppresses doing the same thing twice -- one is done by your app and another one is done by debugger. In the other hand, if your app was not spawned by debugger, `Abort` fails with an exception and swallowed, just prompt and wait for a keypress. – Ken Kin Aug 22 '16 at 14:34
  • @StefanMonov This is a very application-specific question, I'm not sure how it could be helpful in the future, especially if Microsoft decide to change even a tiny bit of the the entire mechanism may invalidate the solution. So I'd suggest to figure out how it works and try to write the code as less as possible and don't bother with performence, or you may try to write some fully-native codes instead and don't use WMI. – Ken Kin Aug 22 '16 at 14:36
2

Here is a piece of code that should do it:

class Program
{
    static void Main(string[] args)
    {
        // do your stuff

        if (!WasStartedWithPause())
        {
            Console.WriteLine("Press any key to continue . . . ");
            Console.ReadKey(true);
        }
    }
}

public static bool WasStartedWithPause()
{
    // Here, I reuse my answer at http://stackoverflow.com/questions/394816/how-to-get-parent-process-in-net-in-managed-way
    Process parentProcess = ParentProcessUtilities.GetParentProcess();

    // are we started by cmd.exe ?
    if (string.Compare(parentProcess.MainModule.ModuleName, "cmd.exe", StringComparison.OrdinalIgnoreCase) != 0)
        return false;

    // get cmd.exe command line
    string cmdLine = GetProcessCommandLine(parentProcess);

    // was it started with a pause?
    return cmdLine != null & cmdLine.EndsWith("& pause\"");
}

public static string GetProcessCommandLine(Process process)
{
    if (process == null)
        throw new ArgumentNullException("process");

    // use WMI to query command line
    ManagementObjectCollection moc = new ManagementObjectSearcher("SELECT CommandLine FROM Win32_Process WHERE ProcessId=" + process.Id).Get();
    foreach (ManagementObject mo in moc)
    {
        return (string)mo.Properties["CommandLine"].Value;
    }
    return null;
}
Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
2

Sounds like this prompt is provided by the pause command. This command is automatically added by Visual Studio.

When you run your project outside of Visual Studio, there is no reason to "detect" this command. You can safely assume that it will not be added to your program. This means you can go ahead and add whatever prompt you want, similar to:

Console.WriteLine("Press any key...");
Console.Read();

See this question.

Community
  • 1
  • 1
s_hewitt
  • 4,252
  • 24
  • 24
  • Good answer on the mechanism, tho it turns out that's not the most helpful part of my question to me unless I find a way to detect that it is happening. Do you know how to detect if my app is being wrapped that way? I would like to detect this because I don't want to disable the VS feature, and I don't want to be prompted twice to kill my app. – Merlyn Morgan-Graham Sep 18 '11 at 01:35
  • I made an edit, does that help? I am not familiar enough with the command line and C# to know whether or not you can determine if the current program was called with the `pause` as a parameter. – s_hewitt Sep 18 '11 at 01:38
1

This is the output of "pause" command which is added by visual studio. If you see this, the program is ended. Which comes to a question like is it possible for an application to detect that it self is ended. I think this is not logical.

Mert Gülsoy
  • 2,779
  • 1
  • 19
  • 23
  • 1
    The goal is to do the opposite - detect if you aren't hosted by VS, thus should present a pausing mechanism, and avoid a double-pause if you are hosted by VS. – Merlyn Morgan-Graham Mar 03 '13 at 00:30
1

Won't help to detect, but will add the same prompt when you run with debugger attached (F5) if you add this to the end of Main:

if (Debugger.IsAttached)
{
    Console.Write("Press any key to continue . . . ");
    Console.ReadKey();
}

It will in practice do the same as Ctrl + F5 does with & pause

Ilya Chernomordik
  • 27,817
  • 27
  • 121
  • 207
1

That message has nothing to do with your program. You can add to your program anything you like and it will perform in the same way as it would, if you were to run it from the command prompt.

Visual studio displays it for the purpose of showing you that its execution has finished running, so you know it ended correctly. If you wish to skip it you could try to "run without debugging" (or something like that; it's just below "run with debugger").

Rook
  • 60,248
  • 49
  • 165
  • 242
  • Ctrl-F5 is the shortcut for "Start Without Debugging". Will edit my question to clarify. – Merlyn Morgan-Graham Sep 18 '11 at 01:40
  • @Merlyn - Try the other one then - I know it skips the "press any key" on one of the two (with/without debugging). It should anyways. – Rook Sep 18 '11 at 01:42
  • So while I agree with the "Doctor, my arm hurts when I do this" "Well, don't do that" sentiment, I am also curious if it is possible and how to go about detecting that the prompt will be displayed. – Merlyn Morgan-Graham Sep 18 '11 at 01:46
  • @Merlyn - To my knowledge it will always be displayed when using one of those, and never when using another (w/wo debugging) option. I don't know how you could detect it, but with the above in mind, I don't know either why you would. With what purpose? – Rook Sep 18 '11 at 01:49
  • It is not displayed if I run under a debugger. This doesn't jive with my standard workflow, though, because running under the debugger is hefty. I do it when it is useful, and don't when it isn't (80% of the time). – Merlyn Morgan-Graham Sep 18 '11 at 01:54
  • @Merlyn - it is assumed (i guess) that if you're debugging through a program you'll already know when it will end ... – Rook Sep 18 '11 at 02:07
0

This prompt is given when the command system("pause")

is used.

Even I faced this problem. You can either use the function _getch() under conio.h for waiting for key press.

So you can use the following code:

      cout<<"your message here"
      _getch()

This would wait for the key press and display your own prompt.

IcyFlame
  • 5,059
  • 21
  • 50
  • 74