Get TFS Connection in Custom Build Workflow Parameter Editor - c#

So, I am trying to display all available TFS test suites in a custom build workflow parameter editor. See my previous question.
Now I can establish a connection to my TFS instance by using the .Net TFS API just as a normal client Application would. But I would have to embedd the URL to my TFS in the custom assembly, and that's something I would like to avoid.
That got me thinking: This code runs within Visual Studio, so it must somehow be possible to obtain information about the current TFS connection. After searching the web, a lot of different sites showed code about how to do this in a normal Visual Studio extension. So I put together something like this:
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
if (provider != null)
{
EnvDTE80.DTE2 dte;
dte = (EnvDTE80.DTE2)System.Runtime.InteropServices.Marshal.GetActiveObject("VisualStudio.DTE.12.0");
MessageBox.Show("Got dte: " + dte.ActiveDocument.ToString());
TeamFoundationServerExt ext = dte.DTE.GetObject("Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt") as TeamFoundationServerExt;
MessageBox.Show("Got tfs: " + ext);
I am able to get the DTE object, calling its ToString() method gives me System.__ComObject, so this part appearantly works.
But when I try to get the TeamFoundationServerExt object, I always get null
Any tips why this does not work?

So, as it turns out, you do not have to use the DTE stuff at all.
You actually can get the TFS connection like this:
var builddef = (IBuildDefinition)provider.GetService(typeof(IBuildDefinition));
var tpc = builddef.BuildServer.TeamProjectCollection;
var tp = builddef.TeamProject;

Related

List MS Access tables of existing Access instance from VSTO C# using COM Interop

Using VSTO and COM Interop, I am trying to access and manipulate a database that is already open in Microsoft Access. I can get hold of the Access application object, the current user name, and other properties. But when I try to get a list of the tables to print the table names, the code fails.
My end goal is to run an Access VBA macro using 'application.Run("MyMacroName")'. Can anyone tell me what I am missing or doing wrong? Thank you.
UPDATE SOLUTION
Thank you to Albert (see below) who kept providing (Visual Basic) information until I was able to get the C# code to work. The biggest things I was missing were 1) the DAO using reference (VStudio said I didn't need it, originally), and 2) an explicit DAO TableDef type for the loop ('var' would not work).
UPDATED CODE THAT WORKS:
using System.Runtime.InteropServices;
using Microsoft.Office.Interop.Access;
using Microsoft.Office.Interop.Access.Dao; // *MUST* have DAO as well
using Access = Microsoft.Office.Interop.Access; // Access = a namespace qualifier
[TestMethod()]
public void MsAccessComInteropTest() {
// explicitly cast the object to an Access.Application
// I used 'Access.Application' to qualify from a System.xxxx.Application type
var app = (Access.Application) Marshal.GetActiveObject("Access.Application");
Dprint($"{app.Name}"); // prints Microsoft Access
Dprint($"{app.CurrentUser()}"); // prints Admin
var db = app.CurrentDb();
// *MUST* use TableDef type explicitly from DAO in loop - cannot use 'var'
foreach (TableDef tbl in db.TableDefs) {
// Attributes == 0 means a user-defined table
if (tbl.Attributes == 0) {
Debug.Print($"{tbl.Name} '{tbl.Attributes.ToString()}'");
}
}
app.Run("MyVbaMacroName");
}
You need a reference to both Access, and also DAO.
These two:
Microsoft.Office.Interop.Access
C:\Program Files (x86)\Microsoft Visual Studio 12.0\
Visual Studio Tools for Office\PIA\Office14\
Microsoft.Office.Interop.Access.dll
And you need this:
Microsoft.Office.Interop.Access.Dao
C:\Program Files (x86)\Microsoft Visual Studio 12.0\
Visual Studio Tools for Office\PIA\Office14\
Microsoft.Office.interop.access.dao.dll
And thus your code will look like this:
{
Access.Application app;
app = Interaction.GetObject(Class: "Access.Application");
Access.Dao.Database db;
db = app.CurrentDb;
Access.Dao.TableDef tblDef;
foreach (var tblDef in db.TableDefs)
Debug.Print(tblDef.Name);
}
In fact, you could use late binding, and NOT even reference the interop assembles.
eg this:
{
var app = Interaction.GetObject(Class: "Access.Application");
var db = app.CurrentDB;
var tblDef;
foreach (var tblDef in db.TableDefs)
Debug.Print(tblDef.Name);
}
but, if you go 100% late binding, then you not get any intel-sense.
Also, this looks strange:
using Access = Microsoft.Office.Interop.Access;
Why the "=" in above - I don't think that looks right.
should be:
using Access Microsoft.Office.Interop.Access;
So, if you going to reference using interop - you need both Access + the DAO. (and they are different assembles, but use the same name space - not sure if that an issue in c#, but it is in vb.net
If you want a full list of tables, it's usual to use CurrentDb.Tabledefs
var db = app.CurrentDb();
foreach (var td in db.TableDefs) {
Dprint(td.Name);
}

How to lock a file under local workspace via TFS 2012 API

I am attempting to check-out a single file via workspace.PendEdit with an exclusive lock LockLevel.CheckOut. The following function succeeds (no errors) but it seems to have no effect on the file in TFS (not checked-out and no lock).
public static void Lock(string filePath)
{
var workspace = GetWorkspace(filePath);
workspace.PendEdit(new[] {filePath}, RecursionType.None, null, LockLevel.CheckOut);
}
I am suspecting that this has something to do with my TFS Workspace being local. However, Visual Studio 2015 seems to have no problem establishing a lock on the file via [Source Control Explorer]->[Right Click Selected File]->[Advanced]->[Lock]. What am I doing that's different than what VS is doing? Am I missing something?
You should use RecursionType.Full not RecursionType.None.
workspace.PendEdit(new[] {filePath}, RecursionType.Full, null, LockLevel.CheckOut);
The PendEdit() method return the number of files that were checked out/locked for the filePath you specify. The RecursionType.Full will recurse to the last child of the path.
Update:
Please try to install this TFS nuget package(https://www.nuget.org/packages/Microsoft.TeamFoundationServer.ExtendedClient/) for your API project and test if this issue still exists. If it works, no matter what version of VS you use, this issue won't appear.
After much trial and error I ended up implementing an event handler for NonFatalError like this:
private static void VersionControlServer_NonFatalError(object sender, ExceptionEventArgs e)
{
if (e.Failure != null && e.Failure.Severity == SeverityType.Error)
throw new ApplicationException("An internal TFS error occurred. See failure message for details:\r\n"+e.Failure.Message);
}
Once the event handler was hooked up to the versionControlServer object via versionControlServer.NonFatalError += VersionControlServer_NonFatalError; I was able to see what was going on with my exclusive check-outs. As it turned out, TFS was failing silently with the following error:
TF400022: The item $/Fake/Server/Path/project.config cannot be locked for checkout in workspace MYWORKSPACE;Dan Lastname. Checkout locks are not supported in local workspaces.
The solution was to change the LockLevel from LockLevel.CheckOut to LockLevel.Checkin. Its a slightly different type of lock but its sufficient for my needs and that's the type of lock VS is using when you attempt to lock a file in a local workspace. So here is my original function with the tiny change in LockLevel that made all the difference.
public static void Lock(string filePath)
{
var workspace = GetWorkspace(filePath);
workspace.PendEdit(new[] {filePath}, RecursionType.None, null, LockLevel.Checkin);
}

TFS can't find BuildDefinition

I am working on a solution where I want to get parameters from a Builddefinition per Code. When I hit it, I get an error message "No build definition was found for
team project ToyStory with name Spass-mit-Flaggen."
The used code is written below:
var tfsCreds = new TfsClientCredentials(new WindowsCredential(), false);
var tpc = new TfsTeamProjectCollection(new Uri(options.CollectionUri), tfsCreds);
var buildServer = (IBuildServer)tpc.GetService(typeof(IBuildServer));
var buildDetail = buildServer.GetBuild(new Uri(options.BuildUri));
var buildDefinition = buildServer.GetBuildDefinition(
buildDetail.TeamProject,
options.BuildDefinition);
The options object contains all program parameters. In this case they are the following strings:
options.CollectionUri == "http://tfs-test:8080/tfs/Test/"
options.BuildUri == "vstfs:///Build/Build/85"
options.BuildDefiniton == "Spass-mit-Flaggen"
Has someone an idea what's going wrong here?
Thanks in advance
You're using the old SOAP API for accessing builds. The new build system introduced in TFS 2015 doesn't use SOAP messaging, it has a totally separate REST API. You'll need to use the REST API, available in easily-consumable object model form on NuGet.

How to find in VSPackage which version control system a solution uses

I'm new to extending Visual Studio and I'm trying to find way to find which source control system is used by current solution.
I created VsPackage project and I am able to obtain reference to solution via IVsSolution and to hook up to solution events via IVsSolutionEvents.
Inside OnAfterSolutionOpen (or possibly some other if there's an alternative) I would like to act differently basing on whether the solution uses TFS or Git or something else. How can I obtain this information?
I plan to support as many Visual Studio versions as possible, but if it isn't possible I would like to support at least VS2012 and higher.
Ok, after several hours of digging I've found a solution to this. Thanks to the article of Mark Rendle and the source code for his NoGit extension I've found, that the list of registered source control plugins is located in registry: HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0_Config\SourceControlProviders (in case of VS 2013).
So now, we can have both plugin guid, and the name of the provider. This sample code can fetch those values:
var key = #"Software\Microsoft\VisualStudio\" + "12.0" + #"_Config\SourceControlProviders";
var subkey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(key);
var providerNames = subkey.GetSubKeyNames().Dump();
var dict = new Dictionary<Guid, String>();
foreach (var provGuidString in subkey.GetSubKeyNames())
{
var provName = (string)subkey.OpenSubKey(provGuidString).GetValue("");
dict.Add(Guid.Parse(provGuidString), provName);
}
Now, there are two ways I've found to obtain guid of currently active provider.
important update: Apparently the second way of obtaining currently active plugin does not work as expected. I strongly advise using first solution.
This is the way that bases on the extension mentioned earlier:
var getProvider = GetService(typeof(IVsRegisterScciProvider)) as IVsGetScciProviderInterface;
Guid pGuid;
getProvider.GetSourceControlProviderID(out pGuid);
Or we can just go to HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\12.0\CurrentSourceControlProvider and get the default value of this key:
var key2 = #"Software\Microsoft\VisualStudio\12.0\CurrentSourceControlProvider";
var guidString = (string)Microsoft.Win32.Registry.CurrentUser.OpenSubKey(key2).GetValue("");
var currentGuid = Guid.Parse(guidString);
Now we just take var activeProviderName = dict[currentGuid]; and that's all.

TFS ISubscriber plugin, how do I access the files checked in?

I am writing a plugin for TFS that performs automatic branching and merging based on an xml file stored in source. I am able to perform this on the server except I am unable to get the latest of the xml file if it was changed.
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties)
{
TeamFoundationVersionControlService versionControl = requestContext.GetService<TeamFoundationVersionControlService>();
string localTempFile = Path.GetTempFileName();
versionControl.DownloadFile(requestContext, serverItemPath, 0, VersionSpec.Parse("C" + versionControl.GetLatestChangeset(requestContext).ToString(), null).First(), localTempFile);
return EventNotificationStatus.ActionApproved
}
The issue is that because I want to intercept the checkin before it becomes a changeset, the download file function gets me the version of the latest checkin, not the version that was promoted. Does anyone know how to get the version being checked in?
You should try the artifacts field of the CheckinEvent class.
CheckinEvent ev = notificationEventArgs as CheckinEvent;
I would take a different route: use a CI build that monitors just that file.
It is way more robust and manageable than a server plug-in.

Categories

Resources