views:

446

answers:

2

I have some tools that perform updates on .net solutions, but they need to know the directory where the solution is located.

I added these tools as External Tools, where they appear in the IDE Tools menu, and supplying $(SolutionDir) as an argument. This works fine.

However, I want these tools to be easier to access in the IDE for the user through a custom top level menu (for which I created a visual studio integration package project) and through a context menu on solution nodes (for which I created a visual studio add-in project). I'm looking for a way to get the current solution directory through these contexts.

I tried getting the solution info from the VisualStudio.DTE object:

EnvDTE.DTE dte = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE");
string solutionDir = System.IO.Path.GetDirectoryName(dte.Solution.FullName);

But, this returns the solution directory for the add ins, not the current solution.

I tried echoing $(SolutionDir) and reading it back:

System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo("cmd", "echo $(SolutionDir)");

// The following commands are needed to redirect the standard output.
// This means that it will be redirected to the Process.StandardOutput StreamReader.
procStartInfo.RedirectStandardOutput = true;
procStartInfo.UseShellExecute = false;
// Do not create the black window.
procStartInfo.CreateNoWindow = true;
// Now we create a process, assign its ProcessStartInfo and start it
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procStartInfo;
proc.Start();
// Get the output into a string
string result = proc.StandardOutput.ReadToEnd();

But, this returned the directory for the IDE, not the current solution.

I didn't see any relevant info in the solution node CommandBar.

Alternatively, if there was a way to programmatically access the defined visual studio external tools and launch them (using the already defined macro arguments), that would work.

Any ideas?

+2  A: 

EnvDTE.DTE dte = (EnvDTE.DTE)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE"); string solutionDir = System.IO.Path.GetDirectoryName(dte.Solution.FullName);

But, this returns the solution directory for the add ins, not the current solution.

Your approach to get directory is good. What's wrong is the way you get dte object. Where is this code called? I assume it is in your add-in. Do you execute (debug) your add-in in VS which opens another instance of VS where you open your solution? So you have two instances of VS. The GetActiveObject("VisualStudio.DTE") gets random VS instance. In your case it is apparently VS with add-in project since you get path to your add-in. That's for explanation what would be the reason of your problem.

The correct way to get DTE is very simple. In fact, your add-in already has reference to DTE in which it runs (i.e. in which the solution is opened). It is stored in global variable _applicationObject in your add-in Connect class. It is set when your add-in starts in OnConnection event handler. So all you need is to call:

string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName);
Peter Macej
Thanks Peter, that was precisely the issue and solution! Now I'll look for a way to get the output from executing the tools via the custom menus to go to the output window instead of a separate window and all will work perfectly. Thanks again.
Dave Clemmer
+1  A: 

