Creating VSIX package for TFS Source control explo

2019-08-11 14:54发布

问题:

I am trying to create VSIX package to extend functionality of TFS 2012 source control right click context menu when clicking on branch. I don't want to use Add-in. this has to be package which other developers can directly install. The customized menu items need to appear in the source control explorer contextual menu after they install the extension. I am not able to get any sample for this requirement or not able to get proper documentation source. One of sample I found is "TFS community branch tool", which is kind of similar functionality I am looking for, but I am not able to get the source code of it.

Appreciate your help.

回答1:

I assume that you are familiar with the .vsct file, command/menu/groups Guids/Id stuff (all this is documented in MSDN). So, the question would be which is the Guid/Id of the group inside the context menu of Source Control Explorer.

Guessing that you may want your command below the "Get Latest Version" menu entry of the context menu of a file, the code would be:

 <Commands package="guidVSMyPackagePkg">

     <Buttons>
      <Button guid="guidVSMyPackageCmdSet" id="cmdidMyCommand" priority="0x0100" type="Button">
         <Parent guid="guidSourceControlExplorerMenuGroup" id="SourceControlExplorerMenuGroupId"/>
        <Strings>
          <ButtonText>My Command</ButtonText>
        </Strings>
      </Button>
    </Buttons>
  </Commands>

  <Symbols>
    <GuidSymbol name="guidVSMyPackagePkg" value="{...}" />

    <GuidSymbol name="guidVSMyPackageCmdSet" value="{...}">
      <IDSymbol name="cmdidMyCommand" value="0x0100" />
    </GuidSymbol>

     <GuidSymbol name="guidSourceControlExplorerMenuGroup" value="{ffe1131c-8ea1-4d05-9728-34ad4611bda9}">
         <IDSymbol name="SourceControlExplorerMenuGroupId" value="0x1111" />
     </GuidSymbol>
   </Symbols>


回答2:

Building upon Carlos Quintero's answer: If you need to put the command in any other location in the Source Control Explorers context menu, you need the right Id. Using EnableVSIPLogging you can only find information for commands and their parent menus, but not the groups.

In order to find Group Ids (or any other ID for that matter) used in the Source Control Explorer you can follow these steps (for VS2015):

  1. Decompile Microsoft.VisualStudio.TeamFoundation.VersionControl.dll (using JetBrains dotPeek for instance).
  2. Open Resources\HatPackage.resources.
  3. Look up 1000.ctmenu and copy the Base64 data.
  4. Convert the data from Base64 to bytes.
  5. Save the bytes in a file as TfsMenu.cto (the extension needs to be .cto and it needs to be in a location with write rights for the next step to work).
  6. Run "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VSSDK\VisualStudioIntegration\Tools\Bin\vsct.exe" TfsMenu.cto TfsMenu.vsct to decompile the file.

Now you have the original .vsct file that was used to make the TFS plugin. In here you can look up all IDs.

To get you started finding the menuitems in TfsMenu.vsct, you can enable EnableVSIPLogging:
Add HKEY_CURRENT_USER\SOFTWARE\Microsoft\VisualStudio\14.0\General\EnableVSIPLogging as DWORD32 with value 1.
Now, in Visual Studio, when Holding Ctrl+Shift while hovering menus or clicking menu items in the Source Control Explorer, a messagebox pops up with information about that item, including the GUID and ID of that menu/menuitem



回答3:

@Erik I was so happy to run across your explanation for extracting the vsct as I trying very hard to figure out how to do that very thing. Just to expound upon your answer I converted it into code. Sharing here in case anyone is interested.

static void Main(string[] args)
{
    /*
        Extract menus from extensions
        http://stackoverflow.com/questions/29831181/creating-vsix-package-for-tfs-source-control-explorer-context-menu-extension
     */

    try
    {
        string vsctPath = ConfigurationManager.AppSettings["VSCTPath"];
        if (!File.Exists(vsctPath))
        {
            WriteConsole("The path to the vsct.exe could not be found. Please edit the app.config to set the right executable path.", ConsoleColor.Yellow);
            return;
        }

        //TODO: Convert to a command line argument
        string dllPath = @"C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\ManagementStudio\Extensions\Application\Microsoft.SqlServer.Management.SqlStudio.Explorer.dll";


        var assembly = Assembly.LoadFrom(dllPath);

        if (assembly == null)
        {
            WriteConsole("Could not load assembly.", ConsoleColor.Yellow);
            return;
        }

        var resourceName = assembly.GetManifestResourceNames().FirstOrDefault(n => Regex.IsMatch(n, @"VSPackage\.resources", RegexOptions.IgnoreCase));

        if (String.IsNullOrWhiteSpace(resourceName))
        {
            WriteConsole("Could find VSPackage.resources in assembly.", ConsoleColor.Yellow);
            return;
        }

        var resourceManager = new ResourceManager(Path.GetFileNameWithoutExtension(resourceName), assembly);

        if (resourceManager == null)
        {
            WriteConsole("Could find load the resource " + resourceName + ".", ConsoleColor.Yellow);
            return;
        }

        var menus = resourceManager.GetObject("Menus.ctmenu") as byte[];

        if (menus == null)
        {
            WriteConsole("Could find Menus.ctmenu resource in VSPackage.resources.", ConsoleColor.Yellow);
            return;
        }

        string dir = Path.Combine(Path.GetTempPath(), "PackageMenus");
        string fileName = Path.GetFileNameWithoutExtension(dllPath) + ".cto";

        Directory.CreateDirectory(dir);
        Directory.SetCurrentDirectory(dir);

        File.WriteAllBytes(Path.Combine(dir, fileName), menus);

        string processArgs = String.Format(@"{0} {1}.vsct", fileName, fileName);
        var pi = new ProcessStartInfo(vsctPath, processArgs);

        pi.UseShellExecute = false;
        pi.RedirectStandardError = true;
        pi.RedirectStandardOutput = true;
        var ret = Process.Start(pi);

        var output = ret.StandardOutput.ReadToEnd();
        var errors = ret.StandardError.ReadToEnd();

        Console.WriteLine(output);

        if (!string.IsNullOrWhiteSpace(errors))
        {
            Console.Write("Errors: ");
            WriteConsole(errors, ConsoleColor.Red);
        }
        else
        {
            Console.WriteLine("New files written to: " + dir);
        }
    }
    catch(Exception ex)
    {
        WriteConsole(ex.ToString(), ConsoleColor.Red);
    }
    finally
    {
        Console.WriteLine("\r\nPress any key to continue.");
        Console.ReadKey(true);
    }

}

private static void WriteConsole(string message, ConsoleColor color = ConsoleColor.White)
{
    Console.ForegroundColor = color;
    Console.WriteLine(message);
    Console.ResetColor();
}