0 Comments

In my last post (Introduction to DotNet Pretty) I mentioned that I would be added more visualizers to DotNet Pretty soon Smile. Today I finally managed to get around to that and added some visualizers for TFS Work Items.

3 new visualizers were added 2 debugger display visualizers and 1 debugger visualizer. The first is for Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType and just displays the Name of the Work Item Type

image

The next  one added was for Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem and that displays the Status, Id and Title of a work item which is awesome for viewing the results of a query during debugging Smile.

image

Now having the above is very useful but drilling down into fields was a nightmare so I decided to create a nice visualizer for Work Item which would show you the work item on a form as it would be inside Visual Studio. You simple click the little arrow next to the value and then click on View Work Item

image 

and then you will notice that your life has just been changed Smile. Any links you click on from within this window will open in the web access and not in another dialog as you might expect.

image

This work item will show as it is at the current point in the code to the best of the visualizers abilities Smile, in the case above I altered the title just before I opened the visualizer and it kept it's new value as expected Open-mouthed smile.

image

I have tested this on both On Premise TFS and Online with VSO. The On Premise version did give an issue about COM which I'm hoping is just an issue on my machine/instance but will investigate anyway

image

I know this is going to help me speed working with the TFS API and I'm hoping it's useful to others as well Open-mouthed smile.

0 Comments

This post has been ported from https://gbeeming.wordpress.com/2013/10/06/creating-fake-tfs-builds/.

Sometimes you can’t always use the tools you want to use. This is especially true when there is already a bunch of stuff setup around existing tools. Lets say for example that you have an existing application doing your builds, for everything you need around TFS you can do it from within the Web Access although this is all fine and works well, when you want to see how the builds are running you need to go out to another tool to see this information.

Today I’m going to help you setup your builds external to TFS to send build information to TFS so that you can see this info in TFS, the purpose of this is just to give you visibility from TFS and is a very basic introduction that can be extended on a lot with bigger builds.

Before we dig into any code, you will need to create a Build Definition that we will use for our fake builds, if you don’t currently have any controllers setup for you specific collection you will need to create one, after you create the build definition you will be able to de register this build controller as it’s not needed for the manually builds which we will be using.

What we had initially was a build.bat file that has the basics in it (build the build.proj file and log to different files for multiple levels of logging and dumps the latest build in a folder) that our current build server would run that looked like below

@echo off
call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VsDevCmd.bat"
msbuild build.proj /t:DoBuild /v:d /fl1 /fl2 /fl3 /fl4 /flp1:logfile=build.log /flp2:logfile=build.errors.log;errorsonly /flp3:logfile=build.warnings.log;warningsonly /flp4:logfile=build.details.log;detailsonly /p:OutputPath=\\GORDON-PC\Demos\drops\TfsFakeBuilds-Latest

and a build.proj file that basically just let use build multiple projects that could be part of multiple solutions (for the purpose of this post we only have 1)

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="DoBuild" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<BuildProject Include="TfsFakeBuilds.sln"/>
</ItemGroup>

<Target Name="DoBuild">
<MSBuild Projects="@(BuildProject)">
</MSBuild>
</Target>
</Project>

The next steps was to create 2 msbuild tasks that we could use for the starting and stopping of our TFS Build. Create a new class library project and add a reference to the following assemblies

  • Microsoft.Build.Framework
  • Microsoft.Build.Utilities.v4.0
  • Microsoft.TeamFoundation.Build.Client
  • Microsoft.TeamFoundation.Client
  • Microsoft.TeamFoundation.Common
  • Microsoft.VisualStudio.Services.Common

Add 2 classes that each inherit from Microsoft.Build.Utilities.Task, you can call theses classes TfsFakeBuildStart and TfsFakeBuildFinish. All these 2 tasks are going to do is call a call into a 3rd class that is going to do all the work.

TfsFakeBuildStart.cs

namespace TfsBuildTask
{
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class TfsFakeBuildStart : Task
{
[Required]
public string Architecture { get; set; }
[Required]
public string CollectionUri { get; set; }
[Required]
public string Configuration { get; set; }
[Required]
public string DefinitionName { get; set; }
[Required]
public string DetailedLogPath { get; set; }
[Required]
public string DropLocation { get; set; }
[Required]
public string ErrorLogPath { get; set; }
[Required]
public string PathFromBuildRoot { get; set; }
[Required]
public string RegularLogPath { get; set; }
[Required]
public string ServerPath { get; set; }
[Required]
public string TargetNames { get; set; }
[Required]
public string TeamProjectName { get; set; }
[Required]
public string WarningLogPath { get; set; }
public override bool Execute()
{
try
{
base.Log.LogMessage("Tfs Fake Build Starting on '" + this.DefinitionName + "'.");
FakeBuildObject.Instance.Start(this.DetailedLogPath, this.ErrorLogPath, this.WarningLogPath, this.DropLocation, this.RegularLogPath, this.CollectionUri, this.TeamProjectName, this.DefinitionName, this.Configuration, this.PathFromBuildRoot, this.Architecture, this.ServerPath, this.TargetNames);
base.Log.LogMessage("Tfs Fake Build Started on '" + this.DefinitionName + "'.");
return true;
}
catch (Exception ex)
{
base.Log.LogError("Failed to start Fake Build: " + ex);
return false;
}
}
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
}
}

