Archive

Posts Tagged ‘TFS API’

Use PowerShell to call TFS 2015 REST API’s


Few months ago, I was helping a client upgrading from TFS 2010 to 2015. After the upgrade, we were getting a message that says “This feature cannot be used until you configure it for this team project”.

Long story short, I had to upgrade the process template for over 77 team projects. Luckily they were all created using the Agile template 🙂 The process involves running some commands for the collection and others for each team project. That’s when I decided to script it with PowerShell.

In this post, I will show how to use Powershell to call the TFS 2015 REST API. Let me know in the comment section if you are interested in the script to upgrade the process template.

Show me the code

I am using the “Get list of team project” API which is listed at https://www.visualstudio.com/en-us/docs/integrate/api/tfs/projects


$tfsUrl = 'http://tfsUrl:8080/tfs/'
$collectionName = 'DefaultCollection'
$collectionUrl =  "$($tfsUrl)$($collectionName)"
# Construct the Get list of team projects url
$getProjectsUrl = "$($collectionUrl)/_apis/projects/?api-version=1.0"

# Call the REST API using Invoke-RestMethod. -UseDefaultCredentials for using windows authentication
$json = Invoke-RestMethod -UseDefaultCredentials -uri $getProjectsUrl
$formatedJson =  $json | Format-List
Write-Output $formatedJson

The value field contains the data we need. It is just a matter of extracting the project names from the value field

$tpNames = $json.value | select name
Write-Output $tpNames

The above snippet only shows you a list of project names on the screen. Lets do something more interesting. Let’s say we want create a new team for each team project in a collection and call it Database Team

# Create team API
$createTeamUrl = "$($collectionUrl)/_apis/projects/{0}/teams?api-version=2.2"
#Json that contains the data to be POSTed
$Body = @{
            name = "Database"
            description = "Database Team"
        }

# for each team project, create a team called Database by doing a post request to the create team api
$json.value.ForEach({
    Invoke-RestMethod -Method Post -UseDefaultCredentials -uri ($createTeamUrl -f $_.name) -Body (ConvertTo-Json $Body) -ContentType "application/json"
})

Conclusion

It is really convent and powerful to use Powershell or any other scripting language to TFS’s REST API. To learn more about TFS’s REST APIs visit https://www.visualstudio.com/en-us/docs/integrate/api/overview

About Lajak Technologies

A consulting firm in Ottawa, Ontario that provides services related to Microsoft technologies, Team Foundation Server, DevOps practices, security and more. Contact us today to help you solving your complex software problems. Visit us at http://www.lajak.com.

 

 

Advertisements

Customize Build Template in TFS2012 to Build All Solutions-Part 2


This post improves the Users Experience for Customize Build Template in TFS2012 to Build All Solutions-Part 1 solution. In this article, I will create a custom editor (dialog box) that will show all solutions in the source control repository and comma separate the selected values into the value cell of the Solutions argument we created in the previous post. The editor also reads the value in the PropertyGrid and select the matching solutions in the dialog.

History and Credits

I built this solution to generate MSI’s for Biztalk projects with the deployment framework after building the solution back in 2011 or early 2012. But to simplify the example, I am using this approach to build the selected solutions in the version control. I remember at the time I learned how to build a UIEditor for the build definition from http://blogs.msdn.com/b/jpricket/archive/2010/01/18/tfs-2010-custom-process-parameters-part-3-custom-editors.aspx

Create the Solutions’ Selector Dialog

My approach to create the dialog starts by creating the following files

C# Library Project: I am calling it TFSBuildEditors

SelectableItemsUIEditor.cs: this is the editor class that need to be referenced with the argument. It is also responsible for launching the dialog. Class extends UITypeEditor.

SelectableItemsForm: this is a WindowsForm Item. To launch the dialog (form) we need a reference to IWindowsFormsEditorService. That service supports only Windows Form instances.

