39

I am writing a custom tool and I currently have it doing what I want as far as functionality. I would like to be able to write to Visual Studio if something goes wrong. (Incorrectly formatted code or whatever).

Are there any standards for this? Right now I basically can force the tool to fail and Visual Studio puts in a warning that it has done so. I'd like a category in the Output window with any resulting messages I want to send. I could also live with a more descriptive task/warning in the Error list window.

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Jeff Martin
  • 10,812
  • 7
  • 48
  • 74

6 Answers6

67

Output Window

To write to the "General" output window in Visual Studio, you need to do the following:

IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;

Guid generalPaneGuid = VSConstants.GUID_OutWindowGeneralPane; // P.S. There's also the GUID_OutWindowDebugPane available.
IVsOutputWindowPane generalPane;
outWindow.GetPane( ref generalPaneGuid , out generalPane );

generalPane.OutputString( "Hello World!" );
generalPane.Activate(); // Brings this pane into view

If, however, you want to write to a custom window, this is what you need to do:

IVsOutputWindow outWindow = Package.GetGlobalService( typeof( SVsOutputWindow ) ) as IVsOutputWindow;

// Use e.g. Tools -> Create GUID to make a stable, but unique GUID for your pane.
// Also, in a real project, this should probably be a static constant, and not a local variable
Guid customGuid = new Guid("0F44E2D1-F5FA-4d2d-AB30-22BE8ECD9789");
string customTitle = "Custom Window Title";
outWindow.CreatePane( ref customGuid, customTitle, 1, 1 );

IVsOutputWindowPane customPane;
outWindow.GetPane( ref customGuid, out customPane);

customPane.OutputString( "Hello, Custom World!" );
customPane.Activate(); // Brings this pane into view

Details on IVsOutputWindow and IVsOutputWindowPane can be found on MSDN.

Error List

For adding items to the error list, the IVsSingleFileGenerator has a method call void Generate(...) which has a parameter of the type IVsGeneratorProgress. This interface has a method void GeneratorError() which lets you report errors and warnings to the Visual Studio error list.

public class MyCodeGenerator : IVsSingleFileGenerator
{
    ...
    public void Generate( string inputFilePath, string inputFileContents, string defaultNamespace, out IntPtr outputFileContents, out int output, IVsGeneratorProgress generateProgress )
    {
        ...
        generateProgress.GeneratorError( false, 0, "An error occured", 2, 4);
        ...
    }
    ...
}

The details of GeneratorError() can be found on MSDN.

Alex
  • 3,429
  • 4
  • 36
  • 66
  • For the custom window: if I (VS2010) use your solution, I get a nice window pane that displays nothing. If I change it to outWindow.CreatePane( ref customGuid, customTitle, 1, 0 ); all is well... This change means that the window is not cleared after the solution is closed, and I can't see why this is necessary for the window to display anything at all. Can you? – BennyBarns Sep 30 '13 at 12:43
  • Sorry, I have no idea. I wrote this thing for VS2008, and I haven't really dealt with VS plugin development since that time... – Alex Sep 30 '13 at 13:47
  • In my case, panel is not activating. `customPane.Activate();` not working. any idea? – Rashmin Javiya Sep 27 '17 at 08:16
  • in VS2022 (I know, 12 years later), this fails, as outWindow.GetPane(ref generalPaneGuid, out generalPane); returns null for generalPane. The answer of @pancake provided success. – Elroy Flynn Jun 02 '22 at 22:39
9

There is another way using Marshal.GetActiveObject to grab a running DTE2 instance.

First reference EnvDTE and envdte80. This currently works in VisualStudio 2012, I haven't tried the others yet.

using System;
using System.Runtime.InteropServices;
using EnvDTE;
using EnvDTE80;

internal class VsOutputLogger
{
    private static Lazy<Action<string>> _Logger = new Lazy<Action<string>>( () => GetWindow().OutputString );

    private static Action<string> Logger
    {
        get { return _Logger.Value; }
    }

    public static void SetLogger( Action<string> logger )
    {
        _Logger = new Lazy<Action<string>>( () => logger );
    }

    public static void Write( string format, params object[] args)
    {
        var message = string.Format( format, args );
        Write( message );
    }

    public static void Write( string message )
    {
        Logger( message + Environment.NewLine );
    }

    private static OutputWindowPane GetWindow()
    {
        var dte = (DTE2) Marshal.GetActiveObject( "VisualStudio.DTE" );
        return dte.ToolWindows.OutputWindow.ActivePane;
    }
}
Daniel Fisher lennybacon
  • 3,865
  • 1
  • 30
  • 38
Ian Davis
  • 3,848
  • 1
  • 24
  • 30
4

If you want anything to appear in the Output window, it has to come from stdout. To do this, your app needs to be linked as a "console" app. Set the /SUBSYSTEM:CONSOLE flag in the project's property page, under Linker/System set the SubSystem property to CONSOLE.