TfsFakeBuildFinish.cs

namespace TfsBuildTask
{
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class TfsFakeBuildFinish : Task
{
public override bool Execute()
{
try
{
base.Log.LogMessage("Tfs Fake Build Stopping.");
FakeBuildObject.Instance.Stop();
base.Log.LogMessage("Tfs Fake Build Stopped.");
return true;
}
catch(Exception ex)
{
base.Log.LogError("Failed to stop Fake Build: " + ex);
return false;
}
}
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
}
}

In our 3rd class we will be getting a reference to a build definition and then kicking off a manual build in our start method and then in our stop method we will collection all the information to from the build and finish off the TFS manual build. Create a 3rd class like below

FakeBuildObject.cs

namespace TfsBuildTask
{
using System;
using System.IO;
using System.Linq;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
public class FakeBuildObject
{
private static FakeBuildObject instance;
private string architecture;
private string collectionUri;
private string configuration;
private string definitionName;
private string detailedLogPath;
private string dropLocation;
private string errorLogPath;
private string pathFromBuildRoot;
private string regularLogPath;
private string serverPath;
private string targetNames;
private string teamProjectName;
private string warningLogPath;
private IBuildDefinition buildDefinition;
private IBuildProjectNode buildProjectNode;
private IBuildServer buildServer;
private TfsTeamProjectCollection collection;
private IBuildDetail detail;
private DateTime startTime;
private DateTime endTime;
public static FakeBuildObject Instance
{
get
{
if (instance == null)
{
instance = new FakeBuildObject();
}
return instance;
}
}
public void Start(string detailedLogPath, string errorLogPath, string warningLogPath, string dropLocation, string regularLogPath, string collectionUri, string teamProjectName, string definitionName, string configuration, string pathFromBuildRoot, string architecture, string serverPath, string targetNames)
{
this.detailedLogPath = detailedLogPath;
this.errorLogPath = errorLogPath;
this.warningLogPath = warningLogPath;
this.dropLocation = dropLocation;
this.regularLogPath = regularLogPath;
this.collectionUri = collectionUri;
this.teamProjectName = teamProjectName;
this.definitionName = definitionName;
this.configuration = configuration;
this.pathFromBuildRoot = pathFromBuildRoot;
this.architecture = architecture;
this.serverPath = serverPath;
this.targetNames = targetNames;
this.startTime = DateTime.Now;
this.collection = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(new Uri(this.collectionUri));
this.buildServer = this.collection.GetService<IBuildServer>();
this.buildDefinition = this.buildServer.GetBuildDefinition(this.teamProjectName, this.definitionName);
this.detail = this.buildDefinition.CreateManualBuild(this.definitionName + " - " + DateTime.Now.ToString("yyyyMMddHHmmss"), this.dropLocation);
this.buildProjectNode = this.detail.Information.AddBuildProjectNode(this.configuration, this.pathFromBuildRoot, this.architecture, this.serverPath, this.startTime, this.targetNames);
}
public void Stop()
{
this.endTime = DateTime.Now;
string[] regularLinesFromLog = ConvertToLines(this.ReadFile(this.regularLogPath));
string[] errorLinesFromLog = ConvertToLines(this.ReadFile(this.errorLogPath));
string[] warningLinesFromLog = ConvertToLines(this.ReadFile(this.warningLogPath));
this.buildProjectNode.CompilationErrors = 0;
this.buildProjectNode.CompilationWarnings = 0;
DateTime nextMessageLogTime = this.startTime;
int timeBetweenMessages = Convert.ToInt32((this.endTime - this.startTime).TotalMilliseconds / regularLinesFromLog.Length);
foreach (string line in regularLinesFromLog)
{
if (!string.IsNullOrEmpty(line))
{
if (errorLinesFromLog.Contains(line))
{
this.buildProjectNode.Node.Children.AddBuildError(line, nextMessageLogTime);
this.buildProjectNode.CompilationErrors++;
}
else if (warningLinesFromLog.Contains(line))
{
this.buildProjectNode.Node.Children.AddBuildWarning(line, nextMessageLogTime);
this.buildProjectNode.CompilationWarnings++;
}
else
{
this.buildProjectNode.Node.Children.AddBuildMessage(line, BuildMessageImportance.Normal, nextMessageLogTime);
}
}
nextMessageLogTime.AddMilliseconds(timeBetweenMessages);
}
this.buildProjectNode.Node.Children.AddExternalLink("Detailed Log File", new Uri(this.detailedLogPath));
this.buildProjectNode.Node.Children.AddExternalLink("Error Log File", new Uri(this.errorLogPath));
this.buildProjectNode.Node.Children.AddExternalLink("Warning Log File", new Uri(this.warningLogPath));
this.buildProjectNode.Save();
this.detail.Information.Save();
this.detail.FinalizeStatus(errorLinesFromLog.Length == 1 && string.IsNullOrEmpty(errorLinesFromLog[0]) ? BuildStatus.Succeeded : BuildStatus.Failed);
}
private static string[] ConvertToLines(string sr)
{
return sr.Replace("\r",string.Empty).Split('\n');
}
private string ReadFile(string logPath)
{
using (FileStream fs = new FileStream(logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs))
{
return sr.ReadToEnd();
}
}
}
}
}

