Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Write status, diagnostic, and informational messages to a custom channel in the Visual Studio Output window.
Note
This guide uses the VisualStudio.Extensibility SDK. See Choose the right extensibility model for model comparison.
Full example
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace OutputWindowSample;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.Commands;
using Microsoft.VisualStudio.Extensibility.Documents;
/// <summary>
/// A sample command for displaying a test message in the output window.
/// </summary>
[VisualStudioContribution]
public class TestOutputWindowCommand : Command
{
private OutputChannel? outputChannel;
/// <inheritdoc />
public override CommandConfiguration CommandConfiguration => new("%OutputWindowSample.TestOutputWindowCommand.DisplayName%")
{
Placements = [CommandPlacement.KnownPlacements.ToolsMenu],
Icon = new(ImageMoniker.KnownValues.ToolWindow, IconSettings.IconAndText),
};
/// <inheritdoc />
public override async Task InitializeAsync(CancellationToken cancellationToken)
{
this.outputChannel = await this.Extensibility.Views().Output.CreateOutputChannelAsync(Strings.OutputWindowDisplayName, cancellationToken);
await base.InitializeAsync(cancellationToken);
}
/// <inheritdoc />
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken cancellationToken)
{
if (this.outputChannel != null)
{
await this.outputChannel.WriteLineAsync("This is a test of the output window.");
}
}
}
Tip
See the full working sample: OutputWindowSample ↗
Related concepts: Contributions and configurations — how output window channels are registered
Understand the example
Create a channel and write to it
[VisualStudioContribution]
internal class MyCommand : Command
{
private OutputChannel? outputChannel;
public override async Task InitializeAsync(CancellationToken ct)
{
this.outputChannel = await this.Extensibility.Views().Output
.CreateOutputChannelAsync("My Extension", ct);
}
public override async Task ExecuteCommandAsync(IClientContext context, CancellationToken ct)
{
if (this.outputChannel != null)
{
await this.outputChannel.Writer.WriteLineAsync("Operation started...");
// ... do work ...
await this.outputChannel.Writer.WriteLineAsync("Operation complete.");
}
}
}
Writing options
The OutputChannel provides three ways to write:
| Method | Use when |
|---|---|
outputChannel.Writer.WriteLineAsync() |
Standard text output (most common) |
outputChannel.WriteLineAsync() |
Direct channel write (convenience) |
outputChannel.PipeWriter.WriteAsync() |
High-performance binary/bulk output |
The Writer property is a standard System.IO.TextWriter ↗ that supports Write(), WriteAsync(), WriteLine(), and WriteLineAsync().
For high-performance scenarios, use the PipeWriter property (System.IO.Pipelines.PipeWriter ↗):
var bytes = new ReadOnlyMemory<byte>(Encoding.UTF8.GetBytes("Binary output message"));
await this.outputChannel.PipeWriter.WriteAsync(bytes, cancellationToken);
Tips
✅ DO
- Call
CreateOutputChannelAsync()once during initialization (e.g.,InitializeAsync) - Store the
OutputChannelas a field for reuse across methods - Use descriptive channel names that match your extension name
❌ DON'T
- Don't call
CreateOutputChannelAsync()multiple times with the same name — it throws an exception - Don't assume the Output window is visible — users must open it via View > Output
- Don't write high-frequency messages (floods the UI and hurts performance)
🔧 Troubleshooting
| Symptom | Fix |
|---|---|
| Exception on channel creation | You called CreateOutputChannelAsync twice with same name; create once in InitializeAsync |
| Messages not visible | User must select your channel from the "Show output from" dropdown |
| Null reference on channel | Ensure InitializeAsync completed before using the channel |
Next steps
- Status bar — show brief notifications inline
- User prompts — interactive message dialogs
- Add a command — trigger output from a command
- Logging and diagnostics — extension-level logging