Output Window
The Output window displays status messages, build output, debug information, and other runtime text. Extensions can create custom output panes to display their own messages.
Quick Start with Community Toolkit
The simplest way to write to the Output window:
// Write to the General pane
await VS.Windows.WriteToOutputWindowAsync("Hello from my extension!");
// Write to a custom pane (created if it doesn't exist)
var pane = await VS.Windows.CreateOutputWindowPaneAsync("My Extension");
await pane.WriteLineAsync("Extension initialized successfully");
Creating a Custom Output Pane
With Community Toolkit
public class MyExtension
{
private OutputWindowPane _pane;
public async Task InitializeAsync()
{
// Create or get existing pane
_pane = await VS.Windows.CreateOutputWindowPaneAsync("My Extension");
}
public async Task LogAsync(string message)
{
await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] {message}");
}
public async Task LogErrorAsync(string error)
{
await _pane.WriteLineAsync($"ERROR: {error}");
await _pane.ActivateAsync(); // Bring pane to front
}
}
Traditional Approach
public class OutputPaneManager
{
private static readonly Guid PaneGuid = new Guid("YOUR-GUID-HERE");
private IVsOutputWindowPane _pane;
public async Task InitializeAsync(AsyncPackage package)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
var outputWindow = await package.GetServiceAsync(typeof(SVsOutputWindow)) as IVsOutputWindow;
// Create the pane
outputWindow.CreatePane(
ref PaneGuid,
"My Extension",
fInitVisible: 1,
fClearWithSolution: 1);
outputWindow.GetPane(ref PaneGuid, out _pane);
}
public void WriteLine(string message)
{
ThreadHelper.ThrowIfNotOnUIThread();
_pane?.OutputStringThreadSafe($"{message}{Environment.NewLine}");
}
public void Activate()
{
ThreadHelper.ThrowIfNotOnUIThread();
_pane?.Activate();
}
public void Clear()
{
ThreadHelper.ThrowIfNotOnUIThread();
_pane?.Clear();
}
}
Writing to Built-in Panes
Visual Studio has several built-in output panes you can write to:
General Pane
// Community Toolkit
await VS.Windows.WriteToOutputWindowAsync("Message to General pane");
// Traditional
var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
var generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane;
outputWindow.GetPane(ref generalPaneGuid, out var generalPane);
generalPane.OutputStringThreadSafe("Message to General pane\n");
Build Pane
var buildPaneGuid = VSConstants.GUID_BuildOutputWindowPane;
outputWindow.GetPane(ref buildPaneGuid, out var buildPane);
buildPane.OutputStringThreadSafe("Build message\n");
Debug Pane
var debugPaneGuid = VSConstants.GUID_OutWindowDebugPane;
outputWindow.GetPane(ref debugPaneGuid, out var debugPane);
debugPane.OutputStringThreadSafe("Debug message\n");
Built-in Pane GUIDs
| Pane | GUID | Constant |
|---|---|---|
| General | {65482C72-DEFA-41B7-902C-11C091889C83} | VSConstants.GUID_OutWindowGeneralPane |
| Build | {1BD8A850-02D1-11D1-BEE7-00A0C913D1F8} | VSConstants.GUID_BuildOutputWindowPane |
| Debug | {FC076020-078A-11D1-A7DF-00A0C9110051} | VSConstants.GUID_OutWindowDebugPane |
Output Methods
OutputStringThreadSafe vs OutputString
// Thread-safe - can be called from any thread
pane.OutputStringThreadSafe("Safe from any thread\n");
// Must be on UI thread
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
pane.OutputString("Must be on UI thread\n");
Tip
Always prefer OutputStringThreadSafe unless you need special formatting or are already on the UI thread.
Formatted Output
// Add timestamp prefix
public void Log(string message)
{
var timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
_pane.OutputStringThreadSafe($"[{timestamp}] {message}\n");
}
// Log levels
public void LogInfo(string message) => Log($"INFO: {message}");
public void LogWarning(string message) => Log($"WARN: {message}");
public void LogError(string message) => Log($"ERROR: {message}");
Pane Options
When creating a pane, you can configure its behavior:
outputWindow.CreatePane(
ref paneGuid,
"My Extension",
fInitVisible: 1, // 1 = visible in dropdown, 0 = hidden until first write
fClearWithSolution: 1 // 1 = clear when solution closes, 0 = preserve content
);
| Parameter | Values | Description |
|---|---|---|
fInitVisible | 0 or 1 | Whether pane appears in dropdown before first write |
fClearWithSolution | 0 or 1 | Whether to clear content when solution closes |
Showing and Activating
// Show the Output window (bring to front)
await VS.Windows.ShowOutputWindowAsync();
// Activate your specific pane
await pane.ActivateAsync();
// Traditional approach
var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
outputWindow.GetPane(ref paneGuid, out var pane);
pane.Activate();
// Show Output window via UI shell
var uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
var outputWindowGuid = new Guid("{34E76E81-EE4A-11D0-AE2E-00A0C90FFFC3}");
uiShell.FindToolWindow((uint)__VSFINDTOOLWIN.FTW_fForceCreate, ref outputWindowGuid, out var frame);
frame.Show();
Complete Example
A reusable logging service for your extension:
using System;
using System.Threading.Tasks;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Shell;
public interface IExtensionLogger
{
Task LogAsync(string message);
Task LogWarningAsync(string message);
Task LogErrorAsync(string message);
Task ClearAsync();
}
public class ExtensionLogger : IExtensionLogger
{
private readonly string _paneName;
private OutputWindowPane _pane;
public ExtensionLogger(string paneName)
{
_paneName = paneName;
}
private async Task EnsurePaneAsync()
{
_pane ??= await VS.Windows.CreateOutputWindowPaneAsync(_paneName);
}
public async Task LogAsync(string message)
{
await EnsurePaneAsync();
await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] {message}");
}
public async Task LogWarningAsync(string message)
{
await EnsurePaneAsync();
await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] ⚠ WARNING: {message}");
}
public async Task LogErrorAsync(string message)
{
await EnsurePaneAsync();
await _pane.WriteLineAsync($"[{DateTime.Now:HH:mm:ss}] ❌ ERROR: {message}");
await _pane.ActivateAsync();
}
public async Task ClearAsync()
{
await EnsurePaneAsync();
await _pane.ClearAsync();
}
}
// Usage in your package
public sealed class MyPackage : ToolkitPackage
{
public static IExtensionLogger Logger { get; private set; }
protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
{
await base.InitializeAsync(cancellationToken, progress);
Logger = new ExtensionLogger("My Extension");
await Logger.LogAsync("Extension loaded successfully");
}
}
// Usage anywhere in your extension
await MyPackage.Logger.LogAsync("Processing file...");
await MyPackage.Logger.LogErrorAsync("Failed to process file");
Best Practices
- Use descriptive pane names - Make it clear which extension owns the pane
- Add timestamps - Helps users understand when events occurred
- Use thread-safe methods - Prefer
OutputStringThreadSafefor background operations - Don’t spam - Only log meaningful information
- Activate on errors - Bring the pane to attention when something goes wrong
- Clear appropriately - Don’t clear too often; users may want to scroll back
Warning
Avoid writing large amounts of text rapidly. This can freeze the UI. For high-volume logging, consider batching writes or using a separate log file.
Deleting a Pane
Remove a custom pane when it’s no longer needed:
var outputWindow = (IVsOutputWindow)GetService(typeof(SVsOutputWindow));
var paneGuid = new Guid("YOUR-PANE-GUID");
outputWindow.DeletePane(ref paneGuid);
Note
You cannot delete built-in panes (General, Build, Debug).
See Also
- Tool Windows - Creating custom dockable windows
- Error List - Displaying errors, warnings, and messages
- Status Bar - Brief status messages