Resolve Visual Studio Macros In VSIX Packages Or T4 Templates

We all love Visual Studio Macros, right? Pre- and Post-Build actions or MSBuild scripts would get very nasty if there were no macros like “ProjectDir”, “ConfigurationName” or “OutDir”. If these macros are so useful, then why not make use of them in your own VSIX packages or T4 templates? If your answer is “because I got lost in the maze of Visual Studio’s object model and it’s too dark to see” then fear not! You will find your way out very soon.

The key to this problem is the IVsBuildMacroInfo interface. IVsHierarchy items representing a solution or a project implement this interface.

So you got a project item at hand? Then you can resolve macros. Let’s try this. The following code shows how to use the IVsBuildMacroInfo interface.

IVsBuildMacroInfo macroInfo = ...; // We got the instance by magic
string value;

macroInfo.GetBuildMacroValue("ConfigurationName", out value);

Debug.WriteLine(value); // outputs e.g. "Debug".

Ok great, but what magic got us the IVsBuildMacroInfo instance? As already stated, we can cast an IVsHierarchy we’ve got at hand.

IVsHierarchy hierarchy = ...; // We got the instance from Visual Studio
IVsBuildMacroInfo macroInfo = (IVsBuildMacroInfo) hierarchy;

// ...

That’s better. But what if I don’t have an IVsHierarchy, but an EnvDTE.Project instead?
Pablo Galiano and Daniel Cazzulino say to do it like this:

// Get an IServiceProvider from DTE.
var serviceProvider = new ServiceProvider(project.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);

// Get the current solution.
var solution = (IVsSolution)serviceProvider.GetService(typeof (SVsSolution));

// Find the IVsHierarchy for the project.
IVsHierarchy hierarchy;
solution.GetProjectOfUniqueName(project.FullName, out hierarchy);

Ok, so this all great and stuff, but I only care about resolving macros in my T4 template. Well good news, everyone! You need to set the “hostspecific” attribute of the T4 template set to “true” and then use the following code.

<#@ template hostspecific="true" language="C#" #>
<#
    var serviceProvider = (IServiceProvider) this.Host;
    var dte = (DTE) serviceProvider.GetService(typeof (DTE));
    var projectItem = dte.Solution.FindProjectItem(host.TemplateFile);
    var project = projectItem.ContainingProject;

    // continue with code from above
 #>

This is about it. Above are all the bits and pieces you need to resolve Visual Studio Macros. And thanks to my tasty fresh coffee, I’m in the mood of putting it all together in a neat and ready-to-use class (well, you might want to strengthen the code with some more error checking).

using System;
using EnvDTE;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextTemplating;

namespace MunirHusseini
{
    public class MacroResolver
    {
        private IVsBuildMacroInfo _macroInfo;

        public MacroResolver(object template)
        {
            object o;
            try
            {
                o = ((dynamic)template).Host;
            }
            catch (RuntimeBinderException)
            {
                throw new ArgumentException("The specified object does not contain a property named 'Host'.", "template");
            }

            var host = o as ITextTemplatingEngineHost;

            if (host == null)
            {
                throw new ArgumentException("The specified object does not contain a property named 'Host' of type Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost.", "template");
            }

            Init(host);
        }

        public MacroResolver(ITextTemplatingEngineHost host)
        {
            Init(host);
        }

        public MacroResolver(Project project)
        {
            var serviceProvider = new ServiceProvider(project.DTE as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
            Init(project, serviceProvider);
        }

        public MacroResolver(IVsHierarchy hierarchy)
        {
            Init(hierarchy);
        }

        public string Resolve(string macro)
        {
            string result;
            _macroInfo.GetBuildMacroValue(macro, out result);
            return result;
        }

        private void Init(ITextTemplatingEngineHost host)
        {
            var serviceProvider = (IServiceProvider)host;
            var dte = (DTE)serviceProvider.GetService(typeof(DTE));
            var projectItem = dte.Solution.FindProjectItem(host.TemplateFile);
            var project = projectItem.ContainingProject;

            Init(project, serviceProvider);
        }

        private void Init(Project project, IServiceProvider serviceProvider)
        {
            var solution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
            IVsHierarchy hierarchy;
            solution.GetProjectOfUniqueName(project.FullName, out hierarchy);

            Init(hierarchy);
        }

        private void Init(IVsHierarchy hierarchy)
        {
            _macroInfo = (IVsBuildMacroInfo)hierarchy;
        }
    }
}
Advertisements

One thought on “Resolve Visual Studio Macros In VSIX Packages Or T4 Templates

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s