With Peter's push in the right direction, I set up the context menu addin to launch an external tool with the solution directory, and output the results to the output pane. Some example blurb from the add in:

    ///--------------------------------------------------------------------------------
    /// <summary>This method implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
    /// 
    /// <param term='application'>Root object of the host application.</param>
    /// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
    /// <param term='addInInst'>Object representing this Add-in.</param>
    /// <seealso class='IDTExtensibility2' />
    ///--------------------------------------------------------------------------------
    public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
    {
        _applicationObject = (DTE2)application;
        _addInInstance = (AddIn)addInInst;

        // get the solution command bar
        CommandBar solutionCommandBar = ((CommandBars)_applicationObject.CommandBars)["Solution"];

        // set up the main InCode
        CommandBarPopup solutionPopup = (CommandBarPopup)solutionCommandBar.Controls.Add(MsoControlType.msoControlPopup, System.Reflection.Missing.Value, System.Reflection.Missing.Value, 1, true);
        solutionPopup.Caption = "InCode";

        // add solution updater submenu
        CommandBarControl solutionUpdaterControl = solutionPopup.Controls.Add(MsoControlType.msoControlButton, System.Reflection.Missing.Value, System.Reflection.Missing.Value, 1, true);
        solutionUpdaterControl.Caption = "Update Solution";
        updateSolutionMenuItemHandler = (CommandBarEvents)_applicationObject.Events.get_CommandBarEvents(solutionUpdaterControl);
        updateSolutionMenuItemHandler.Click += new _dispCommandBarControlEvents_ClickEventHandler(updateSolution_Click);
    }

    // the event handlers for the solution submenu items
    CommandBarEvents updateSolutionMenuItemHandler;

    ///--------------------------------------------------------------------------------
    /// <summary>This property gets the solution updater output pane.</summary>
    ///--------------------------------------------------------------------------------
    protected OutputWindowPane _solutionUpdaterPane = null;
    protected OutputWindowPane SolutionUpdaterPane
    {
        get
        {
            if (_solutionUpdaterPane == null)
            {
                OutputWindow outputWindow = _applicationObject.ToolWindows.OutputWindow;
                foreach (OutputWindowPane loopPane in outputWindow.OutputWindowPanes)
                {
                    if (loopPane.Name == "Solution Updater")
                    {
                        _solutionUpdaterPane = loopPane;
                        return _solutionUpdaterPane;
                    }
                }
                _solutionUpdaterPane = outputWindow.OutputWindowPanes.Add("Solution Updater");
            }
            return _solutionUpdaterPane;
        }
    }

    ///--------------------------------------------------------------------------------
    /// <summary>This method handles clicking on the Update Solution submenu.</summary>
    /// 
    /// <param term='inputCommandBarControl'>The control that is source of the click.</param>
    /// <param term='handled'>Handled flag.</param>
    /// <param term='cancelDefault'>Cancel default flag.</param>
    ///--------------------------------------------------------------------------------
    protected void updateSolution_Click(object inputCommandBarControl, ref bool handled, ref bool cancelDefault)
    {
        try
        {
            // set up and execute solution updater thread
            UpdateSolutionDelegate updateSolutionDelegate = UpdateSolution;
            updateSolutionDelegate.BeginInvoke(UpdateSolutionCompleted, updateSolutionDelegate);
        }
        catch (System.Exception ex)
        {
            // put exception message in output pane
            SolutionUpdaterPane.OutputString(ex.Message);
        }
    }

    protected delegate void UpdateSolutionDelegate();

    ///--------------------------------------------------------------------------------
    /// <summary>This method launches the solution updater to update the solution.</summary>
    ///--------------------------------------------------------------------------------
    protected void UpdateSolution()
    {
        try
        {
            // set up solution updater process
            string solutionDir = System.IO.Path.GetDirectoryName(_applicationObject.Solution.FullName);
            System.Diagnostics.ProcessStartInfo procStartInfo = new System.Diagnostics.ProcessStartInfo(@"SolutionUpdater.exe", solutionDir);
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.UseShellExecute = false;
            procStartInfo.CreateNoWindow = true;
            System.Diagnostics.Process proc = new System.Diagnostics.Process();
            proc.StartInfo = procStartInfo;

            // execute the solution updater
            proc.Start();

            // put solution updater output to output pane
            SolutionUpdaterPane.OutputString(proc.StandardOutput.ReadToEnd());
            SolutionUpdaterPane.OutputString("Solution update complete.");
        }
        catch (System.Exception ex)
        {
            // put exception message in output pane
            SolutionUpdaterPane.OutputString(ex.Message);
        }
    }

    ///--------------------------------------------------------------------------------
    /// <summary>This method completing the update solution thread.</summary>
    /// 
    /// <param name="ar">IAsyncResult.</param>
    ///--------------------------------------------------------------------------------
    protected void UpdateSolutionCompleted(IAsyncResult ar)
    {
        try
        {
            if (ar == null) throw new ArgumentNullException("ar");

            UpdateSolutionDelegate updateSolutionDelegate = ar.AsyncState as UpdateSolutionDelegate;
            Trace.Assert(updateSolutionDelegate != null, "Invalid object type");

            updateSolutionDelegate.EndInvoke(ar);
        }
        catch (System.Exception ex)
        {
            // put exception message in output pane
            SolutionUpdaterPane.OutputString(ex.Message);
        }
    }

Bonus question: Is there a way to poll the external process to put results into the output pane as they are happening instead of when the process is done?

Dave Clemmer