SelectableItemsControl.xaml: I am using a WPF control here which is embedded into the WindowForm window. I am using an Element Host control to do so. I am using WPF control here because it makes my life easier by using binding 🙂

SelectableItem.cs: is a class the contains two properties: Item and IsItemChecked.

TfsHelper.cs: Contains some of the code we used in the previous post to retrieve the solutions from source control.

image

Creating the Project

Create a C# library project targeting .NET 4.5 (I am using Visual Studio 2012)

Add the following references:

Microsoft.TeamFoundation.Build.Client.dll

Microsoft.TeamFoundation.Client.dll

Microsoft.TeamFoundation.VersionControl.Client.dll

Creating TfsHelper

Add a folder, I am calling it TFSAPI

Added a c# class and call it TfsHelper.cs

Add the following code:


using System.Collections.Generic;
 using System.Linq;
 using Microsoft.TeamFoundation.VersionControl.Client;

namespace TFSBuildEditor.TFSApi
 {
 public class TfsHelper
 {
 public static IEnumerable<string> GetPathList(string pattern,
 VersionControlServer versionControlServer)
 {
 List<string> solutionList = new List<string>();
 var lists = versionControlServer.GetItems(pattern,
 VersionSpec.Latest,
 RecursionType.Full,
 DeletedState.NonDeleted,
 ItemType.File);

if ( lists.Items.Any())
 {
 var list = lists.Items
 .Select(i => i.ServerItem)
 .ToList();
 return list;
 }

return solutionList; ;
 }
 }
 }

SelectableItem

Create a C# class and call it SelectableItem

Use the following code to define it


using System;
 using System.ComponentModel;

namespace TFSBuildEditor
 {
 public class SelectableItem : INotifyPropertyChanged
 {
 private string item;
 public string Item
 {
 get { return item; }
 set
 {
 if (value != item)
 {
 item = value;
 OnPropertyChange("Item");
 }
 }
 }

private Boolean isItemChecked;
 public Boolean IsItemChecked
 {
 get { return isItemChecked; }
 set
 {
 if (value != isItemChecked)
 {
 isItemChecked = value;
 OnPropertyChange("IsPathChecked");
 }
 }
 }

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChange(string propertyName)
 {
 if (PropertyChanged != null)
 {
 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 }

}
 }
 }

SelectableItemControl.xaml

Add a WPF User Control to the project

I am using the following code for the xaml


<UserControl x:Class="TFSBuildEditor.SelectableItemsControl"
 xmlns="<a href="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;">http://schemas.microsoft.com/winfx/2006/xaml/presentation"</a>
 xmlns:x="<a href="http://schemas.microsoft.com/winfx/2006/xaml&quot;">http://schemas.microsoft.com/winfx/2006/xaml"</a>
 xmlns:mc="<a href="http://schemas.openxmlformats.org/markup-compatibility/2006&quot;">http://schemas.openxmlformats.org/markup-compatibility/2006"</a>
 xmlns:d="<a href="http://schemas.microsoft.com/expression/blend/2008&quot;">http://schemas.microsoft.com/expression/blend/2008"</a>
 mc:Ignorable="d"
 d:DesignHeight="300" d:DesignWidth="300">
 <Grid>
 <Grid.RowDefinitions>

</Grid.RowDefinitions>
 <ListBox Margin="0,0,0,0" Name="SelectableItemsList" ItemsSource="{Binding ItemsCollection}">
 <ListBox.ItemTemplate>
 <DataTemplate>
 <StackPanel Margin="0,0,0,17" Orientation="Horizontal">
 <CheckBox IsChecked="{Binding IsItemChecked, Mode=TwoWay}"/>
 <TextBlock Text="{Binding Item, Mode=TwoWay}" TextWrapping="Wrap"/>
 </StackPanel>
 </DataTemplate>
 </ListBox.ItemTemplate>

</ListBox>

</Grid>
 </UserControl>

SelectableItemsForm

