Daniel Cazzulino's Blog : How to convert from EnvDTE.Project to IVsHierarchy and IVsProject and viceversa

How to convert from EnvDTE.Project to IVsHierarchy and IVsProject and viceversa

I've long been looking for an answer to how to go from an automation (DTE) project to its corresponding VSIP project instance. I've been given consistently the same non-satisfactory answer: there's no way because they are two fundamentally different object models that VSIP projects may or may not expose, and there's no consistent way of going from one to the other. Or that's what they said to me at the time.

As I'm not keen on accepting a "no" for an answer, I went on to investigate how to do it, and finally got to some working code that does it. It involves the DTE, VSIP and MSBuild. Here's the code:

public static class VsHelper
{
    public static IVsHierarchy GetCurrentHierarchy(IServiceProvider provider)
    {
        DTE vs = (DTE)provider.GetService(typeof(DTE));
        if (vs == null) throw new InvalidOperationException("DTE not found.");

        return ToHierarchy(vs.SelectedItems.Item(1).ProjectItem.ContainingProject);
    }
    public static IVsHierarchy ToHierarchy(EnvDTE.Project project)
    {
        if (project == null) throw new ArgumentNullException("project");

        string projectGuid = null;

        // DTE does not expose the project GUID that exists at in the msbuild project file.
        // Cannot use MSBuild object model because it uses a static instance of the Engine, 
        // and using the Project will cause it to be unloaded from the engine when the 
        // GC collects the variable that we declare.
        using (XmlReader projectReader = XmlReader.Create(project.FileName))
        {
            projectReader.MoveToContent();
            object nodeName = projectReader.NameTable.Add("ProjectGuid");
            while (projectReader.Read())
            {
                if (Object.Equals(projectReader.LocalName, nodeName))
                {
                    projectGuid = projectReader.ReadContentAsString();
                    break;
                }
            }
        }

        Debug.Assert(!String.IsNullOrEmpty(projectGuid));

        IServiceProvider serviceProvider = new ServiceProvider(project.DTE as
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
        
        return VsShellUtilities.GetHierarchy(serviceProvider, new Guid(projectGuid));
    }
    public static IVsProject3 ToVsProject(EnvDTE.Project project)
    {
        if (project == null) throw new ArgumentNullException("project");

        IVsProject3 vsProject = ToHierarchy(project) as IVsProject3;

        if (vsProject == null)
        {
            throw new ArgumentException("Project is not a VS project.");
        }

        return vsProject;
    }
    public static EnvDTE.Project ToDteProject(IVsHierarchy hierarchy)
    {
        if (hierarchy == null) throw new ArgumentNullException("hierarchy");

        object prjObject = null;
        if (hierarchy.GetProperty(0xfffffffe, -2027, out prjObject) >= 0)
        {
            return (EnvDTE.Project)prjObject;
        }
        else
        {
            throw new ArgumentException("Hierarchy is not a project.");
        }
    }
    public static EnvDTE.Project ToDteProject(IVsProject project)
    {
        if (project == null) throw new ArgumentNullException("project");

        return ToDteProject(project as IVsHierarchy);
    }

}

The code above requires a reference to Microsoft.Build.Engine.dll, EnvDTE.dll and Microsoft.VisualStudio.Shell.Interop.dll (part of the VS SDK).

Now, whenever you have a DTE Project and need a VSIP IVsHierarchy, you just call VsHelper.ToHierarchy(project), and inversely, when you need a DTE project from a hierarchy or VS project, you call the ToProject methods.

Enjoy! 

posted on Friday, January 06, 2006 4:11 PM by kzu

# re: How to convert from EnvDTE.Project to IVsHierarchy and IVsProject and viceversa @ Friday, January 20, 2006 1:54 PM

You dont need to read the project file to get the Project Guid, you can use the function DteHelper.GetVsHierarchy to get the IVSHierary, This is available in the GAT library.

Oscar Calvo

# re: How to convert from EnvDTE.Project to IVsHierarchy and IVsProject and viceversa @ Friday, January 20, 2006 5:53 PM

Yup, you're right Oscar. Going from a DTE.Project to an IVsHierarchy could also be done by:

1 - Getting the IVsSolution service from the service provider (taken from the project.DTE wrapped in a ServiceProvider)
2 - Calling IVsSolution.GetProjectOfUniqueName(project.UniqueName, out vsHierarchy) on it.

kzu

# re: How to convert from EnvDTE.Project to IVsHierarchy and IVsProject and viceversa @ Tuesday, February 28, 2006 4:13 AM

Hi Daniel,
Thanks for this code it is very useful.

The function GetCurrentHierarchy is not working always because a bug at least in the implementation of C# automation model. If you select a reference in the solution explorer and call GetCurrentHierarchy the vs.SelectedItems.Item(1).ProjectItem returns null so the function fail.

I solved this problem using the service IVsMonitorSelection and calling GetCurrentSelection.
I realized of this when I was implementing the copy/paste of references ;)

Gaston Milano

# MZ-Tools Articles Series: HOWTO: Get the project flavor (subtype) of a Visual Studio project from an add-in @ Wednesday, February 28, 2007 8:28 AM

It seems that as I learn more and more about the IDE services (VS SDK) and how to call them from an add-in

Anonymous