How to get list of projects in current Visual studio solution?
Asked Answered
I

3

23

When we open Package Manager Console in any open solution, it shows all the projects of that solution. How it is loading all the projects of the same solution. When I tried with below shown code it is fetching me projects of the first solution which I have opened.

    private List<Project> GetProjects()
    {
        var dte = (DTE)Marshal.GetActiveObject(string.Format(CultureInfo.InvariantCulture, "VisualStudio.DTE.{0}.0", targetVsVersion));
        var projects = dte.Solution.OfType<Project>().ToList();
        return projects;
    }
Idocrase answered 28/3, 2014 at 5:43 Comment(2)
Package Manager Console is an in-process VS extension, it never has any trouble finding the correct instance of VS. An add-in gets it on a silver platter in the OnConnection() method, application argument. This is the recommended approach of course, you can never get it wrong this way.Extinguisher
Thanks for the information. But I am not creating any addin, I am creating a visual studio package where I want specific instance of a solutions which are open.Idocrase
G
24

Here are a various set of functions that allow you to enumerate projects in a given solution. This is how you would use it with the current solution:

// get current solution
IVsSolution solution = (IVsSolution)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(IVsSolution));
foreach(Project project in GetProjects(solution))
{
    ....
}

....

public static IEnumerable<EnvDTE.Project> GetProjects(IVsSolution solution)
{
    foreach (IVsHierarchy hier in GetProjectsInSolution(solution))
    {
        EnvDTE.Project project = GetDTEProject(hier);
        if (project != null)
            yield return project;
    }
}

public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution)
{
    return GetProjectsInSolution(solution, __VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION);
}

public static IEnumerable<IVsHierarchy> GetProjectsInSolution(IVsSolution solution, __VSENUMPROJFLAGS flags)
{
    if (solution == null)
        yield break;

    IEnumHierarchies enumHierarchies;
    Guid guid = Guid.Empty;
    solution.GetProjectEnum((uint)flags, ref guid, out enumHierarchies);
    if (enumHierarchies == null)
        yield break;

    IVsHierarchy[] hierarchy = new IVsHierarchy[1];
    uint fetched;
    while (enumHierarchies.Next(1, hierarchy, out fetched) == VSConstants.S_OK && fetched == 1)
    {
        if (hierarchy.Length > 0 && hierarchy[0] != null)
            yield return hierarchy[0];
    }
}

public static EnvDTE.Project GetDTEProject(IVsHierarchy hierarchy)
{
    if (hierarchy == null)
        throw new ArgumentNullException("hierarchy");

    object obj;
    hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out obj);
    return obj as EnvDTE.Project;
}
Gean answered 1/4, 2014 at 12:5 Comment(1)
Your approach will not work in case of projects are wrapped into the Solution Folder(s) : #33210089Stainless
I
3

There may be a nicer way but I had a quick go at this and found this to work (it assumes you have a way of knowing the solution name). According to this post, GetActiveObject does not guarantee the current instance of VS which is why you're getting results from another instance. Instead, you can use the GetDTE method shown there:

[DllImport("ole32.dll")]
private static extern int CreateBindCtx(uint reserved, out IBindCtx ppbc);

public static DTE GetDTE(int processId)
{
    string progId = "!VisualStudio.DTE.10.0:" + processId.ToString();
    object runningObject = null;

    IBindCtx bindCtx = null;
    IRunningObjectTable rot = null;
    IEnumMoniker enumMonikers = null;

    try
    {
        Marshal.ThrowExceptionForHR(CreateBindCtx(reserved: 0, ppbc: out bindCtx));
        bindCtx.GetRunningObjectTable(out rot);
        rot.EnumRunning(out enumMonikers);

        IMoniker[] moniker = new IMoniker[1];
        IntPtr numberFetched = IntPtr.Zero;
        while (enumMonikers.Next(1, moniker, numberFetched) == 0)
        {
            IMoniker runningObjectMoniker = moniker[0];

            string name = null;

            try
            {
                if (runningObjectMoniker != null)
                {
                    runningObjectMoniker.GetDisplayName(bindCtx, null, out name);
                }
            }
            catch (UnauthorizedAccessException)
            {
                // Do nothing, there is something in the ROT that we do not have access to.
            }

            if (!string.IsNullOrEmpty(name) && string.Equals(name, progId, StringComparison.Ordinal))
            {
                Marshal.ThrowExceptionForHR(rot.GetObject(runningObjectMoniker, out runningObject));
                break;
            }
        }
    }
    finally
    {
        if (enumMonikers != null)
        {
            Marshal.ReleaseComObject(enumMonikers);
        }

        if (rot != null)
        {
            Marshal.ReleaseComObject(rot);
        }

        if (bindCtx != null)
        {
            Marshal.ReleaseComObject(bindCtx);
        }
    }

    return (DTE)runningObject;
} 

