How to get DTE from Visual Studio process ID?

DTE is an automation framework that is used to programmatically control Visual Studio, often from another process. It internally uses COM remoting to execute commands from another process on the VS UI thread.

A while back I have written about How to start Visual Studio programmatically and get the DTE object to control the devenv.exe process. But how to get the DTE object to automate an already running Visual Studio instance? Or how to get the DTE object to automate Visual Studio started from an experimental hive (using the /rootsuffix command line option)?

Here’s the code (need to reference EnvDTE, which is a PIA, no pun intended):

 using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using EnvDTE;
 
public class AutomateVS
{
    [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;
    }
 
    static void Main(string[] args)
    {
        var devenv = System.Diagnostics.Process.Start("devenv.exe");
 
        DTE dte = null;
        do
        {
            System.Threading.Thread.Sleep(2000);
            dte = GetDTE(devenv.Id);
        }
        while (dte == null);
 
        dte.ExecuteCommand("View.CommandWindow");
        dte.StatusBar.Text = "Hello World!";
        System.Threading.Thread.Sleep(2000);
        dte.ExecuteCommand("File.Exit");
        devenv.WaitForExit();
        Marshal.ReleaseComObject(dte);
    }
}

When Visual Studio starts (regardless if it was passed the /rootsuffix command line argument), it places itself onto the Running Object Table (aka ROT). We know the moniker that the Visual Studio process will use to identify itself (it’s !VisualStudio.DTE.10.0:1234, where 1234 is the devenv.exe process ID). We then enumerate though all the objects in the ROT and find the one with the right moniker.

Then you can just keep using the DTE object as if you’re writing a macro (Visual Studio macros also use DTE to automate VS).

Comments

  • Anonymous
    August 10, 2011
    Is there a reason you can't use Marshal.GetActiveObject for this?  I'm not near a computer with VS otherwise I'd test it myself.

  • Anonymous
    August 10, 2011
    You wouldn't know how to get the right one, if there are several VS instances running. You would get a random one I believe. This way is more ugly, but it lets you get the exact process you need.

  • Anonymous
    August 11, 2011
    The documentation says you're supposed to Release() the IBindCtx

  • Anonymous
    August 12, 2011
    Great point, I've updated the code to release the allocated COM objects. It's also now runnable using F5, shows VS, shows the command window and sets the status bar text to "Hello World!".

  • Anonymous
    December 26, 2016
    hey, im trying to make a list of the running visual studios in case i have more then one open, however im only getting the first one that was opened. the second one alwasys returns null. is the second visual studio not going into the ROT? or is there another easyer way to get the list of active visual studios?