Once you have your output in the window, if you include the text "Error:" it will appear as an error, or if you set "Warning:" it will appear as a warning. If your error text begins with a path/filename, followed by a line number in parenthesis, the IDE will recognize it as a "clickable" error, and navigate you automatically to the faulting line.

John Deters
  • 4,295
  • 25
  • 41
  • I am doing this in C# and there is no /subsystem stuff in the project property, do i need to make a console app to do this? its working right now as a DLL, but obviously i'm not getting the output. – Jeff Martin Jul 08 '09 at 14:55
  • It is a DLL? How is it being invoked? Normally, the output window captures the tools that are executed in the tool chain that MSBuild constructs, but as far as I knew those all were independent executables, and MSBuild would simply capture all stdout for display in the window. It just occurred to me that your custom tool may not be related to building. The "Output" window is reserved for the build process. If you are looking to put out run-time information, you should be using the debug window (with OutputDebugString() or Debug.Print() or whatever.) – John Deters Jul 08 '09 at 16:51
  • 2
    Thanks, John. I'd discovered how to create an error in MSBuild through luck and was searching the web for how to create a warning. Interestingly, your description didn't work directly for me. I got it to work by outputting ": warning: " (w/o the quotes). Visual studio wanted the leading colon, which is used to separate the file/line # info from the warning text, if the warning is associated with a particular file/line#. For some reason, VS 2008 won't accept just "warning: text". Also, it didn't matter if the output stream was stdout, stderr, etc. And it is case-sensitive. Geesh. – David Gladfelter Dec 21 '10 at 21:56
1

This is demonstrated in the following helper class from a Microsoft sample project:

https://github.com/microsoft/VSSDK-Extensibility-Samples/blob/df10d37b863feeff6e8fcaa6f4d172f602a882c5/Reference_Services/C%23/Reference.Services/HelperFunctions.cs#L28

The code is as follows:

using System;
using System.Diagnostics;
using Microsoft.VisualStudio.Shell.Interop;

namespace Microsoft.Samples.VisualStudio.Services
{
    /// <summary>
    /// This class is used to expose some utility functions used in this project.
    /// </summary>
    internal static class HelperFunctions
    {
        /// <summary>
        /// This function is used to write a string on the Output window of Visual Studio.
        /// </summary>
        /// <param name="provider">The service provider to query for SVsOutputWindow</param>
        /// <param name="text">The text to write</param>
        internal static void WriteOnOutputWindow(IServiceProvider provider, string text)
        {
            // At first write the text on the debug output.
            Debug.WriteLine(text);

            // Check if we have a provider
            if (null == provider)
            {
                // If there is no provider we can not do anything; exit now.
                Debug.WriteLine("No service provider passed to WriteOnOutputWindow.");
                return;
            }

            // Now get the SVsOutputWindow service from the service provider.
            IVsOutputWindow outputWindow = provider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
            if (null == outputWindow)
            {
                // If the provider doesn't expose the service there is nothing we can do.
                // Write a message on the debug output and exit.
                Debug.WriteLine("Can not get the SVsOutputWindow service.");
                return;
            }

            // We can not write on the Output window itself, but only on one of its panes.
            // Here we try to use the "General" pane.
            Guid guidGeneral = Microsoft.VisualStudio.VSConstants.GUID_OutWindowGeneralPane;
            IVsOutputWindowPane windowPane;
            if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
                (null == windowPane))
            {
                if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.CreatePane(ref guidGeneral, "General", 1, 0)))
                {
                    // Nothing to do here, just debug output and exit
                    Debug.WriteLine("Failed to create the Output window pane.");
                    return;
                }
                if (Microsoft.VisualStudio.ErrorHandler.Failed(outputWindow.GetPane(ref guidGeneral, out windowPane)) ||
                (null == windowPane))
                {
                    // Again, there is nothing we can do to recover from this error, so write on the
                    // debug output and exit.
                    Debug.WriteLine("Failed to get the Output window pane.");
                    return;
                }
                if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.Activate()))
                {
                    Debug.WriteLine("Failed to activate the Output window pane.");
                    return;
                }
            }

            // Finally we can write on the window pane.
            if (Microsoft.VisualStudio.ErrorHandler.Failed(windowPane.OutputString(text)))
            {
                Debug.WriteLine("Failed to write on the Output window pane.");
            }
        }
    }
}
Pancake
  • 739
  • 1
  • 5
  • 20
0

You can use the Debug and/or Trace classes. There is some information here: http://msdn.microsoft.com/en-us/library/bs4c1wda(VS.71).aspx

Best of luck.

Jon Onstott
  • 13,499
  • 16
  • 80
  • 133
-4

use System.Diagnostics.Debugger.Message

Juan Mellado
  • 14,973
  • 5
  • 47
  • 54