Uploading custom Build Task to TFS 2015

In a previous article I wrote on how to write a custom Task For Visual Studio Team Services, but a usual question is: can I use the same technique to write a task to TFS 2015 on-premise?

The answer is yes, and it is really simple, thanks to this fantastic article by Jesse, that explain how to use Fiddler to being able to authenticate to on-premise TFS without the hassle of enabling basic authentication. Thanks to that article and Fiddler, you can simply login from tfx-cli to your TFS 2015 without any problem.

image

Figure 1: Login against your local TFS Service

Once I’m logged in, I can simply use the very same command used for VSTS to upload the directory where I defined the Build Task on my local TFS on-premise Server.

image

Figure 2: Task was uploaded to the server

As you can see the task was uploaded to the server, exactly in the very same way I uploaded to my VSTS account. The task is now available to be used in my TFS.

image

Figure 3: Custom task in action in a TFS 2015 on-premise build

Thanks to the extensibility with PowerShell I did not need to care about versions of VSTS or TFS, because the script does not have reference on any dll or package and the same task can be used both in VSTS or in TFS 2015 without changing a single line.

Thanks to the new Buidl System, extending a build for both VSTS and TFS is now a simple and easy task.

Gian Maria.

“Unsupported filter” using ContainsAny in Mongo 2.x driver

Porting code from Legacy driver to new driver syntax is quite annoying for .NET MongoDb driver. In the new Drivers almost everything is changed, and unless you want to still use old legacy syntax creating a mess of new and old syntax, you should convert all the code to the new syntax.

One of the annoying problem is ContainsAny in LINQ compatibility driver. In old drivers, if you have an object that contains an array of strings, and you want to filter for objects that have at least one of the value contained in a list of allowed values you had to resort to this syntax.

  return Containers.AllUnsorted
                .Any(c => c.PathId.Contains(containerIdString) &&
                c.Aces.ContainsAny(aceList));

In this situation Aces properties is an HashSet<String> and aceList is a simple String[], the last part of the query uses the ContainsAny extension method from Legacy MongoDb driver. That extension was needed in the past because the old driver has no full support for LINQ Any syntax.

The problem that arise with the new driver is, after migrating code, the above code still compiles because it references the Legacy Drivers, but it throws an “Unsupported Filter” during execution. The solution is really simple, the new driver now support the whole LINQ Any syntax, so you should write:

return Containers.AllUnsorted
	 .Any(c =&gt; c.PathId.Contains(containerIdString) &amp;&amp;
			c.Aces.Any(a =&gt; aceList.Contains(a)));

As you can see, you can now write the Query with standard LINQ syntax without the need to resort to ContainsAny.

While I really appreciate that in the new Drivers LINQ support is improved, it is quite annoying that the old code still compiles but it throws at run-time.

Gian Maria.

Error in WSUS after Windows Update KB3148812

I have a test lab with a Windows Server 2012 R2 domain controller, and one of the feature I like the most is WSUS, that allows me to spin of an update new Virtual Machines without the need to wait for all Windows Update to be downloaded from the Internet.

Yesterday I noticed that suddently the WSUS service stopped working, a couple of Test VS gaves me error trying to connect to the update service, and in the WSUS Server the service was indeed stopped.

After a look in Event Viewer I was able to track down that the reason is the database is not operational. I fired Sql Server Management studio and connected to the internal database with the address

\\.\pipe\Microsoft##WID\tsql\query

And noticed that the WSUS Database was in Recovering state, the bad stuff is that I restored a previous backup but the problem did not go away. After a quick search I found that someone blamed the update KB3148812 to be the cause of this problem. After uninstalling that update and rebooting the server the issue went away.

Gian Maria.

Execute powershell code during a build in TFS 2015 Update 2

In TFS 2015 build system, running a PowerShell script stored in source code is simple because there is a dedicated action to accomplish this task, but if you want to run a PowerShell script that is not in source control, you have no option out of the Box.

Thanks to Update 2, you have now the ability to use the extensions you can find on Visual Studio Marketplace directly in your on-premise TFS installation. In the Upper Right corner of Web Ui you can find an icon that link to

This image shows the link in TFS UI to open the control panel to manage extension.

Figure 1: Link to manage extensions

This opens an administration page that allows you to manage extensions of your TFS installation. The first time you open this page, you will see no extension installed, so it is time to browse Visual Studio MarketPlace to find an extension. Actually I want to run inline PowerShell and I can immediately find that there is an extension that can solve my need.

As you can see from Figure 2, this extension have a couple of button, the first one, Install, is used to install this extension on one of your VSTS account, but if the extension is compatible with on-premise TFS you have also the option to Download the extension.

This image shows that extensions can be downloaded to local pc to be installed to TFS

Figure 2: Once you find an extension compatible with on-premise TFS you can download it.

