none
How to get the Guid of a Project by name RRS feed

  • Question

  • Hi all,

    I am writing a VSPackage in C#. I need to get the Guid of a project which may be unloaded (so iterating over the Solution.Projects collection is not an option). Basically, I would like to do something like this:

     List<Guid> projGuids = null;

     uint numProjects;
     sln.GetProjectFilesInSolution(0,0,null,out numProjects);

     if (numProjects > 0)
     {
       projGuids = new List<Guid>();

       string[] projectNames = new string[numProjects];
       sln.GetProjectFilesInSolution(0,numProjects,projectNames,out numProjects);

       foreach (string projName in projectNames)
       {
         projGuids.Add(GetGuidByProjectName(projName));
       }
     }

    The crux is that I have not found any documented way to get the Guid from the names/strings returned by the Solution.GetProjectFilesInSolution method.

    I know that I can retrieve the Guid via the UniqueName property of a Project instance, however, as I pointed out, some projects may not be loaded, and hence the Project instance is not available. After some debugging, I have found that the UniqueName property seems to be the path of the project file relative to the solution base directory. I could probably build some helper function to strip the base directory name from the project file path, and then use that to get the Guid, but I figure there must be some easier (cleaner) way.

    I would appreciate any pointers here.

    BR,

      Matthias

    Monday, October 29, 2012 2:49 PM

Answers

  • Yeah, I was going to suggest bypassing DTE, something like

    // In some existing function
    IVsSolution sol = (IVsSolution)GetService(typeof(SVsSolution));
    foreach (ProjInfo info in GetProjectInfo(sol))
    {
        // Do something here
    }
    
    internal sealed class ProjInfo
    {
        public ProjInfo(Guid guid, string name)
        {
            Guid = guid;
            Name = name;
        }
    
        public Guid Guid { get; private set; }
        public string Name { get; private set; }
    }
    
    private static IEnumerable<ProjInfo> GetProjectInfo(IVsSolution sol)
    {
        Guid ignored = Guid.Empty;
        IEnumHierarchies hierEnum;
        if (ErrorHandler.Failed(sol.GetProjectEnum((int)__VSENUMPROJFLAGS.EPF_ALLPROJECTS, ref ignored, out hierEnum)))
        {
            yield break;
        }
    
        IVsHierarchy[] hier = new IVsHierarchy[1];
        uint fetched;
        while ((hierEnum.Next((uint)hier.Length, hier, out fetched) == VSConstants.S_OK) && (fetched == hier.Length))
        {
            int res = (int)VSConstants.S_OK;
    
            Guid projGuid;
            if (ErrorHandler.Failed(res = sol.GetGuidOfProject(hier[0], out projGuid)))
            {
                Debug.Fail(String.Format("IVsolution::GetGuidOfProject retuend 0x{0:X}.", res));
                continue;
            }
    
            string uniqueName;
            if (ErrorHandler.Failed(res = sol.GetUniqueNameOfProject(hier[0], out uniqueName)))
            {
                Debug.Fail(String.Format("IVsolution::GetUniqueNameOfProject retuend 0x{0:X}.", res));
                continue;
            }
    
            yield return new ProjInfo(projGuid, Path.GetFileNameWithoutExtension(uniqueName));
        }
    }
    


    Monday, October 29, 2012 5:14 PM
    Moderator

