21

I am trying to redirect the output of a C# program to a file. When using "cmd.exe" I can simply run it with myprogram.exe arg1 arg2 > out.txt, but I'd like to accomplish the same thing using Visual Studio Start Options.

I created a C# Empty Project and added this code:

using System;
class Test
{
    public static void Main(string[] args)
    {
        foreach (var arg in args) Console.WriteLine(arg);
    }
}

Then I edited the command line arguments in the Project Settings: Project Properties

Running the project with Ctrl+F5 does not work as intended. I get the command line arguments printed in the console and not in the output file:

arg1
arg2
>
output.txt

If I change the command line arguments to: arg1 arg2 "> output.txt" I get the following output:

arg1
arg2
^> output.txt

I noticed that an empty output.txt file gets created in the Output folder.

Is this thing possible to accomplish or am I forced to keep using cmd.exe to launch my program?

tyrion
  • 1,984
  • 1
  • 19
  • 25
  • This can't be done for C# projects, but (as you probably know) doing exactly this does work for C++ projects. I don't know why VS doesn't behave consistently. I guess it's just Microsoft's general lack of polish and coordination between teams. – bames53 Aug 26 '14 at 19:00

7 Answers7

11

In the strict sense, you are forced to use the command prompt to start the program with redirected output. Otherwise, you would need to parse the command line yourself, the GUI shell might not do that.

If you just want to redirect the output when you Start Debugging, then uncheck the check box of Enable the Visual Studio hosting process, you are done.

If you did not, and the "output.txt" you've seen there, in fact, is not generated by your application, but "YourApplication.vshost.exe" which is spawned before you started to debug, by Visual Studio IDE. The content would always be empty, and cannot be written; because it's locked by the Hosting Process.

fo1e8.png

However, if you want the application behaves as the same whatever the mode you start it, things are more complicated.

When you start debugging with the application, it's started with:

"YourApplication.exe" arg1 arg2

because the output is already redirected by the IDE.

And when you Start Without Debugging, it's started with:

"%comspec%" /c ""YourApplication.exe" arg1 arg2 ^>output.txt & pause"

This is the correct way to let your application gets all the arguments which you specified.

You might want to have a look at my previous answer of How can I detect if "Press any key to continue . . ." will be displayed?.

Here I'm using an approach like atavistic throwback in the code below:

  • Code of application

    using System.Diagnostics;
    using System.Linq;
    using System;
    
    class Test {
        public static void Main(string[] args) {
            foreach(var arg in args)
                Console.WriteLine(arg);
        }
    
        static Test() {
            var current=Process.GetCurrentProcess();
            var parent=current.GetParentProcess();
            var grand=parent.GetParentProcess();
    
            if(null==grand
                ||grand.MainModule.FileName!=current.MainModule.FileName)
                using(var child=Process.Start(
                    new ProcessStartInfo {
                        FileName=Environment.GetEnvironmentVariable("comspec"),
                        Arguments="/c\x20"+Environment.CommandLine,
                        RedirectStandardOutput=true,
                        UseShellExecute=false
                    })) {
                    Console.Write(child.StandardOutput.ReadToEnd());
                    child.WaitForExit();
                    Environment.Exit(child.ExitCode);
                }
    #if false // change to true if child process debugging is needed 
            else {
                if(!Debugger.IsAttached)
                    Debugger.Launch();
    
                Main(Environment.GetCommandLineArgs().Skip(1).ToArray());
                current.Kill(); // or Environment.Exit(0); 
            }
    #endif
        }
    }
    