The extension is a simple .vsix file, and from extension administration page you can upload to your TFS.

This image shows how you can upload downloaded extension to your TFS instance.

Figure 3: Once downloaded you can upload the extension to your TFS.

The process of uploading is really simple:

This image shows how you can choose the file to upload

Figure 4: Browse for downloaded extension

Tfs shows info for file to upload

Figure 5: Upload UI shows all the detail of the file

Now you can press Upload button, and the extension is uploaded to TFS and ready to be activated.

TFS shows the list of uploaded and installed extensions

Figure 6: Uploaded extension is now listed in the list of available extension

For each extension TFS ask you to install in specific project collection

  1. Figure 7: Install the extension on one or more collection

The concept behind “install then activate” is that, once uploaded the extension is available for the TFS Server instance, but then you should install in each Project Collection where you want to use that extension.

TFS asks you to choose the collection where you want to use the extension.

Figure 8: Choose the collection you want to install the extension into

Once you choose the Project Collection you need to confirm the operation

Figure 9: Confirm the selection

Once the Extension is installed in one Project Collection it can be used. In this example the extension contains a new build Task that allows you to execute an arbitrary PowerShell script during the build. To verify that everything is ok you can create a build and verify that the new Task is available and ready to use.

the extension is used in a build, where I was able to add the new task that executes arbitrary powershell code

Figure 10: The extension is ready and can be used

Thanks to TFS 2015 Update 2 you can Download and install extension for VSTS Marketplace to your on-premise TFS installation with few clicks.

Gian Maria.

Change how MongoDb C# Driver serialize Guid in new driver version

We have code based on the old legacy mongo driver that uses MemberSerializationOptionsConvention class to serialize all Guid as plain strings. This option is really useful because saving Guid in Bson format is often source of confusion (CSUUID, … ) for .NET users. On the contrary, having saving Guid as plain string solves some pain and made your Bson really more readable.

Here is the old code that runs with the old legacy driver.

var conventions = new ConventionPack
    {
        new MemberSerializationOptionsConvention(
            typeof (Guid),
            new RepresentationSerializationOptions(BsonType.String)
            )
    };
ConventionRegistry.Register("guidstring", conventions, t =&gt; true);

In the new Driver, these classes are removed, so we need to find a different way to serialize Guid as strings. Here is a possible solution.

public static void RegisterMongoConversions(params String[] protectedAssemblies)
{
    var guidConversion = new ConventionPack();
    guidConversion.Add(new GuidAsStringRepresentationConvention(protectedAssemblies.ToList()));
    ConventionRegistry.Register("guidstring", guidConversion, t =&gt; true);
}

This class uses a custom class called GuidAsStringRepresentationConvention, used to instruct Mongo serializer to serialize Guid as plain String. This class accepts a list of string that represents a list of protected assemblies. The reason behind it is that we do not want this convention to be applied to every type loaded into memory, and we want to avoid to change how Guid are serialized for types stored in assembly we do not have control to.

Here is the code of the class.

/// <summary>
/// A convention that allows you to set the serialization representation of guid to a simple string
/// </summary>
public class GuidAsStringRepresentationConvention : ConventionBase, IMemberMapConvention
{
    private List protectedAssemblies;

    // constructors
    /// <summary>
    /// Initializes a new instance of the  class.
    /// </summary>  
    public GuidAsStringRepresentationConvention(List protectedAssemblies)
    {
        this.protectedAssemblies = protectedAssemblies;
    }

    /// <summary>
    /// Applies a modification to the member map.
    /// </summary>
    /// The member map.
    public void Apply(BsonMemberMap memberMap)
    {
        var memberTypeInfo = memberMap.MemberType.GetTypeInfo();
        if (memberTypeInfo == typeof(Guid))
        {
            var declaringTypeAssembly = memberMap.ClassMap.ClassType.Assembly;
            var asmName = declaringTypeAssembly.GetName().Name;
            if (protectedAssemblies.Any(a =&gt; a.Equals(asmName, StringComparison.OrdinalIgnoreCase)))
            {
                return;
            }

            var serializer = memberMap.GetSerializer();
            var representationConfigurableSerializer = serializer as IRepresentationConfigurable;
            if (representationConfigurableSerializer != null)
            {
                BsonType _representation = BsonType.String;
                var reconfiguredSerializer = representationConfigurableSerializer.WithRepresentation(_representation);
                memberMap.SetSerializer(reconfiguredSerializer);
            }
        }
    }
}

The code is really simple, it scan each memberMap to verify if the memberMap is of type GUID, but if the type is declared in a protected assembly, simply return, because we want to leave serialization as-is. If the type is not in a protected assembly and is of type GUID, we simply change the representation of the serialization to be a BsonType.String.

The result is that now, each property of type GUID, will be serialized as plain string into MongoDb.

Gian Maria.