All replies

  • Nevermind, IVsSolution5 seems to do the trick.
    Monday, October 29, 2012 3:01 PM
  • Yeah, I was going to suggest bypassing DTE, something like

    // In some existing function
    IVsSolution sol = (IVsSolution)GetService(typeof(SVsSolution));
    foreach (ProjInfo info in GetProjectInfo(sol))
    {
        // Do something here
    }
    
    internal sealed class ProjInfo
    {
        public ProjInfo(Guid guid, string name)
        {
            Guid = guid;
            Name = name;
        }
    
        public Guid Guid { get; private set; }
        public string Name { get; private set; }
    }
    
    private static IEnumerable<ProjInfo> GetProjectInfo(IVsSolution sol)
    {
        Guid ignored = Guid.Empty;
        IEnumHierarchies hierEnum;
        if (ErrorHandler.Failed(sol.GetProjectEnum((int)__VSENUMPROJFLAGS.EPF_ALLPROJECTS, ref ignored, out hierEnum)))
        {
            yield break;
        }
    
        IVsHierarchy[] hier = new IVsHierarchy[1];
        uint fetched;
        while ((hierEnum.Next((uint)hier.Length, hier, out fetched) == VSConstants.S_OK) && (fetched == hier.Length))
        {
            int res = (int)VSConstants.S_OK;
    
            Guid projGuid;
            if (ErrorHandler.Failed(res = sol.GetGuidOfProject(hier[0], out projGuid)))
            {
                Debug.Fail(String.Format("IVsolution::GetGuidOfProject retuend 0x{0:X}.", res));
                continue;
            }
    
            string uniqueName;
            if (ErrorHandler.Failed(res = sol.GetUniqueNameOfProject(hier[0], out uniqueName)))
            {
                Debug.Fail(String.Format("IVsolution::GetUniqueNameOfProject retuend 0x{0:X}.", res));
                continue;
            }
    
            yield return new ProjInfo(projGuid, Path.GetFileNameWithoutExtension(uniqueName));
        }
    }
    


    Monday, October 29, 2012 5:14 PM
    Moderator
  • Where does GetService live? can I call that from outside Visual Studio? I have a DTE reference already via 

    var dte = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");


    Programmer in Jacksonville, FL

    Wednesday, January 9, 2013 8:13 PM
  • nvm, got it via this

    var dte = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
    	
    	ServiceProvider sp = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)dte);
    	IVsSolution sol = (IVsSolution)sp.GetService(typeof(SVsSolution));


    Programmer in Jacksonville, FL

    Wednesday, January 9, 2013 8:33 PM
  • GetActiveObject like that is somewhat random, you can't know what instance it will retrieve when multiple VS instances are running. What is the context of your code? How is it started out of proc and why is it running out of proc but interacting with VS?

    Ryan

    Wednesday, January 9, 2013 8:38 PM
    Moderator
  • I'm writing a simple VS automation program to load and unload projects based on the set of projects needed for the current jira bug ticket.

    Currently, once I have only the projects this particular ticket needs loaded, I tell my LinqPad to save the names of the loaded projects.

    I'm trying to build the consumer of that data, which takes the names of the projects that were previously saved, and unloads any not on the list. At the same time (or afterwards depending on what I come up with), it loads those in that list that are not currently loaded.  

    Our solution file has 144 projects of it, for my current ticket I need 18 projects loaded. ReSharper brings the machine to a crawl if I have all 144 loaded. I've found a way to walk down the list getting names and guids, but not yet figured out how to mate that with a call to Project.UnloadProject or (the tough part)Project.ReloadProject.

    Reload is tough because finding how to get the solution explorer to select the unloaded project has proven quite complex.


    Programmer in Jacksonville, FL

    Wednesday, January 9, 2013 8:47 PM
  • It shouldn't be too hard, though I am not sure if it would be exposed via DTE, sometimes DTE is...opionated on what you SHOULD be doing :) I could try putting together a sample later, but it likely wouldn't be using DTE (which is fine, it just may be a bit more verbose) if DTE doesn't expose unloaded projects (possible, I don't know much about DTE). You can select unloaded projects in the IDE so you can definetly do it programatically, likely via something likely using the solution explorer's IVsUIHierarchyWindow to call ExpandItem (which is terribly named, but also supports selection not just expansion).

    Ryan

    Wednesday, January 9, 2013 9:16 PM
    Moderator
  • You could do something like this(sorry for the code mangling the forum does, you can just copy/paste into a project):

    Helper class:

    public class ProjectEnumerator
    {
        private IVsSolution solution;
        public ProjectEnumerator(IVsSolution solution)
        {
            if (solution == null)
                throw new ArgumentNullException("solution");
            this.solution = solution;
        }
        public IEnumerable<IVsHierarchy> LoadedProjects
        {
            get
            {
                IEnumHierarchies hierEnum = GetProjectEnum(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION);
                return EnumerateProjects(hierEnum);
            }
        }
        public IEnumerable<IVsHierarchy> UnloadedProjects
        {
            get
            {
                IEnumHierarchies hierEnum = GetProjectEnum(__VSENUMPROJFLAGS.EPF_UNLOADEDINSOLUTION);
                return EnumerateProjects(hierEnum);
            }
        }
        private IEnumerable<IVsHierarchy> EnumerateProjects(IEnumHierarchies hierEnum)
        {
            IVsHierarchy[] hierFetched = new IVsHierarchy[2];
            uint fetchCount;
            int res = VSConstants.S_OK;
            while (((res = hierEnum.Next((uint)hierFetched.Length, hierFetched, out fetchCount)) == VSConstants.S_OK) &&
                   (fetchCount == hierFetched.Length))
            {
                foreach (IVsHierarchy hier in hierFetched)
                {
                    yield return hier;
                }
            }
            // If Next returns less than the number we asked for it will return S_FALSE and the count of items it returned, so mop
            // those up here. This only matters if you change the hierFetched array above to hold more than a single item.
            if (fetchCount != 0)
            {
                for (int i = 0; i < fetchCount; ++i)
                {
                    yield return hierFetched[i];
                }
             }
        }
        private IEnumHierarchies GetProjectEnum(__VSENUMPROJFLAGS enumFlags)
        {
            Guid ignored = Guid.Empty;
            IEnumHierarchies hierEnum;
                    
            ErrorHandler.ThrowOnFailure(this.solution.GetProjectEnum((uint)enumFlags, ref ignored, out hierEnum));
            return hierEnum;
        }
    }

    Example code that enumerates all projects and then selects the first unloaded one:

    public static class ExtensionMethods { public static void ForEach<T>(this IEnumerable<T> collection, Action<T> func) { foreach (T t in collection) { func(t); } } } public sealed class HierarchyPathPair { public HierarchyPathPair(IVsUIHierarchy hier, string hierPath) { Hierarchy = hier; HierarchyPath = hierPath; } public readonly string HierarchyPath; public readonly IVsUIHierarchy Hierarchy; } private static string GetFullPathToItem(IVsHierarchy hier) { // Most hierarchies in the solution will QI to IVsProject, which allows us to use GetMkDocument to get the full path to the project file, // stub hierarchies (i.e. unloaded hierarchies) do not implement IVsProject, luckily their IVsHierarchy::GetCanonicalName returns the // full path, so we fall back on that. string name; IVsProject proj = hier as IVsProject; if (proj != null) { ErrorHandler.ThrowOnFailure(proj.GetMkDocument((uint)VSConstants.VSITEMID.Root, out name)); } else { ErrorHandler.ThrowOnFailure(hier.GetCanonicalName((uint)VSConstants.VSITEMID.Root, out name)); } return name; } private void Example() { StringBuilder msg = new StringBuilder(String.Format("The loaded projects in the current solution are:{0}{0}", Environment.NewLine)); ProjectEnumerator enumerator = new ProjectEnumerator((IVsSolution)GetService(typeof(SVsSolution))); var loadedProjects = enumerator.LoadedProjects.Select((h) => new HierarchyPathPair((IVsUIHierarchy)h, GetFullPathToItem(h))); loadedProjects.ForEach((hpp) => msg.AppendLine(hpp.HierarchyPath)); msg.AppendFormat("{0}The unloadedprojects in the current solution are:{0}{0}", Environment.NewLine); List<HierarchyPathPair> unloadedProjects = new List<HierarchyPathPair>(enumerator.UnloadedProjects.Select((h) => new HierarchyPathPair((IVsUIHierarchy)h, GetFullPathToItem(h))));
    unloadedProjects.ForEach((hpp) => msg.AppendLine(hpp.HierarchyPath));
    HierarchyPathPair toSelect = unloadedProjects.FirstOrDefault(); if (toSelect != null) { msg.AppendFormat("{0} I will select {1} in the solution explorer wheen this dialog is dismissed.", Environment.NewLine, toSelect.HierarchyPath); } VsShellUtilities.ShowMessageBox(this, msg.ToString(), "Info", OLEMSGICON.OLEMSGICON_INFO, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST); if (toSelect != null) { IVsUIHierarchyWindow slnExpHierWin = VsShellUtilities.GetUIHierarchyWindow(this, VSConstants.StandardToolWindows.SolutionExplorer); if (slnExpHierWin == null) { Debug.Fail("Failed to get the solution explorer hierarchy window!"); } else { slnExpHierWin.ExpandItem(toSelect.Hierarchy, (uint)VSConstants.VSITEMID.Root, EXPANDFLAGS.EXPF_SelectItem); } } }


    • Edited by Ryan MoldenMicrosoft employee, Moderator Friday, January 11, 2013 6:47 PM fixed code oversight I made in editing (it printed out none of the unloaded projects due to ommission of the ForEach over the collection of unloaded projects)
    Thursday, January 10, 2013 5:42 AM
    Moderator
  • Thanks for this sample.  I did notice one issue.  GetFullPathToItem will fail if the IVsHierarchy item passed in is a "solution folder" and not a project. 

    Friday, January 3, 2014 11:13 PM