.NET
C#
console output
application development
programming

Capturing console output from a .NET application C

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Capturing console output in C# is useful in tests, command-line tooling, and process orchestration. The technique depends on what you are trying to capture: output produced by your own code, error output, or output from a child process started by your application. The safest approach is to redirect explicitly, read predictably, and always restore the original streams.

Capture Output from Your Own Process

If the current process writes with Console.WriteLine, redirect Console.Out to a StringWriter. This is the simplest option for unit tests and small command wrappers.

csharp
1using System;
2using System.IO;
3using System.Text;
4
5var originalOut = Console.Out;
6var buffer = new StringBuilder();
7
8using var writer = new StringWriter(buffer);
9Console.SetOut(writer);
10
11Console.WriteLine("Hello");
12Console.WriteLine("Captured output");
13
14Console.Out.Flush();
15Console.SetOut(originalOut);
16
17Console.WriteLine(buffer.ToString());

This captures only writes that go through Console.Out. If a library writes directly to files or logging providers, this technique will not see those messages.

Restore the Original Console Writer

Redirection should be temporary. In long-running programs or test suites, forgetting to restore the original writer can leak state into later code.

csharp
1using System;
2using System.IO;
3using System.Text;
4
5TextWriter originalOut = Console.Out;
6
7try
8{
9    var builder = new StringBuilder();
10    using var capture = new StringWriter(builder);
11    Console.SetOut(capture);
12
13    Console.WriteLine("One");
14    Console.WriteLine("Two");
15    Console.Out.Flush();
16
17    string text = builder.ToString();
18    Console.SetOut(originalOut);
19    Console.WriteLine($"Captured {text.Length} characters");
20}
21finally
22{
23    Console.SetOut(originalOut);
24}

That finally block matters. It protects the application even if captured code throws an exception.

Capture Standard Error Separately

Many tools write diagnostics to standard error rather than standard output. Capture Console.Error the same way with Console.SetError.

csharp
1using System;
2using System.IO;
3using System.Text;
4
5var originalError = Console.Error;
6var errorBuffer = new StringBuilder();
7
8using var errorWriter = new StringWriter(errorBuffer);
9Console.SetError(errorWriter);
10
11Console.Error.WriteLine("Something went wrong");
12
13Console.Error.Flush();
14Console.SetError(originalError);
15
16Console.WriteLine(errorBuffer.ToString());

Keep output and error streams separate when the distinction matters. Merging them too early can make debugging harder.

Capture Output from a Child Process

If your .NET application starts another executable, use ProcessStartInfo with stream redirection. This is different from Console.SetOut, because the child process has its own standard streams.

csharp
1using System;
2using System.Diagnostics;
3using System.Threading.Tasks;
4
5var startInfo = new ProcessStartInfo
6{
7    FileName = "dotnet",
8    Arguments = "--info",
9    RedirectStandardOutput = true,
10    RedirectStandardError = true,
11    UseShellExecute = false,
12    CreateNoWindow = true
13};
14
15using var process = new Process { StartInfo = startInfo };
16process.Start();
17
18string stdout = await process.StandardOutput.ReadToEndAsync();
19string stderr = await process.StandardError.ReadToEndAsync();
20
21await process.WaitForExitAsync();
22
23Console.WriteLine(stdout);
24Console.WriteLine(stderr);

For child processes, UseShellExecute = false is required. Without it, output redirection will fail.

Use Capture in Tests

Console capture is common when testing CLI commands. The pattern is straightforward: redirect, execute, assert, restore.

csharp
1using System;
2using System.IO;
3using Xunit;
4
5public class GreetingTests
6{
7    [Fact]
8    public void WritesExpectedGreeting()
9    {
10        var originalOut = Console.Out;
11
12        try
13        {
14            using var writer = new StringWriter();
15            Console.SetOut(writer);
16
17            Console.WriteLine("Hi, test");
18
19            Console.Out.Flush();
20            Assert.Contains("Hi, test", writer.ToString());
21        }
22        finally
23        {
24            Console.SetOut(originalOut);
25        }
26    }
27}

If tests run in parallel and all of them redirect global console streams, they can interfere with each other. In that situation, either disable parallel execution for those tests or avoid global console usage in the code under test.

Common Pitfalls

The biggest mistake is assuming Console.SetOut captures all logs in the application. It only captures writes that actually go through Console.Out.

Another frequent problem is not restoring the original writer, which causes later output to disappear into a buffer unexpectedly.

When capturing child-process output, reading only one redirected stream can deadlock if the other fills its buffer. Read both streams or use asynchronous handlers.

Summary

  • Use Console.SetOut and StringWriter to capture output from the current process.
  • Restore the original writer in a finally block so redirection does not leak.
  • Capture Console.Error separately when error output matters.
  • Use ProcessStartInfo redirection for output from child processes.
  • Be careful with parallel tests and with libraries that do not write through Console.

Course illustration
Course illustration

All Rights Reserved.