If you know the solution name in advance, you can find it in the MainWindowTitle property of Process and pass the ProcessID to the method above.

var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);

Whilst the above code worked, I encountered a COM error which I fixed by using the MessageFilter class shown here.

From that post, this is what the MessageFilter class looks like

public class MessageFilter : IOleMessageFilter
{            
    // Class containing the IOleMessageFilter
    // thread error-handling functions.

    // Start the filter.
    public static void Register()
    {
        IOleMessageFilter newFilter = new MessageFilter();
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(newFilter, out oldFilter);
    }


    // Done with the filter, close it.
    public static void Revoke()
    {
        IOleMessageFilter oldFilter = null;
        CoRegisterMessageFilter(null, out oldFilter);
    }


    //
    // IOleMessageFilter functions.
    // Handle incoming thread requests.
    int IOleMessageFilter.HandleInComingCall(int dwCallType,
    System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo)
    {

        //Return the flag SERVERCALL_ISHANDLED.
        return 0;
    }


    // Thread call was rejected, so try again.
    int IOleMessageFilter.RetryRejectedCall(System.IntPtr
    hTaskCallee, int dwTickCount, int dwRejectType)
    {

        if (dwRejectType == 2)
        // flag = SERVERCALL_RETRYLATER.
        {
            // Retry the thread call immediately if return >=0 &
            // <100.
            return 99;
        }
        // Too busy; cancel call.
        return -1;
    }


    int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee,
    int dwTickCount, int dwPendingType)
    {
        //Return the flag PENDINGMSG_WAITDEFPROCESS.
        return 2;
    }


    // Implement the IOleMessageFilter interface.
    [DllImport("Ole32.dll")]
    private static extern int
      CoRegisterMessageFilter(IOleMessageFilter newFilter, out
      IOleMessageFilter oldFilter);
}



[ComImport(), Guid("00000016-0000-0000-C000-000000000046"),
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
interface IOleMessageFilter
{
    [PreserveSig]
    int HandleInComingCall(
    int dwCallType,
    IntPtr hTaskCaller,
    int dwTickCount,
    IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall(
    IntPtr hTaskCallee,
    int dwTickCount,
    int dwRejectType);


    [PreserveSig]
    int MessagePending(
        IntPtr hTaskCallee,
        int dwTickCount,
        int dwPendingType);
}

Then you can access the project names like this

var dte = GetDTE(System.Diagnostics.Process.GetProcesses().Where(x => x.MainWindowTitle.StartsWith("SolutionName") && x.ProcessName.Contains("devenv")).FirstOrDefault().Id);
MessageFilter.Register();
var projects = dte.Solution.OfType<Project>().ToList();
MessageFilter.Revoke();

foreach (var proj in projects)
{
   Debug.WriteLine(proj.Name);
}

Marshal.ReleaseComObject(dte);
Ionium answered 30/3, 2014 at 17:57 Comment(2)
Hello, thanks for the solution but it is not working with VS version 12, I want it to work with all the versions of visual studioIdocrase
@Idocrase - Nothing is going to work with all versions of visual studio.Patellate
D
3

I believe you can use something like this:

var dte = (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE));
if (dte != null)
{
    var solution = dte.Solution;
    if (solution != null)
    {
        // get your projects here
    }
}
Dominga answered 31/3, 2014 at 23:12 Comment(3)
What is "GetService()"?Cytaster
@EvgeniNabokov GetService is a function that may be available if your VS package inherits from a specific class. I still havent found that class, but I have found the ServiceProvider and Package.GetGlobalService() are usually present and available when adding a Command, ToolWindow, or other new item from the extensibility project item template list.Patellate
This answer also has the same problem of missing projects that are organized inside solution folders. solution.Projects only returns the top level items, but nothing beneath it.Patellate

© 2022 - 2024 — McMap. All rights reserved.