The last parts is just to include this task in your build and then you will have the info in TFS to display on your dashboard and view without having to bounce out to another tool.

For this we created a folder called tfs_config along side our build.proj file and then placed the dll of our fake build tasks in that folder as well as 2 files as below that will be used to kick off and complete the TFS Manual Build

tfs.build.tasks

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Default" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="TfsBuildTask.dll" TaskName="TfsFakeBuildStart" />
<UsingTask AssemblyFile="TfsBuildTask.dll" TaskName="TfsFakeBuildFinish" />
</Project>

tfs.build.targets

<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Default" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="tfs.build.tasks" />
<PropertyGroup>
<BuildRoot>\\GORDON-PC\Demos\TfsFakeBuilds\</BuildRoot>
<DropLocation>\\GORDON-PC\Demos\TfsFakeBuilds\</DropLocation>
</PropertyGroup>
<Target Name="StartTfsBuild">
<TfsFakeBuildStart Architecture="x86" CollectionUri="http://TfsServerUri:8080/tfs" Configuration="Debug" DefinitionName="Demo Definition" DetailedLogPath="$(BuildRoot)build.detailed.log" DropLocation="$(DropLocation)" ErrorLogPath="$(BuildRoot)build.errors.log" PathFromBuildRoot="$(BuildRoot)build.bat" RegularLogPath="$(BuildRoot)build.log" ServerPath="$/TfsFakeBuilds/build.bat" TargetNames="Defaults" TeamProjectName="Demos" WarningLogPath="$(BuildRoot)build.warnings.log">
</TfsFakeBuildStart>
</Target>
<Target Name="EndTfsBuild" DependsOnTargets="StartTfsBuild;DoBuild">
<TfsFakeBuildFinish />
</Target>
</Project>

next we opened the build.proj file and added the line below above the target DoBuild

<import Project="tfs_config\tfs.build.targets" />

and for the last piece we duplicated the build.bat file so that full builds on dev machines would not log to TFS, all that we changed in that build.bat file was the target that we use with the msdbuild command to EndTfsBuild.

Any finally we could stay in TFS for build info once again and we got all the info you see below Smile. Hope this helps somebody else as well.

Download Files

3 Comments

Some Background on why

Another thing that come out of last weeks training was Visual Studio Debuggers. This lead to me finding the coolest visualizer ever called TPL Dataflow Debugger Visualizer which allows you to easily visualize your TPL Dataflow

TPL DataFlow Debugger Visualizer

Because I found this awesome visualizer I decided that everything while debugging could be awesome if there were more of these so I have created a GitHub project called DotNet Pretty where I plan on creating many visualizers to really try light up the debugging experience.

What is DebuggerDisplayAttribute?

In case you don't know DebuggerDisplayAttribute is used when you want to have a "pretty" representation of the properties in your class when seeing it in the debugger.

What MSDN says

Debugger display attributes allow the developer of the type, who specifies and best understands the runtime behavior of that type, to also specify what that type will look like when it is displayed in a debugger.

How do you implement them

You simple place the DebuggerDisplay Attribute on a class like below

2014-07-17_21-10-51

When the break point is hit instead of you seeing the objects ToString() implementation of the method as below

image

which of course you could override to show a nice message if you wanted to, you will get something like below

image

It doesn't seem like such a big deal with 1 object but think of how easy it would be to know stuff about objects when they in a list if they each implemented this attribute. Now obviously to use the attribute like this you need to own the object so you can add the attribute and release it.

DotNet Pretty's first contribution

