Search code examples
c#visual-studio-2017visual-studio-sdk

How can I get the current Projects & Solutions directory from the settings in VS 2017 and load solution using SDK?


I've built a custom tool window extension for VS 2017. Everything works except for 2 things:

I need to get the "Projects location" from Tools/Options/Projects and Solutions/Locations.

I also need to tell the current instance of VS to load a solution or project.

I have been searching for days on the VS SDK, and haven't been able to come up with much. I got this far:

ShellSettingsManager sm = new ShellSettingsManager();

But that needs one of two interfaces as a parameter, IVsSettingsManager or IServiceProvider.

I implement the interface and of course, it creates the methods, all coded with NotImplemented exceptions.

So since I'm totally clueless here, I hope someone can point me in the right direction, if not just outright show me how to do it.


Solution

  • I finally figured this out. There is a ServiceProvider you can provide to the constructor for the ShellSettingsManager. All the examples i've seen just used:

    SettingsManager sm = new ShellSettingsManager(ServiceProvider);
    

    But when I tried that, I got "ServiceProvider is a type, which is not valid in the given context"

    I finally found an example that used:

    SettingsManager sm = new ShellSettingsManager(ServiceProvider.GlobalProvider);
    

    That same example then went on...

    SettingsStore ss = sm.GetReadOnlySettingsStore(SettingsScope.UserSettings);
    

    Here is where I had to figure the rest out for myself.

    There are two enumerables in the SettingsStore, GetPropertyNames and GetSubCollectionNames. These are helpful to find the collection and property I need. So first I did this:

            string msg = "";
            foreach (string s in ss.GetPropertyNames(""))
            {
                msg += s + "\r\n";
            }
            MessageBox.Show(msg);
    

    The empty string as the parameter gets the root collection properties, and this had what I need.

    screenshot of messagebox

    So now I finally retrieved the value I needed.

    string DefaultNewProjectLocation = Environment.ExpandEnvironmentVariables(ss.GetString("", "DefaultNewProjectLocation"));
    

    The first parameter, the empty string, is the root collection, and the 2nd parameter is the property I want. It returned:

    %USERPROFILE%\source\repos
    

    Expanding the environment variable gives me the literal path.

    Now on to figuring out how to get the current instance of VS to load a project selected in my tool window.

    Edit: After another full day of searching, finally got Visual Studio to load a project in the instance which my extension is running. Several people had told me to use Process.Start and pass the path to the solution file but this started another instance of Visual Studio and loaded it there.

    I found the DTE.ExecuteCommand(string Command, string Args) but didn't know if that would be what I needed. Then I read something that said it executes commands available in the Tools/Options/Environment/Keyboard section. I perused through there and found "File.OpenProject" and figured I could send the solution file as the argument. But there was one more problem in the way...

    Adding a reference to EnvDTE and trying this:

    DTE.ExecuteCommand("File.OpenProject", "\"" + PathToSlnFile + "\"");
    

    Just gave me an error that said,

    an object reference is required for the non-static field, method or Property '_DTE.ExecuteCommand(string, string)'

    I tried instantiating it

    var dte = new DTE();
    

    But that just gave me some runtime error about not being able to read something about the COM library. I found a million examples of using DTE.ExecuteCommand but not one of them showed how to get a reference to it. I did find this in Google Books, "Mastering Visual Studio .NET: Getting the Most Out of the Visual Studio .NET Environment"

    The way in which you obtain a reference to the DTE object will depend on what type of code you are writing. Macros just use a global variable provided by the macro environment called DTE. Add-ins are passed a reference to this object when VS.NET initializes them.

    Ok, that still doesn't tell me anything. How do I get that reference?!?

    And then I got lucky. I finally stumbled across a random page (i'd link it but I closed it already and don't think I could ever find it again). It gave me what I needed.

    private DTE dte = Package.GetGlobalService(typeof(DTE)) as DTE;
    

    And now, I can do this, and it works a treat!

    dte.ExecuteCommand("File.OpenProject", "\"" + PathToSolutionFile + "\""); 
    

    Hopefully this will save some other poor slob like me from spending three full days searching and finding nothing on their foray into writing a Visual Studio Extension.