Wednesday, February 27, 2013

Searching the Visual Studio Toolbox


Ever want to add a control to the design surface, but you have so many controls that you have a difficult time finding them? I was shown this neat shortcut that has saved me a lot of time over the past few days.

  1. Click on the toolbox or use the Hotkey Alt+Ctrl+x
  2. Start typing the name of the control. The control will be highlighted
    1. image
  3. If you have multiple controls that match the text, hit Tab to go to the next one.

Simple and easy!

Monday, February 18, 2013

Querying TFS for Data

At my company we are in the process of migrating to a new TFS environment. As part of that we wanted to do some house cleaning and only migrate the projects that have been used recently.

We’ve got a lot of clients, each with their own project that have been created over the past 3 years. We wanted an easy way to see what the latest activity was on the project. That will give us a good view into what projects can be archived and which ones need to be moved.

I could have just gone to Source Control and did a “view history” for each project, but that would have taken a long time (and be very tedious). Instead I wrote a simple console app that digs into each project collection and project and exports some simple project/Check-in data to a CSV file. I can then open up the CSV in Excel and add other info like “Code Status”, and “Migration Status” easily.

Here is the code I used. This is a pretty basic example and TFS provides a lot more, but it did what I needed it to. As I always say, Keep it simple!

using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace TFSInterrogator
    class Program
        static void Main(string[] args)
            //Set up the TFS SErver URL
            string tfsServer = "";
            tfsServer = tfsServer.Trim();
            var tfsUri = new Uri(tfsServer);

            //Get the Configuration Server
            var configurationServer = TfsConfigurationServerFactory.GetConfigurationServer(tfsUri);

            // Get the catalog of team project collections
            Guid[] gVar = new Guid[] { CatalogResourceTypes.ProjectCollection };
            var collectionNodes = configurationServer.CatalogNode.QueryChildren(gVar, false, CatalogQueryOptions.None);

            List<TfspcInfo> collections = new List<TfspcInfo>();

            //Iterate over the list of collections
            foreach (var collectionNode in collectionNodes)
                Guid collectionId = new Guid(collectionNode.Resource.Properties["InstanceID"]);

                var teamProjectCollection = configurationServer.GetTeamProjectCollection(collectionId);
                TfspcInfo info = new TfspcInfo();
                info.Name = teamProjectCollection.Name;

                Console.WriteLine("Checking Project Collection:" + info.Name);

                VersionControlServer versionControl = null;

                //If a project collection is offline, the Version Control cannot be retrieved. This catches this scenario
                    versionControl = teamProjectCollection.GetService<VersionControlServer>();
                catch (TeamFoundationServiceUnavailableException)

                // Get a catalog of team projects for the collection
                Guid[] hVar = new Guid[] { CatalogResourceTypes.TeamProject };
                var projectNodes = collectionNode.QueryChildren(hVar, false, CatalogQueryOptions.None);

                info.Projects = new List<TfsProjectInfo>();

                // List the team projects in the collection
                foreach (var projectNode in projectNodes)
                    TfsProjectInfo pInfo = new TfsProjectInfo();
                    pInfo.Name = projectNode.Resource.DisplayName;

                    Console.WriteLine("Checking last check in for Project :" + pInfo.Name);

                    // get the path to the project.
                    string vcpath = @"$/" + pInfo.Name + "/";

                    //Gets all changesets for the project and returns the most recent one.
                    if (versionControl != null)
                        List<Changeset> items = versionControl.QueryHistory(

                        if (items.Count > 0)
                            pInfo.LastCheckin = items.FirstOrDefault();


        private static void WriteToFile(List<TfspcInfo> collections)
            var sbLine = new StringBuilder();
            sbLine.Append("Project Collection,Project,Last Check-in User, Last Check-in Date,Last Check-in Comment");
            foreach (var projectCollection in collections)
                Console.WriteLine("Packaging up Project Collection " + projectCollection.Name + " For Export.");
                foreach (var project in projectCollection.Projects)
                    sbLine.Append(projectCollection.Name + ",");
                    sbLine.Append(project.Name + ",");

                    if (project.LastCheckin != null)
                        sbLine.Append(project.LastCheckin.Committer + ",");
                        sbLine.Append(project.LastCheckin.CreationDate + ",");
                        sbLine.Append(StringToCSVCell(project.LastCheckin.Comment) + ",");
            Console.WriteLine("Exporting to File...");
            File.AppendAllText("c:\\TFSExport.csv", sbLine.ToString());

        public static string StringToCSVCell(string str)
            bool mustQuote = (str.Contains(",") || str.Contains("\"") || str.Contains("\r") || str.Contains("\n"));
            if (mustQuote)
                StringBuilder sb = new StringBuilder();
                foreach (char nextChar in str)
                    if (nextChar == '"')
                return sb.ToString();

            return str;


    public class TfspcInfo
        public string Name { get; set; }
        public List<TfsProjectInfo> Projects { get; set; }

    public class TfsProjectInfo
        public string Name { get; set; }
        public Changeset LastCheckin { get; set; }

Wednesday, February 6, 2013

Quickly and Easily Deploy Websites/Web Services with TFS Build via Web Deploy (MSDeploy)


When I first started deploying code from TFS I took the simple approach and created a batch file and deployed the websites via RoboCopy. I’m a very “Keep it simple” kind of guy, so this worked for us for a long time and so nothing was changed.

With my most recent project however, we were deploying code over a slow VPN tunnel from our servers in Chicago to servers located in Europe. Due to this, the RoboCopy was taking over 4.5 hours to complete. I needed a better/faster way so I started looking into Web Deploy (MSDeploy). I was able to get it working fairly easily and I was pleasantly surprised how easy it was to get it working, and how much time its saved me! I can now deploy the code in less than 20 minutes!

I’ve outlined the process in detail below, but in general you only need to do this:

  • Add MSBuild parameters to your automated build
  • Customize the deployment parameters for the website
  • Create a batch file to an auto-generated MSDeploy script
  • Execute the batch file from the automated build


Note: In order to use MSDeploy, you need to install MSDeploy on the destination server. Details can be found on the Web Deploy/MSDeploy website here:

Step 1: Create an automated Build in TFS

This is obvious, but worth mentioning. If you don’t have an automated build, you can’t do automated deployment. here is an MSDN Article that walks you through it:

Kick off your build and verify that it builds and deploys the websites properly. You can do this by making sure the build completes properly and that the code is moved out to the code drop location. The code should be in the following location:

<Code Drop Location from Build Defaults Tab>\<Build Name>\<BuildName>_<timestamp>\PublishedWebsites



Step 2: Customize the build for MSDeploy

After the build is created, there are a few MSDeploy specific changes you need to do.

1. For the Default Template, create a new template by clicking on “New…”, selecting the Default template, naming it and clicking “OK”. This is the template we’ll be editing later on.


2. On the “Process” tab, you need to enter the following for the MSBuild Arguments:

/p:DeployOnBuild=True /p:Configuration=Release


Step 3: Kick off the build and verify MSBuild Arguments:

Now that the MSDeploy stuff has been added, you want to kick off another build. When it completes you should now start seeing some “_Package” folders in the Code Drop Location:


If you don’t see these, then go back to steps 1 and 2 and make sure you’ve got everything set up properly.

Take a look at the contents. You can see that TFS/MSBuild take care of a lot of the heavy lifting.


  • Deploy.cmd: This is a batch file that can be executed to publish code to a server that has Web Deploy installed on it. You can open it and take a look to get an idea of what its doing. Its some pretty cool stuff
  • SetParameters.xml: This is an xml file that allows you to set some specific parameters for the deployment  like IISWebsite name, etc. We’ll be using this file later to tell MSDeploy where to put the code.
  • Sourcemanifest.xml: Config file used by MSDeploy to Locate where the code files are
  • zip file: Compressed version of the published website. This is what is deployed to the server during deployments


Step 4: Save off “SetParameters.xml” files

I like using batch files for deployments and I like to save them on the build server so they are easily executed during deployments. When I do this, I add folders into the Code drop location for each build definition and create a new folder called “Batches”. We’ll need to save off the SetParameters.xml files as well, so lets create another folder in the Batches folder called SetParameters. Grab the SetParameters.xml file from step 3 and copy it into this folder.


Now we need to customize this XML file for our deployment.

Open the SetParameters.xml file in a text editor. You’ll see a “setParameter” XML node where the IIS Web Application name is being set. Update this to match the Website/Web app in IIS on the server where this will be deployed:

In my example, the WebService is actually deployed on the server to the Default Web Site –> NewsService location

IIS on the server:




Step 5: Create a batch file to execute the Deployment

Now that we have the Package folder being created and we’ve customized our XML file, we need to set up a batch file to execute MSDeploy and have it published to the server. The auto generated deploy.cmd file comes in extremely handy.

Again, we’ll leverage the “Batches” folder on the build server.

Create a new batch file called “RunDeployment.cmd” in the Batches folder and add the following lines into it:


  1. Here we set up variables used to execute the MSDeploy. I use variables so that we can easily change things like server names, etc. without changing the actual MSDeploy calls
    • _xcopyFlag: normal xCopy flags used to silently overwrite files via batch files
    • _location: this trims extra quotes from the location parameter passed in. (We’ll pass in this from the Deployment workflow later on)
    • _logfile: Sets up a log file. Very useful for troubleshooting/verification of deployments
    • _Server: name of the server were this will be deployed
    • svcname: name of the website/web service that is being deployed
  2. This moves the customized SetParameters.xml file back into the _Package folder to overwrite the auto generated one.
  3. This calls the deploy cmd file to execute the deployment.
    1. In my example, I’ve got a few extra flags for usename/password for security.
    2. There are other flags that can be used such as “-skip:objectName” that will ignore folders, but in this case we’ll keep it simple.

Step 7: Modify the Build workflow:

Now we need to make a few simple additions to the build workflow to call the batch file.

Open the Build template XAML file that was created in step 2. Typically these are stored in Source Control at $Project\BuildProcessTemplates:

When the workflow opens, add a new Sequence workflow after “Check in gated Changes..” step named “Deploy to Servers”


Open the Sequence object and drag an “InvokeProcess” task onto the surface:


Right click and select properties and enter the following information:


  • Arguments:The arguments we want to pass into the batch file
    • Example: """" + BuildDetail.DropLocation + "\_PublishedWebsites\" + """"
  • FileName:The location of the batch file created in step 5
    • Example: <Code Drop Location>\<BuildDefinition>\Batches\RunDeployment.bat”


Step 8: Run the build

Run the build and check your server. If the bits have been updated, you’ve been successful. If not, check the log file created by the batch file(filecopy.txt) and troubleshoot.

Important: Before you run the build, please make sure to backup the deployed folder on the server if you are concerned about losing anything. MSDeploy cleans up files on the server that aren’t part of the deployment, so if you have images or other files that are not part of the build, they will be deleted from the server. Also, run this on non-production environments first and hone this process down before you use it in production. If you build the batch files with parameters and variables, using MSDeploy in Staging/production environments will be easy.



While the information above may seem complex, most of the work is done by the auto generated files. Basically you only need to update your build with the MSBuild parameters, create a batch file and execute the batch file from the build.

there is a lot of power in MSDeploy that is not being used at this point, but I’ll post something else if I come across anything else useful