The first contribution to DotNet Pretty is one that was used in the training which allows you to use the DebuggerDisplay Attribute in a different way.

Code

This time you specify the target in the attribute like below

2014-07-17_21-35-39

As you can see this is just floating in a random .cs file so it doesn't have to be placed anywhere specific in the code.

What it looks like

This time instead of seeing the ToString() method

image

You will see

image

Deploying Visualizers

Now obviously Visual Studio can't do magic to find out where the visualizers are across your whole machine so to get your custom visualizers to you work you simple need to drop the output assemblies in the path C:\Users\[username]\Documents\[Visual Studio Version]\Visualizers which in my case is C:\Users\GordonB\Documents\Visual Studio 2013\Visualizers.

2014-07-17_21-43-33

How is the TDL Dataflow visualizer done?

In short the TPL Dataflow visualizer uses the DebuggerVisualizerAttribute which looks something like below

2014-07-17_21-47-11

I will do a in detail post on DebuggerVisualizer Attribute when I add one to DotNet Pretty. For now though you can browse the source code of the TPL Dataflow Debugger Visualizer on CodePlex.

So what's the plan?

My plan at the moment is to find the .net types that I use most and implement visualizers for them. I'm planning on trying to get some nice ones in for TFS objects like Work Items. I'm hoping that others will use this library of visualizers and fork the code and help grow it.

0 Comments

I was looking for a PowerShell script that would remove all media tags (post coming) from a folder of music that I have and along the way I came across a blog post called PowerShell - Automatically organizing your mp3-collection. Although this wasn't exactly what I was looking for right now I gave it a try and then thought to share it after I made a couple of changes to make it work on my machine Smile with tongue out and categorize a bit more . You can grab the script off GitHub Gist organise-music.ps1.

Basically the script will take any structure of music and organize it into the structure below

%root%

%root% / %Album Artist%

%root%/ %Album Artist% / %Album Year%

%root%/ %Album Artist% / %Album Year%/ %Album Name%

You also don't need to have all your files in a single folder for this to work, it will recursively find matching file extensions ("*.m4a", "*.m4b", "*.mp3", "*.mp4", "*.wma", "*.flc") and then work out where each file should be placed, creating directories where needed and cleaning up empty directories and extra files when finished Smile

0 Comments

Up until about an hour ago I thought I knew how optional parameters worked but didn't actually. I thought (probably without giving it too much thought) that optional parameters would compile to something that would reflect having multiple overloads as when they came out I ripped out 100s of overloads across multiple libraries and replaces them with optional parameters because it replace code like

2014-07-07_21-38-15

with code like

2014-07-07_21-38-58

It's obviously that the second snippet is easier to read but do you know what's actually happening?

Andrew Clymer mentioned mentioned in training today that optional parameters are compiled into the calling code and not the method where they are used and thought this is something that others might not have known as well Smile

See below for a quick example of this. Create a new Console Application and Class Library as below

MyCalc_-_Microsoft_Visual_Studio_(Administrator)_2014-07-07_21-13-21

If you run this you will see in a console window the message Hello with some underscores.

filecusersgordonbdocumentsvisual_studio_2_2014-07-07_21-13-50

This is what we expect and there is nothing unusual there but what happens if we change the code for the Utils class like below

2014-07-07_21-09-22

Now build only the class library, copy the output assembly (CalcLibrary.dll) to the console windows output directory and then run the console window from windows explorer

CUsersGordonBDocumentsvisual_studio_2013Proj_2014-07-07_21-14-55

You'll notice that again we see what we expect. Now change your Utils class to have underscores as a default in a name parameter and place that in the return string like this

2014-07-07_21-17-36

Again if you run this you will get what you expect

filecusersgordonbdocumentsvisual_studio_2_2014-07-07_21-13-50

Now lets make that second change we did and replace the underscores with a underscore dash repeated 5 times like so

2014-07-07_21-19-21

Build just the class library, copy the assembly over and run the console app in windows explorer and you'll see that the nothing changed in the console window

filecusersgordonbdocumentsvisual_studio_2_2014-07-07_21-13-50

I recently watch Bart De Smet's Pluralsight videos (C# Language Internals - Part 1 | C# Language Internals - Part 2) and one thing I realized is that I thought I knew stuff and to almost truly know you should check the IL or view the IL in a nice tool like IL Spy. So I opened the code in IL Spy and took a look at the class library to make sure that it update and as expected it did

2014-07-07_21-30-54

and to then opened the program class in the class library and found that the code that the console app was compiled against had been added to the call to Utils.GetMessage

2014-07-07_21-32-03

In this example there is nothing that could go wrong and you probably would worry about the behavior of the optional parameter but there will be cases in more important code where this could be a problem maybe for performance or even security I'm guessing.