| title | Output Window |
|---|---|
| description | Learn how to create custom output panes and write messages to the Output window. |
| category | fundamentals |
| order | 8 |
import Callout from '@components/Callout.astro';
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.
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");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
}
}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();
}
}Visual Studio has several built-in output panes you can write to:
// 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");var buildPaneGuid = VSConstants.GUID_BuildOutputWindowPane;
outputWindow.GetPane(ref buildPaneGuid, out var buildPane);
buildPane.OutputStringThreadSafe("Build message\n");var debugPaneGuid = VSConstants.GUID_OutWindowDebugPane;
outputWindow.GetPane(ref debugPaneGuid, out var debugPane);
debugPane.OutputStringThreadSafe("Debug message\n");| 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 |
// 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");// 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}");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 |
// 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();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");- 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
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);- Tool Windows - Creating custom dockable windows
- Error List - Displaying errors, warnings, and messages
- Status Bar - Brief status messages