Search code examples
c#.netvisual-studiovisual-studio-2017envdte

Visual Studio 2017 - Message filter indicated that the application is busy


I'm porting over a small console application that deletes files on disk that are not in a Visual Studio project. This code worked in Visual Studio 2013, but I'm receiving the following error when I ran it in Visual Studio 2017:

System.Runtime.InteropServices.COMException: 'The message filter indicated that the application is busy. (Exception from HRESULT: 0x8001010A (RPC_E_SERVERCALL_RETRYLATER))'

Current Code:

public static int DeleteFilesNotInProject(string solutionFileAndPath, string projectName, string extension)
    {
        var returnValue = 0;
        EnvDTE80.DTE2 dte;

        // Targets Visual Studio 2017
        dte = (EnvDTE80.DTE2)Activator.CreateInstance(Type.GetTypeFromProgID("VisualStudio.DTE.15.0", true), true);

        MessageFilter.Register();

        System.Threading.Thread.Sleep(2000);

        while (!dte.Solution.IsOpen)
        {
            // make sure it is ready to open
            System.Threading.Thread.Sleep(500);
            dte.Solution.Open(solutionFileAndPath);
        }

        dte.Solution.Open(solutionFileAndPath);

        System.Threading.Thread.Sleep(5000);

        foreach (Project project in dte.Solution.Projects)
        {
            if (project.UniqueName.EndsWith(projectName))
                foreach (string s in GetFilesNotInProject(project, extension))
                {
                    FileInfo fi = new FileInfo(s);
                    File.SetAttributes(s, FileAttributes.Normal);
                    File.Delete(s);
                    returnValue++;
                }
        }

        dte.Quit();

        MessageFilter.Revoke();

        return returnValue;
    }

The exception is thrown on the while (!dte.Solution.IsOpen) line. I tried commenting that out and then it is thrown on the foreach (Project project in dte.Solution.Projects) line instead.

Notes:

  • The code is based on this MSDN article. I'm looking to keep this in a Console Application.
  • I found PowerShell, and VS Addons/Extensions, but I'm interested in keeping this a C# class called withing a Console Application.
  • I tried using just the EnvDTE, instead of EnvDTE80 but received the same error.

Solution

  • The implementation of the IOleMessageFilter registered using the CoRegisterMessageFilter function must be in a STA apartment thread.

    From the CoRegisterMessageFilter function documentation:

    Only one message filter can be registered for each thread. Threads in multithreaded apartments cannot have message filters.

    Your question stated that you are porting over a small console application and indicates that you are using C#. I don't know exactly what porting implies, but if the original code was in VB.Net, VB.Net automatically marks Console applications with the STAThreadAttribute where-as C# does not and as such the thread is created in a MTA thread.

    In C#, you apply the attribute to the entry method (Main) as shown below.

    namespace ConsoleApp1
    {
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
            }
        }
    }