We also need the following code so that it can work:

  • Code of extension methods

    using System.Management; // add reference is required
    
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    
    using System.Collections.Generic;
    using System.Linq;
    using System;
    
    public static partial class NativeMethods {
        [DllImport("kernel32.dll")]
        public static extern bool TerminateThread(
            IntPtr hThread, uint dwExitCode);
    
        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenThread(
            uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
    }
    
    public static partial class ProcessThreadExtensions /* public methods */ {
        public static void Abort(this ProcessThread t) {
            NativeMethods.TerminateThread(
                NativeMethods.OpenThread(1, false, (uint)t.Id), 1);
        }
    
        public static IEnumerable<Process> GetChildProcesses(this Process p) {
            return p.GetProcesses(1);
        }
    
        public static Process GetParentProcess(this Process p) {
            return p.GetProcesses(-1).SingleOrDefault();
        }
    }
    
    partial class ProcessThreadExtensions /* non-public methods */ {
        static IEnumerable<Process> GetProcesses(
            this Process p, int direction) {
            return
                from format in new[] { 
                    "select {0} from Win32_Process where {1}" }
                let selectName=direction<0?"ParentProcessId":"ProcessId"
                let filterName=direction<0?"ProcessId":"ParentProcessId"
                let filter=String.Format("{0} = {1}", p.Id, filterName)
                let query=String.Format(format, selectName, filter)
                let searcher=new ManagementObjectSearcher("root\\CIMV2", query)
                from ManagementObject x in searcher.Get()
                let process=
                    ProcessThreadExtensions.GetProcessById(x[selectName])
                where null!=process
                select process;
        }
    
        // not a good practice to use generics like this; 
        // but for the convenience .. 
        static Process GetProcessById<T>(T processId) {
            try {
                var id=(int)Convert.ChangeType(processId, typeof(int));
                return Process.GetProcessById(id);
            }
            catch(ArgumentException) {
                return default(Process);
            }
        }
    }
    

Since the parent would be Visual Studio IDE(currently named "devenv") when we are debugging. The parent and grandparent process in fact are various, and we would need a rule to perform some checking.

The tricky part is that the grandchild is the one really runs into Main. The code check for the grandparent process each time it runs. If the grandparent was null then it spawns, but the spawned process would be %comspec%, which is also the parent of the new process it going to start with the same executable of current. Thus, if the grandparent are the same as itself then it won't continue to spawn, just runs into Main.

The Static Constructor is used in the code, which is started before Main. There is an answered question on SO: How does a static constructor work?.

When we start debugging, we are debugging the grandparent process(which spawns). For debugging with the grandchild process, I made Debugger.Launch with a conditional compilation which will invoke Main, for keeping Main clear.

An answered question about the debugger would also be helpful: Attach debugger in C# to another process.

Community
  • 1
  • 1
Ken Kin
  • 4,503
  • 3
  • 38
  • 76
  • This setting has now disappeared. I was using it but now after the update it's missing. I'm on VS2017 v15.5.2 now – mbudnik Jan 15 '18 at 10:03
  • @mbudnik: You don't have to do so now, just do what the OP did -- specify the redirection will get things work. – Ken Kin Jan 15 '18 at 12:15
  • @mbudnik: The problem you met only happens when you `Start Without Debugging` in vs2017. I've just done a test(again) with my answer, and it still works in either case (debug/non-debug) without the nonexistent-to-the-present option involved. Just apply it and you'll feel the magic. – Ken Kin Jan 15 '18 at 13:14
  • 1
    OK, sorry. I get it now. Should've read whole answer first. Was searching for solution without code changes. The setting change was doing the trick for me and now they have removed it. – mbudnik Jan 15 '18 at 13:26
9

I not sure if this can be done in Visual Studio. My solution would be to set a new output for the console. This example is from the MSDN:

Console.WriteLine("Hello World");
FileStream fs = new FileStream("Test.txt", FileMode.Create);
// First, save the standard output.
TextWriter tmp = Console.Out;
StreamWriter sw = new StreamWriter(fs);
Console.SetOut(sw);
Console.WriteLine("Hello file");
Console.SetOut(tmp);
Console.WriteLine("Hello World");
sw.Close();

http://msdn.microsoft.com/en-us/library/system.console.setout.aspx

Kai
  • 1,953
  • 2
  • 13
  • 18
4

In the Start Options section, change your command line arguments textbox in this way

args1 args2 1>output.txt

This redirects the standard output (1) creating a file named output.txt
If you want to append to a previous version of the file write

args1 args2 1>>output.txt

Now you can debug the program Step-by-Step while the output console is redirected

Steve
  • 213,761
  • 22
  • 232
  • 286
  • Unfortunately that doesn't work in Visual Studio 2012. If you print args[0] from Main(), it'll print `1>output.txt` in the Console window. – Matthew Watson Apr 28 '13 at 12:12
  • I just realised - it works as long as you have at least one argument. If you just put `>output.txt` it *doesn't* work... Wait {edit} Still doesn't work... Hmm – Matthew Watson Apr 28 '13 at 12:18
  • 2
    Ok I found out what it is - if you press F5 to run under the debugger, it works. If you press Ctrl-F5 to run *outside* the debugger (but still launching from VS), then it doesn't work. Note that the OP is asking for it to work from `Ctrl-F5`. – Matthew Watson Apr 28 '13 at 12:26
  • @MatthewWatson Oh, well you are right. Now I think I should ask the OP why he/she needs to run the program from the VS IDE without debugging. – Steve Apr 28 '13 at 12:31
  • Yes, it would seem unneccessary - you could just write a little batch file to launch it with the appropriate arguments and run that instead. – Matthew Watson Apr 28 '13 at 12:33
  • This is just completely and utterly wrong. These redirection commands are valid for the command interpretor, cmd.exe. They have no meaning in the present context. This answer should be removed. – David Heffernan Apr 28 '13 at 12:44
  • I don't agree at all. With these command line arguments I can redirect the output of the current debugged console application to a file. Why do you say that it doesn't works? – Steve Apr 28 '13 at 12:47
  • It apparently only works if I uncheck "Enable the Visual Studio hosting process" and run it with F5. That seems strange to me. Btw there isn't any particular reason to use VS instead of cmd.exe to do that, I was just assuming it was possible (and easy) to do. I wanted to do that to be able to profile the program using `Analyze -> Start performance analysis` but I guess this is still possible using the command line to profile it (somehow). – tyrion Apr 28 '13 at 13:03
  • I am a bit perplexed by this problem. I have the check on 'Enable the Visual Studio Hosting process' and it works as expected (F5) and this is all I really need to debug. I hope someone more knowledgable than me could explain what is the problem with CTRl+F5 or else. It is not to exclude a bug in VS2012, the first tries failed here as well, then, after changing startup mode, a few tries and then returning to the above syntax everything worked. – Steve Apr 28 '13 at 15:33
  • Also, looking at the .csproj.user file I have the same syntax with the `>` inplace of `>` – Steve Apr 28 '13 at 15:35
3

You can open the .csproj.user in an external editor and change the StartArguments from:

<StartArguments>arg1 arg2 &gt; output.txt</StartArguments>

to

<StartArguments>arg1 arg2 > output.txt</StartArguments>
Darko Kenda
  • 4,781
  • 1
  • 28
  • 31
3

I would suggest a better way without of need to writing any piece of code!

The start up settings

Just config visual studio to start your program as an external program.

  • Good. However, this approach would have some issue if you are in debugging mode; and also make different behavior between running under VS(not debugging) and directly under console. I'd suggest using a external program like [cmdow](http://www.commandline.co.uk/cmdow/). Upvote anyway. – Ken Kin May 07 '13 at 16:25
  • Of course debugging option are still available but we can't use visual studio hosting process! – Ali Bahraminezhad May 07 '13 at 16:28
  • Yeah ;) I'm still thinking why the output buffer won't save in the text file in visual studio hosting process. – Ali Bahraminezhad May 07 '13 at 16:59
  • 1
    Because the hosting process is started before the real app starts, but the real app is not able to redirect its output to hosting process's, since it's spawned by the IDE and not the real app. – Ken Kin May 07 '13 at 17:12
  • This should be the accepted answer. Does what the question asked and helped me out too! – snow_FFFFFF Mar 23 '15 at 22:37
2

you are going to have to continue to use cmd.exe if you want to pipe your output to a file. the Command line arguments: is for command line arguments, therefore anything you try will be escaped to be a command line argument. Pipes and redirectors are not command line arguments, therefore they are getting escaped.

I would just create a .bat file that calls the program how I like. You could pin that to the taskbar and simply run it.

Charles Lambert
  • 5,042
  • 26
  • 47
0

The simple way is: Right click on the Project => Properties ==> Debug

enter image description here

And then make sure to run your program in Debug mode.

Shadi Serhan
  • 309
  • 3
  • 9