Add a WindowsForm to the project

Add an ElementHost, OK button and a Cancel button. Adjust the anchor property for each of the controls

image

Right click on the form in the solution explorer and click view code

1-30-2013 1-52-44 AM

I am using the following code, I added comments to make easier to understand what I am doing


using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Data;
 using System.Drawing;
 using System.Linq;
 using System.Text;
 using System.Windows.Forms;
 using System.Collections.ObjectModel;
 using Microsoft.TeamFoundation.VersionControl.Client;
 using TFSBuildEditor.TFSApi;

namespace TFSBuildEditor
 {
 public partial class SelectableItemsForm : Form
 {
 public SelectableItemsForm()
 {
 InitializeComponent();

}

//Property will hold the comma separated values
 public string SelectedItems { get; set; }

//VersionControlServer instance
 public VersionControlServer VersionControlServer { get; set; }

//Collection is bound to the WPF control
 private ObservableCollection<SelectableItem> itemsCollection = null;
 public ObservableCollection<SelectableItem> ItemsCollection
 {
 get
 {
 if (itemsCollection == null)
 {
 itemsCollection = new ObservableCollection<SelectableItem>();
 }
 return itemsCollection;
 }
 set
 {
 itemsCollection = value;
 }
 }

public void Initialize()
 {
 InitializeWPFControl();
 //Idealy you want to read the workspace map
 var projList = TfsHelper.GetPathList("$/*.sln", VersionControlServer);

List<string> selectedProjects = new List<string>();

//Is the value in the propery grid null or whitespace
 if (!string.IsNullOrWhiteSpace(SelectedItems))
 {
 //convert comma seperated string to List<string>
 selectedProjects = SelectedItems.Replace(@"\", @"/").Split(char.Parse(",")).ToList();
 }

//Value cell had valid entries
 if (selectedProjects.Any())
 {
 //for each solution in version control
 foreach (var item in projList)
 {
 //Build the ItemsCollection (list of solutions) and set IsItemCheck to true if
 //The solution was checked before or typed properly in the value cell
 ItemsCollection.Add(
 new SelectableItem
 {
 IsItemChecked = (selectedProjects.Contains(item) ? true : false),
 Item = item
 }
 );
 }
 }
 else
 {
 foreach (var item in projList)
 {
 ItemsCollection.Add(new SelectableItem { IsItemChecked = false, Item = item });
 }
 }

}

private void InitializeWPFControl()
 {
 //Create new instance of the wpf control
 var selectableItemsControl = new SelectableItemsControl();
 //bind the windows form to teh WPF control
 selectableItemsControl.DataContext = this;
 //Assign the wpf control to the element host
 this.elementHost1.Child = selectableItemsControl;
 }

private void Cancel_Click(object sender, EventArgs e)
 {
 this.Close();
 }

private void OkButtons_Click(object sender, EventArgs e)
 {
 //Selected solutions?
 if (ItemsCollection.Any())
 {
 //comma seperate selected solutions
 SelectedItems = string.Join(",", ItemsCollection.Where(p => p.IsItemChecked)
 .Select(p => p.Item)
 .ToArray()).Replace(@"/", @"\");

}
 else
 {
 //Set SelectedItems to empty string
 SelectedItems = string.Empty;
 }
 DialogResult = System.Windows.Forms.DialogResult.OK;
 this.Visible = false;
 }

}
 }

SelectableItemsUIEditor

This Edit will be used later on in the meta data of the WorkFlow build Template. It is the entry point to launch the dialog. I am using the following code


using System;
 using System.Drawing.Design;
 using System.ComponentModel;
 using System.Windows.Forms.Design;
 using System.Windows.Forms;
 using Microsoft.TeamFoundation.VersionControl.Client;

namespace TFSBuildEditor
 {
 public class SelectableItemsUIEditor:UITypeEditor
 {
 public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
 {
 if (provider != null)
 {
 IWindowsFormsEditorService editorService = (IWindowsFormsEditorService)provider.GetService(typeof(IWindowsFormsEditorService));

if (editorService == null)
 {
 return value;
 }

//create new instance of the windows form
 using (SelectableItemsForm dialog = new SelectableItemsForm())
 {
 //set the VersionControlServer property
 dialog.VersionControlServer = (VersionControlServer)provider.GetService(typeof(VersionControlServer));

//Set the SelectedItems string property to the value from the property grid
 dialog.SelectedItems = value as string;

//Call initialize method that calls the tfs api and build the wpf control and populate it with data
 dialog.Initialize();

//Show dialog
 if (editorService.ShowDialog(dialog) == DialogResult.OK)
 {
 value = dialog.SelectedItems;
 }
 }
 }

return value;
 }

public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
 {
 return UITypeEditorEditStyle.Modal;
 }

}
 }

Deploy the editor

Build the project

Copy the dll and the pdb file to C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PrivateAssemblies

Modify build template

Open the build definition template from Source Control\BuildProcessTemplate

image

Click on the small button next to the default value for the Metadata argument

The parameter name must match the name of the argument

Fill the Editor’s textbox with the fully qualified name for the editor

image

Click OK and Check in the Build Process Template

Testing

Close and open Visual Studio

Open your build definition

image

To debug, open another instance of Visual Studio and attached it other instance that has the build definition open

Click on Tools—>Attach to Process

image

Select devenv.exe and click attach. Make sure your code has a break point in the EditValue method in SelectableItemsUIEditor and then click on the ellipsis button in the build definition

image

image

TFS 2012 API: Find all Solutions in Source Control


In this article, I am going to show how you can retrieve source control to find all solutions. Actually the solution I am provide can find any specific file or pattern. The code supports wildcard search. I AM NOT DOING ANY VALIDATION TO KEEP THE CODE SHORT. If more than one match found, my code will comma separate them. We will use this code in a later post to incorporate it in a Team Build Definition to build all solutions in the Source Control.

Start by Create a WPF Application

Add the following references:

Microsoft.TeamFoundation.Client

Microsoft.TeamFoundation.VersionControl.Client

image

Build the following UI but replace the values in the textboxes with your team project Url and file pattern respectively:

image

The following code is in the code behind


private void Button_Click_1(object sender, RoutedEventArgs e)
{
//Get Team Project collection form Uri
var teamProjectCollection = GetTeamProjectCollection(new Uri(TeamProjectCollectionTextBox.Text));

//Get version control instance from team project collection
var versionControlServer = GetVersionControlServer(teamProjectCollection);

//Get server path for all files with pattern
var pathList = GetPathList(PatternTextBox.Text, versionControlServer);

//Comma separate results
ResultTextBox.Text = string.Join(",", pathList);

}

private IEnumerable<string> GetPathList(string pattern,
VersionControlServer versionControlServer)
{
List<string> solutionList = new List<string>();

//Get items with pattern (must be prefixed with root path (server or local))
// for example $/ or C:\sc
//Version sepc = latest code
// recursionType = Full =  search sub folders
// DeletedState =  only non-deleted files
//ItemType = files
var lists = versionControlServer.GetItems(pattern,
VersionSpec.Latest,
RecursionType.Full,
DeletedState.NonDeleted,
ItemType.File);

if (lists.Items.Any())
{
var list = lists.Items
.Select(i => i.ServerItem)
.ToList();
solutionList.AddRange(list);
}

return solutionList; ;
}

private VersionControlServer GetVersionControlServer(TfsTeamProjectCollection tfsTeamProjectcollection)
{
var versionControlServer = tfsTeamProjectcollection.GetService<VersionControlServer>();

return versionControlServer;
}

private TfsTeamProjectCollection GetTeamProjectCollection(Uri uri)
{
var tfs = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(uri);

tfs.EnsureAuthenticated();
return tfs;
}

image