I had run into a need to intercept GetLast and GetSpecific menu commands in Visual Studio Source Control component that contact Team Foundation Server and retrieve specified items. I had quite a bit of trouble finding exactly what I needed in documentation, on MSDN library, or in MSDN forums. I have eventually found a way to do it. The class VSCommandInterceptor comes from this blog . Below are the relevant C# excerpts:
1: using System;
2: using System.IO;
3: using System.Collections;
4: using System.Collections.Generic;
5: using Extensibility;
6: using EnvDTE;
7: using EnvDTE80;
8: using EnvDTE90;
9: using Microsoft.VisualStudio.TeamFoundation;
10: using Microsoft.TeamFoundation.Client;
11: using Microsoft.VisualStudio.TeamFoundation.VersionControl;
12: using Microsoft.TeamFoundation.VersionControl.Client;
13: using Microsoft.VisualStudio;
14: using Microsoft.VisualStudio.CommandBars;
15: namespace VSTS.AddIns.PureAwesomeness
16: {
17: /// <summary>The object for implementing an Add-in.</summary>
18: /// <remarks>
19: /// </remarks>
20: /// <seealso class='IDTExtensibility2' />
21: public class Connect : IDTExtensibility2
22: {
23: #region Private Fields
24: /// <summary>
25: /// Version control extension
26: /// </summary>
27: private VersionControlExt _vcExt;
28: /// <summary>
29: /// Version control explorer window
30: /// </summary>
31: private VersionControlExplorerExt _explorer;
32: /// <summary>
33: /// Version control workspace
34: /// </summary>
35: private Workspace _workspace;
36: /// <summary>
37: /// Version control server
38: /// </summary>
39: private VersionControlServer _tfsVcSrv;
40: private DTE2 _applicationObject;
41: private AddIn _addInInstance;
42: private SortedList endWSItemColl = null;
43: private List<VSCommandInterceptor> cmdHandlers;
44: #endregion
45: /// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
46: public Connect()
47: {
48: }
49: //this is no longer necessary, but still a pretty nice way to intercept context menu & button clicks
50: private void AttachToMenuCommands( DTE2 appObj )
51: {
52: if (cmdHandlers == null) { cmdHandlers = new List<VSCommandInterceptor>(); }
53: Commands cmds = appObj.Commands;
54: //find GetLast & GetSpecific commands in VSTS IDE (IDs 21010, 21011 for GetLast; 20500, 20501 for GetSpec)
55: foreach (Command c in cmds)
56: {
57: if (c.ID == 21010 || c.ID == 21500 || c.ID == 21011 || c.ID == 21501)
58: {
59: VSCommandInterceptor vsCmd = new VSCommandInterceptor(appObj, new Guid(c.Guid), c.ID);
60: vsCmd.BeforeExecute += new EventHandler<CommandExecuteEventArgs>(VSIDECommand_BeforeExecute);
61: vsCmd.AfterExecute += new EventHandler<CommandExecuteEventArgs>(VSIDECommand_AfterExecute);
62: cmdHandlers.Add(vsCmd);
63: LogUtility.Instance.TraceInformation("Added Handling for Command: (Name {0}, ID {1}, GUID {2})", c.Name, c.ID, c.Guid);
64: }
65: }
66: cmds = null;
67: }
68: //do useful work here
69: private void VSIDECommand_BeforeExecute(object sender, CommandExecuteEventArgs e)
70: {
71: //LogUtility.Instance.TraceProperties("Invoked VSIDECommand_BeforeExecute:",e);
72: }
73: private void VSIDECommand_AfterExecute(object sender, CommandExecuteEventArgs e)
74: {
75: //LogUtility.Instance.TraceProperties("Invoked VSIDECommand_AfterExecute:",e);
76: }
77: private void AttachToVcSrvEvents(DTE2 appObj)
78: {
79: // Capture the relevent TFS objects
80: try
81: {
82: _vcExt = appObj.GetObject("Microsoft.VisualStudio.TeamFoundation.VersionControl.VersionControlExt") as VersionControlExt;
83: _explorer = _vcExt.Explorer;
84: //open source control view, if not open to avoid exceptions
85: if (_explorer == null || _explorer.Workspace == null || _explorer.Workspace.VersionControlServer == null)
86: {
87: appObj.ExecuteCommand("View.TfsSourceControlExplorer", "");
88: }
89: _workspace = _explorer.Workspace;
90: AttachToMenuCommands(appObj);
91: }
92: catch (Exception ex)
93: {
94: LogUtility.Instance.TraceError("Error attaching to TFS Version Control IDE events", ex);
95: return;
96: }
97: }
98: #region IDTExtensibility2 implementation
99: /// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
100: /// <param term='application'>Root object of the host application.</param>
101: /// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
102: /// <param term='addInInst'>Object representing this Add-in.</param>
103: /// <seealso class='IDTExtensibility2' />
104: public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
105: {
106: // Capture the plug-in context
107: try
108: {
109: //LogUtility.Instance.WriteLine("Connecting the VSTS DateModifier addin");
110: _applicationObject = (DTE2)application;
111: _addInInstance = (AddIn)addInInst;
112: //LogUtility.Instance.WriteLine("... connected the VSTS DateModifier addin");
113: }
114: catch (Exception ex)
115: {
116: LogUtility.Instance.TraceError("Error loading plugin", ex);
117: return;
118: }
119: AttachToVcSrvEvents();
120: }
121: /// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
122: /// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
123: /// <param term='custom'>Array of parameters that are host application specific.</param>
124: /// <seealso class='IDTExtensibility2' />
125: public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
126: {
127: if (cmdHandlers != null)
128: {
129: foreach (VSCommandInterceptor cmd in cmdHandlers)
130: {
131: if (cmd != null) cmd.Dispose();
132: }
133: cmdHandlers = null;
134: }
135: }
136: /// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
137: /// <param term='custom'>Array of parameters that are host application specific.</param>
138: /// <seealso class='IDTExtensibility2' />
139: public void OnAddInsUpdate(ref Array custom)
140: {
141: }
142: /// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
143: /// <param term='custom'>Array of parameters that are host application specific.</param>
144: /// <seealso class='IDTExtensibility2' />
145: public void OnStartupComplete(ref Array custom)
146: {
147: }
148: /// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
149: /// <param term='custom'>Array of parameters that are host application specific.</param>
150: /// <seealso class='IDTExtensibility2' />
151: public void OnBeginShutdown(ref Array custom)
152: {
153: }
154: #endregion
155: }
156: }
VSCommandInterceptor (a little modified from the original) follows:
1: using System;
2: using System.Collections.Generic;
3: using System.Text;
4: using EnvDTE;
5: using EnvDTE80;
6: namespace VSTS.AddIns.PureAwesomeness
7: {
8: public class VSCommandInterceptor : IDisposable
9: {
10: private DTE2 _dte;
11: private Guid _guid;
12: private int _id;
13: private bool _isDisposed;
14: public VSCommandInterceptor(DTE2 dte, Guid commandGuid, int commandId)
15: {
16: this._dte = dte;
17: this._guid = commandGuid;
18: this._id = commandId;
19: if(CommandEvents != null)
20: {
21: CommandEvents.AfterExecute += new _dispCommandEvents_AfterExecuteEventHandler(OnAfterExecute);
22: CommandEvents.BeforeExecute += new _dispCommandEvents_BeforeExecuteEventHandler(OnBeforeExecute);
23: }
24: }
25: public event EventHandler<CommandExecuteEventArgs> AfterExecute;
26: public event EventHandler<CommandExecuteEventArgs> BeforeExecute;
27: private CommandEvents commandEvents;
28: protected CommandEvents CommandEvents
29: {
30: get
31: {
32: if(commandEvents == null)
33: {
34: if(_dte != null)
35: {
36: commandEvents = _dte.Events.get_CommandEvents(_guid.ToString("B"), _id) as CommandEvents;
37: }
38: }
39: return commandEvents;
40: }
41: }
42: public void Dispose()
43: {
44: this.Dispose(true);
45: GC.SuppressFinalize(this);
46: }
47: private void Dispose(bool disposing)
48: {
49: if(!this._isDisposed && disposing)
50: {
51: if(CommandEvents != null)
52: {
53: try
54: {
55: CommandEvents.AfterExecute -= OnAfterExecute;
56: CommandEvents.BeforeExecute -= OnBeforeExecute;
57: }
58: catch (Exception ex)
59: {
60: LogUtility.Instance.TraceError("Exception while detaching VSTS IDE command handlers", ex);
61: }
62: }
63: this._isDisposed = true;
64: }
65: }
66: private void OnAfterExecute(string Guid, int ID, object CustomIn, object CustomOut)
67: {
68: if(AfterExecute != null)
69: {
70: AfterExecute(this, new CommandExecuteEventArgs(Guid,ID,CustomIn,CustomOut,false));
71: }
72: }
73: private void OnBeforeExecute(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
74: {
75: if(BeforeExecute != null)
76: {
77: BeforeExecute(this, new CommandExecuteEventArgs(Guid,ID,CustomIn,CustomOut,CancelDefault));
78: }
79: }
80: }
81: //Usage
82: // VSCommandInterceptor interceptor =
83: // new VSCommandInterceptor(
84: // serviceProvider,
85: // typeof(Microsoft.VisualStudio.VSConstants.VSStd97CmdID).GUID,
86: // (int)Microsoft.VisualStudio.VSConstants.VSStd97CmdID.CleanSln);
87: // interceptor.BeforeExecute += new EventHandler<EventArgs>(BeforeExecute);
88: //private void BeforeExecute(object sender, EventArgs e)
89: //{
90: // //TODO: Provide logic
91: //}
92: public class CommandExecuteEventArgs : System.EventArgs
93: {
94: #region private fields
95: private object customIn;
96: private object customOut;
97: private string commandGuid;
98: private int commandId;
99: private bool cancelDefault;
100: #endregion
101: public CommandExecuteEventArgs(string guid, int id, object objIn, object objOut, bool cancelDefaultAction)
102: {
103: customIn = objIn;
104: customOut = objOut;
105: commandGuid = guid;
106: commandId = id;
107: cancelDefault = cancelDefaultAction;
108: }
109: public object CustomIn
110: {
111: get { return customIn; }
112: }
113: public object CustomOut
114: {
115: get { return customOut; }
116: }
117: public string CommandGuid
118: {
119: get { return commandGuid; }
120: }
121: public int CommandId
122: {
123: get { return commandId; }
124: }
125: public bool CandelDefault
126: {
127: get { return cancelDefault; }
128: }
129: }
130: }