Welcome to MSDN Blogs Sign in | Join | Help

This particular post has nothing to do with technology. I'm an avid college football fan and have always cheered for my alma mater, the Georgia Tech Yellow Jackets. Tonight, for the first time since I have been actively following them, we beat Clemson for the 2nd time this season (39-34) and earned a berth in the BCS Orange Bowl!! 

Not to downplay Clemson, however, as they put up a really good fight both times and it came down to who had the ball last. The first game was equally amazing to watch and ended with a field goal to beat the Tigers 30-27. This game we won on a touchdown by Jonathan Dwyer! He's the man! Speaking of the man, I think C. J. Spiller deserves to go to New York and be in the running for the Heisman. Amazing athlete who I'm sure will do well in the NFL. 

 Ok, I'm done gloating ... hopefully this will earn the ACC more respect. Now on to the Orange Bowl! 

 One more thing .. of course I can't leave Paul Johnson hanging high and dry in recognition. He has done an amazing job as head coach of GT, taking a team that went 7-5 to 9-4 and now 11-2 with a BCS berth, first appearance in the Orange Bowl since the early 60's! 

Note: I apologize in advance if this first post seems unorganized. There is a lot of material to cover and I couldn’t find a really good way to order the topics. Hang in there and hopefully it will start making sense.

In order to help everyone get up to speed with build customization, I wanted to take a few posts to share our collective knowledge on the Workflow framework in .NET 4.0. In this first post I plan to tackle the different base classes that may be used when designing your own custom activities, along with typical usage scenarios. Other topics, such as extensions, tracking, and more advanced customization will be covered later. I will also be keeping an eye on comments to help point me in the right direction. So without further adieu, on with the code!

The Core Activity Classes

There are 4 base classes that you should become very familiar with in the framework. Each class has a corresponding generic version, which informs the workflow runtime that it returns a result of the specified type (more on this later). I have listed out the 4 base classes below, along with general information on when you might choose to utilize each one.

System.Activities.Activity

This class serves as the base class for all of the other activities in included in the framework, as well as the class you will be extending for activities composed in code. Most of our activities in TFS 2010 are derived from this activity or Activity<T>, which is simply an activity that returns a result of type T.

System.Activities.CodeActivity

CodeActivity, along with CodeActivity<T>, provide a simple mechanism for providing the implementation of an activity as .NET code. Classes that utilize this as a base class should not perform long running operations as there is no way to cancel the execution of this activity.

System.Activities.AsyncCodeActivity

AsyncCodeActivity and AsyncCodeActivity<T> are very similar to the CodeActivity classes but are intended to perform long running operations. If you need perform I/O or would like to provide the ability to cancel the execution of your activity, you should derive from the asynchronous variety. Most of the code activities in TFS 2010 utilize the AsyncCodeActivity classes so a build may be stopped cleanly during an operation.

System.Activities.NativeActivity

This particular activity, in conjunction with NativeActivity<T>, are not intended for heavy use. In fact, the workflow framework even makes somewhat limited use of this activity as a base class. Typically you should only use this activity if you would like to define your own scheduling behavior or need to interact with bookmarks and external services.

The Core Data Classes

System.Activities.Variable

A variable in represents the same concept as it does in code when you declare an integer or string. The purpose of variables is to act as a data storage mechanism within a particular visibility scope. Much like you cannot declare two variables of the name ‘foo’ in C# without a compiler error, you cannot successfully run a workflow that attempts to define two variables in the same scope with the same name.

System.Activities.Argument

An argument represents the flow of data, and does not actually have the ability to store anything (the actual storage mechanism for an argument is typically a variable or top-level input to the workflow). There are 3 important sub-classes of argument that you will need to become familiar with:

    1. System.Activities.InArgument<T>
      • The value is meant to flow into the method. Much like a pointer in C, the pointer itself cannot be changed but if a complex object is passed as an argument then fields of that object may be changed.
    2. System.Activities.OutArgument<T>
      • The value is meant to flow out of the method. This argument type is synonymous to the ‘out’ keyword in C#.
    3. System.Activities.InOutArgument<T>
      • The value is meant to flow in and possibly be modified as an output. This argument type is synonymous to the ‘ref’ keyword in C#.

Designing Your First Activity

For simplicity, we’re going to start with the age-old example of “Hello, World!” but accomplish the task with workflow. For this introduction we just need to load up visual studio and create a new C# ‘Console Application’ project. Normally you would select a ‘Workflow Console Application’ for the project type, but I’d rather go through the steps manually to hopefully aid in your understanding of the framework. Once you have the project created, add a reference to System.Activities using the ‘.NET’ tab. Now we need to create our first activity for interacting with the console for output. Create a new C# class called ‘WriteLine’ and paste the following code into the file.

using System;
using System.Activities;
using System.ComponentModel;
using System.IO;

namespace HelloWorkflow
{
    public sealed class WriteLine : CodeActivity
    {
        [Browsable(true)]
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<TextWriter> TextWriter
        {
            get;
            set;
        }

        [Browsable(true)]
        [RequiredArgument]
        [DefaultValue(null)]
        public InArgument<String> Text
        {
            get;
            set;
        }

        protected override void Execute(CodeActivityContext context)
        {
            String text = Text.Get(context);
            TextWriter textWriter = TextWriter.Get(context);

            if (textWriter == null)
            {
                throw new ArgumentException("The provided text writer is invalid");
            }

            textWriter.WriteLine(text);
        }
    }
}

What we have done is created an activity, called WriteLine, that takes a TextWriter and some Text as arguments and writes the text to the writer. I know this may seem overly simple but it works pretty well for explaining the general concepts of data flow. If you think about this a little bit, you may notice that this mimics code quite a bit! For instance, we could write this using C# instead in the following way:

public static void WriteLine(TextWriter textWriter, String text)
{
    if (textWriter == null)
    {
        throw new ArgumentNullException("textWriter");
    }

    textWriter.WriteLine(text);
}

Taking a moment to analyze the correlation between these two approaches, we notice a couple of similarities. First, the CodeActivity.Execute(CodeActivityContext) method is nothing more than the private implementation of the C# method. Second, the input arguments are nothing more than properties on the activity of type System.Activities.InArgument<T>. Although there isn’t always a direct 1-to-1 mapping as illustrated by this simple example, you should approach your development of activities in much the same way. Activities are meant to be modular, reusable pieces of code that may be composed into more complex functionality, exactly like functions/methods in written code.

Now that we have our activity, let’s see how we can use it. Go back to your Program.cs file, or where your Main method exists, and make it look similar to the following.

static void Main(String[] args)
{
    WorkflowInvoker invoker = new WorkflowInvoker(new WriteLine());
    
    Dictionary<String, Object> inputs = new Dictionary<String, Object>();
    inputs.Add("TextWriter", Console.Out);
    inputs.Add("Text", "Hello, Workflow!");

    invoker.Invoke(inputs);
}

If you now run this program you should see the text “Hello, Workflow!” output to the standard output stream of the console window! Ok, I know that wasn’t all that exciting, but you always need to start somewhere to understand the basic concepts before we can move on to the better stuff. Let’s take a second to go through the main method and explain what it does. First we allocate a WorkflowInvoker object, which is very useful for running simple workflows (most hosting environments, including TFS Build 2010, will most likely utilize the WorkflowApplication class instead), specifying the activity definition we would like to use. Next, we set up some inputs which map to the argument names on our activity, which define the execution environment for the runtime. Last, we invoke the workflow which will execute our activity with the provided input environment.

Activity Definition vs. Activity Instance

One particularly interesting area I would like to point out is the separation of the activity definition from the activity instance. When you load a workflow for execution and provide an instance of a C# class, in this case the WriteLine activity, you are actually supplying what is known as the activity definition. The definition defines the inputs, outputs, control flow, and everything else you would expect that remains static, much like a class definition in code. Each time the activity is executed it reuses the same C# class instance of the definition – once again, each time the activity is executed it reuses the same C# class instance of the definition. Why did I say this twice? I want to make it extremely clear why it’s important to differentiate between properties that belong to the definition and properties that belong to the individual execution instance. Instance-level storage is accomplished with the mechanisms provided by the workflow runtime (Variable<T>, InArgument<T>, InOutArgument<T>, etc), while definition-level storage is accomplished via standard class properties. If you take a look back at our WriteLine activity, the execute method obtains the values for the inputs using the provided CodeActivityContext. Along with providing mechanisms to interact with the tracking participant and the ability to get extensions, the activity context also provides the storage for the current instance environment visible to the activity. So, if you want to retrieve the value for one of the inputs to the activity you must have access to the activity context to retrieve the value. If you would like to experiment with this more, try adding an integer as a regular field or property to the class and increment it each time the workflow is run.

Wrapping It Up

Reading this post should introduce you to basic concepts of workflow, how data flows in a workflow, and how to get started writing your own activity. I plan on continuing this series with regular updates on progressively more advanced and cumulative topics, so if you’re looking to make use of this framework in your own programs or are simply attempting to extend your build process keep an eye out! My current plan is to cover AsyncCodeActivity and composition through Activity in the following posts, but feel free to steer me in a different direction.

My previous post introduced the basics of how the tracking participant may be used to create custom build information through the activity context. However, as pointed out at the end of the article, the best practice for creating custom activities in Workflow 4.0 is to do so through composition, meaning that you should derive from Activity or Activity<T>. In compliance with this model we supply activities that allow you to perform the same custom information creation through activity composition rather than from code. The intention of this article is to lead you through how you might make this type of functionality available, as well as introduce you to composing custom build information into your own build processes.

Tracking Custom Build Information using Activity Composition

As you may recall, we previously introduced a custom tracking record, BuildInformationRecord<T>, for sending instructions to the tracking participant to create custom information. In order to keep things generic, we want to allow users of our activity to have the same flexibility with our new activity that they have when interacting directly with the activity context. Since our activity will need to perform the interaction with the activity context on behalf of the workflow writer, we need to create a custom CodeActivity, which may be defined similarly to the class below.

public sealed class WriteBuildInformation<T> : CodeActivity
{
    public WriteBuildInformation()
    {
    }

    [RequiredArgument]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public InArgument<T> Value { get; set; }

    protected override void Execute(CodeActivityContext context)
    {
        context.Track(new BuildInformationRecord<T>() { Value = Value.Get(context) });
    }
}

As you can see, our activity is simply a thin wrapper around the code we previously explored. However, there are a couple of advantages I’d like to point out when using this approach over the strict code approach.

  1. Since execution is very short lived, the tracking participant gets a chance to receive the record and take action once the method completes.
  2. Gives consumers of the activity a chance to incorporate custom information by building activities through composition in either XAML or code.

This activity should only be considered a utility activity that serves as the base for special purpose activities. The reason I propose limited use of this activity directly is because the XAML for it can get relatively tedious fairly quickly. For example, if we step back and model our example of writing a build message in XAML using this activity, we will end up with something very similar to the XAML below.

<my:WriteBuildInformation x:TypeArguments="my:BuildMessage" xmlns:my="http://mycustomschema.company.com" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <my:WriteBuildInformation.Value>[New BuildMessage() With {.Importance = BuildMessageImportance.Normal, .Message = "This is a custom message" }]</my:WriteBuildInformation.Value>
</my:WriteBuildInformation>

Judging by the XAML, the verbosity could be greatly reduced if we didn’t need to specify the x:TypeArguments or construct the Value parameter with a visual basic expression; this is where activity composition comes in handy. We can provide any number of specialized wrapper activities that simplify the usage of this activity for particular information types. Once again, for the sake of consistency, we will revisit the BuildMessage for an example. What we would ultimately like to provide for users of our activity library is a single, specialized activity for writing build messages. Users should be able to set the Importance and the Message as arguments to the activity, just as they would when constructing the XAML above. Given this small set of requirements, we might end up with an activity that looks similar to the following:

public sealed class WriteBuildMessage : Activity
{
    public WriteBuildMessage()
    {
        base.Implementation = () => CreateBody();
    }

    [RequiredArgument]
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public InArgument<String> Message { get; set; }

    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    public InArgument<BuildMessageImportance> Importance { get; set; }
 
    Activity CreateBody()
    {
        return new WriteBuildInformation<BuildMessage>
        {
            Value = new InArgument<BuildMessage>(ctx => new BuildMessage() { Message = Message.Get(ctx), Importance = Importance.Get(ctx) }),
        };
    }
}

There a few things I would like to point out about the code above, just in case not everyone is familiar with lambda syntax and activity composition in general.

  • The single line in the constructor is one of the most important lines. The way composed activities work is you provide a Func<Activity>, exposed as the property Implementation, that returns the private implementation of your activity (synonymous to the implementation of a method or function in code). If you forget to set this, the activity will not produce an error and will produce no output.
  • The InArgument<T> type has a few constructors. In this particular scenario we want to construct an object at runtime based on the incoming arguments. The syntax we must use in this case is the constructor that takes a Func<ActivityContext, T> so we can utilize the activity context handed to us by the runtime to extract the values of the arguments for our BuildMessage. Since Workflow 4.0 is very new and still in beta, I plan on covering general topics regarding activity design and best practices we learned through our use of the framework in TFS 2010 in coming posts. If you don’t quite understand what I’m talking about just be patient, for soon you will be a Workflow Master (probably not, but you’ll understand it better than you do now)!

Now that we have our specialized activity for writing build messages, we can express the equivalent XAML we saw earlier with the following:

<my:WriteBuildMessage xmlns:my="http://mycustomschema.company.com" Importance="[BuildMessageImportance.Normal]" Message="This is a custom message" />

As you can see the syntax is much more concise than the previous example. It also takes the burden off the user of your activity to create an instance of the appropriate type and use initializer-syntax to set the property values. Although this simplifies the XAML you should probably use good judgment when writing these specialized activities to keep your library to a reasonable size. You will find the following activities exposed in the library that shipped with TFS 2010 Beta 2:

  • WriteBuildError
  • WriteBuildMessage
  • WriteBuildWarning

Using these activities you can, you guessed it, write messages, warnings, and errors. These particular information types are special in that they are well understood by our build report, however, and will show up with warning or error icons next to the message automatically when viewing the build log. Jason Prickett should be making a follow-up post soon describing how you can extend the build report to display your custom information. I’ll be sure to notify everyone once this happens so we can bring all of the customizations together and show off the entire scenario.

After writing these past 2 articles I have come to the realization that it would probably be very helpful to go over Workflow 4.0 in general so everyone can get up to speed on these topics. My next few posts will focus on development of activities and design practices with the 4.0 APIs, so keep reading for more in-depth explanations of the underlying framework.

Before reading this post, you should take a moment to read Aaron Hallberg’s post on build process customization in TFS 2010 Beta 1. Now that you fully understand custom activities in Windows Workflow 4.0 (just kidding), I would like to focus on how we have made it much easier to customize the information created and stored along with the build. There are currently multiple mechanisms for getting additional information into the build process, made possible by some activities, extension methods, and integration with the tracking participant (synonymous to the MSBuild logger for those familiar with that framework). This post will focus on using the activity context to create custom information from code.

Build Information (VERY quick primer)

Build information is simply a hierarchical data store that is extensible. Each node in the hierarchy has a Type and a set of Fields (name/value pairs as strings). All information created since TFS 2008, including task logging, build steps, and compilation details, are stored using this mechanism. Since its introduction into the API this hierarchy has always been extensible, but with the introduction of Windows Workflow 4.0 we have tried to make it easier.

Tracking Custom Build Information using the ActivityContext

Tracking in Windows Workflow is the mechanism that a workflow may use to communicate information to the tracking participant(s). For each workflow run in TFS Build 2010 we hook in a custom implementation of a tracking participant, the BuildTrackingParticipant. Along with tracking general activity in the workflow, such as activity execution, we also provide a way to inject custom build information during the workflow in a safe manner by interaction with the tracking participant. In order to facilitate this we have provided a custom tracking record defined as follows (certain classes in the hierarchy have been omitted):

public sealed class BuildInformationRecord<T>
{
    public T Value { get; set; }
}

This specialized record should be used for writing all custom build information during a workflow. The tracking participant creates information nodes from these records by analyzing the enclosed type via designer-based reflection and converts the object into an IDictionary<String, String> for storage. If complete customization of the data conversion is desired you may provide a custom TypeConverter for T which can take the object and convert it to an IDictionary<String, String> (in most instances this level of customization will not be necessary, but the hook exists if the need arises). The information node type name is extracted from the name of the managed type (e.g. typeof(T).Name), so there is no need to provide this explicitly. This mechanism of writing build information is how all built-in information nodes are written to the build log, excluding the activity tracking nodes themselves.

Illustration by example is typically the easiest way to understand, so next I will describe how we write information nodes of type BuildMessage to the log using this tracking record. The first thing we will need to do is define a class that describes the data we will be storing for a message: Message and Importance. You will find the definition of this class below.

public sealed class BuildMessage
{
    public String Message { get; set; }
    public BuildMessageImportance Importance { get; set; }
}

As you can see, all we had to do is define a class with a type name and set of properties matching the information type we would like to create. Now, in order to inject a BuildMessage information node into the build log all we need to do is call the appropriate CodeActivityContext.Track(CustomTrackingRecord) or NativeActivityContext.Track(CustomTrackingRecord) method from code, depending on which activity type your custom activity derives. For instance, if you would like to track a message from a code activity you could do the following:

public sealed class WriteMessage : CodeActivity
{
    protected override void Execute(CodeActivityContext context)
    {
        context.Track(new BuildInformationRecord<BuildMessage>()
        {
            Value = new BuildMessage()
            {
                Importance = BuildMessageImportance.Normal,
                Message = "This is a custom information node",
            },
        });
    }
}

When the tracking participant receives this record, it will essentially perform the following steps:

  1. Find the closest activity tracking node in scope (more details on how this is managed in a later post), using the root node of the current IBuildDetail if none is found.
  2. Create a new IBuildInformationNode as a child of the node from (1) with a type name of “BuildMessage” (derived from typeof(T).Name as described earlier).
  3. Retrieve the TypeDescriptor for T. If the type converter can convert directly to an IDictionary<String, String>, it simply allows the converter to do the heavy lifting. If the converter cannot perform this conversion, an information field will be created from each PropertyDescriptor retrieved from a call to TypeDescriptor.GetProperties(Object), converting each value to a String using the converter of the PropertyDescriptor. In most cases this will simply invoke the ToString() method of the type. However, we provide explicit converter implementations which cannot be overridden for DateTime and Byte[] properties to ensure appropriate and consistent conversions of these data types.

While this mechanism works fine there is a pretty important caveat with tracking custom information from within a CodeActivity that you should be aware of. With the exception of the AsyncCodeActivity, every operation in a single workflow instance is executed on the same thread, including tracking. So, if you have a long-running execute method where you are attempting to track progress by logging multiple messages you will be surprised when nothing shows up until the activity completes. This behavior is due to the single thread; since your execute method is using the thread the tracking records will not be flushed to the tracking participant until your method returns. As a general rule of thumb, we have tried to keep away from creating long-running and complex code activities in favor of producing much smaller, more modular behavior that we then build into an activity through composition. Most of our activities in the shipping library derive from Activity or Activity<T>, with workflow driving the majority of the logical constructs. Due to this design we needed a way to create build information through composition, which drove us to create some custom activities for exactly this purpose. I plan to explore these activities and how to use them in a future post. Stay tuned …

In TFS 2008 we made quite a few improvements to build, such as scheduled builds and continuous integration. The continuous integration feature is nice for detecting failures in the build as soon as possible to ensure minimal downtime and reduce the chance of generating a bad nightly build. However, it’s not a perfect solution since even when the build fails it is possible for others to submit unrelated changes and keep the build broken. As a first measure to aid this scenario, Buck Hodges wrote a check-in policy that would determine which, if any, build definitions would be affected by the changes being checked in – if the last build for the definition failed, the policy would not allow the user to check-in without overriding it. But there are quite a few issues with this approach, the main one being that it requires the users to install the policy and only override it when honestly submitting fixes for the build break. At this point you may be asking yourself “what if there were a way to stop people from submitting broken source code into the repository on the server?” This is exactly what gated check-in offers, and I intend to explore the features around gated check-in in the following paragraphs.

Setting Up Gated Check-in

Gated check-in is merely an extension of the check-in trigger for a build definition, so it should be easy to setup for those familiar continuous integration. In TFS 2008, we provided ‘Manual’, ‘Continuous Integration’, ‘Rolling builds’, and ‘Schedule’ (along with the option to force builds on that schedule even if no changes occurred since the last build). When viewing the build definition properties in TFS 2010, you will see the same options (with the addition of ‘Gated Check-in’):

Definition_Triggers 

When this trigger is selected for a build definition, any check-in that is made to a file that is mapped in the associated workspace will trigger a verification build. If the build succeeds, the changes will be submitted to the repository. If the build fails, the changes are not allowed to be submitted and must be fixed and resubmitted.

Submitting a Gated Check-in

When a user attempts to submit changes to a version control path that is mapped by a build definition with the ‘Gated Check-in’ trigger, they will be presented with the following dialog:

Gated_Confirmation

The first thing to point out here is that the check-in been automatically converted to a shelveset. In this particular scenario, the shelveset has been named ‘Gated_2009-06-29_02.49.00.9801’. The check-in notes, associated work items, etc., are all replicated into the shelveset and will be included in the changeset upon a successful build. The second piece of the dialog to pay attention to is the build definition selection. If there were only a single build definition that was considered affected by the changes, the combo box would be disabled and you would not be provided any options. If, however, there is more than one build definition that maps the changes being submitted, the user you will be presented with the list of build definitions as shown in the following image:

Gated_Confirmation_Dropdown

It’s important to note here that the actual definition selected to perform the check-in does not matter here, as both have been determined to map at least some subset of changes included in the check-in. However, it’s possible that ‘Gated Definition 1’ only maps a single file in the check-in, while ‘Gated Definition 2’ could map all the files or a larger subset. Currently we do not provide any indication as to what percentage of the changes are mapped by any given build definition. Once you have selected a build definition, you have the ability to specify some extra options. For example, it is entirely possible that you are a build administrator and wish to submit a fix directly rather than waiting for the verification build. However, this option will only be enabled for a selected build definition if you have the appropriate permission, ‘Override Check-in Validation by Build’. If you have the appropriate permission, we have provided the ability to bypass build verification as shown in the following image (although it’s disabled in this particular shot since no build definition is currently selected):

Gated_Confirmation_Options

The other option available in the ‘Show Options’ section of the dialog is ‘Preserve my pending changes locally’. This allows the user to decide whether or not the changes being submitted should be left in the workspace or undone once the ‘Build Changes’ button is clicked (if the changes are left in the workspace, there is an entry point that we will explore later in the article to reconcile the submitted changes with those in your workspace). Once you are satisfied with the options on this dialog and ‘Build Changes’ is clicked, you will be redirected to the build explorer where the build just queued will be highlighted for easy discovery as shown here:

Gated_BuildExplorer

Now I know there is only one build in this particular queue so it wouldn’t be very difficult to locate the build, but imagine there is a team of 50-60 people all submitting changes to the same build definition, or even multiple definitions all sharing the same build controller. :-) Anyway, moving along to the successful gated check-in, you can see from the following build report that the submitted changeset is associated with the gated build, just like changesets would be associated with a non-gated build by comparing the labels and analyzing the differences.

Gated_BuildDetail_Successful

Now that the build has completed successfully, you may have some further actions to perform if you chose not to preserve pending changes in your workspace. The entry point for this functionality is in build explorer, which is available via the context menu of the build detail entry in the ‘Completed Builds’ tab or the queued build entry in the ‘Queued Builds’ tab. If you only have a single workspace on the current machine then a workspace will be automatically selected for you. If, however, you have more than one workspace on the machine, you will be prompted to select the workspace which you would like to reconcile changes with. We currently do not support storing the source workspace from which the shelveset originated, so we cannot automatically determine the workspace used to create the shelveset. However, this feature is on a backlog and we are looking at including this functionality in a future version. The second entry point is from the gated check-in notification window in the build notification system tray application, which is included with the product in TFS 2010.

This has been a very targeted and simple walk-through of the new gated check-in functionality available in TFS 2010. I will be covering more advanced scenarios surrounding permissions, integration with the build notification system tray application, and more in future postings. Stay tuned!

Developing a program that does not require administrative privileges requires quite a bit of work up-front during the install. You need to make sure you do things like write registry keys to HKLM, create file system entries, and most importantly ensure that your ACLs (Access Control List) are correct to ensure smooth operation of your program while running under that non-admin account. However, sometimes this last step fails with a rather cryptic error, mentioning something along the lines of "The Access Control List is not canonical". After searching the web for solutions to this error, I couldn't find a definitive answer. One thing I did notice is if you open the 'security' dialog of the object that exhibits this issue, the Windows UI will actually recognize that there is an issue and ask if you would like to correct it. So obviously there is a way to fix this problem, but for my scenario requiring user intervention was just not good enough .. so I returned to searching. I finally came across an algorithm, buried in the depths of MSDN, which described how to fix the ordering of a DACL (Discretionary Access Control List). There was only one problem: it was written in VB. So, after a lot of hard work converting the code (just kidding, it was quite simple to convert), I produced the following helper method to attempt fixing the order of a DACL.

 

   internal static void CanonicalizeDacl(NativeObjectSecurity objectSecurity)
   {
       if (objectSecurity == null)
       {
           throw new ArgumentNullException("objectSecurity");
       }

       if (objectSecurity.AreAccessRulesCanonical)
       {
           return;
       }

       // A canonical ACL must have ACES sorted according to the following order:
        //   1. Access-denied on the object
        //   2. Access-denied on a child or property
        //   3. Access-allowed on the object
        //   4. Access-allowed on a child or property
        //   5. All inherited ACEs 
        RawSecurityDescriptor descriptor = new RawSecurityDescriptor(objectSecurity.GetSecurityDescriptorSddlForm(AccessControlSections.Access));

       List<CommonAce> implicitDenyDacl = new List<CommonAce>();
       List<CommonAce> implicitDenyObjectDacl = new List<CommonAce>();
       List<CommonAce> inheritedDacl = new List<CommonAce>();
       List<CommonAce> implicitAllowDacl = new List<CommonAce>();
       List<CommonAce> implicitAllowObjectDacl = new List<CommonAce>();

       foreach (CommonAce ace in descriptor.DiscretionaryAcl)
       {
           if ((ace.AceFlags & AceFlags.Inherited) == AceFlags.Inherited)
           {
               inheritedDacl.Add(ace);
           }
           else
            {
               switch (ace.AceType)
               {
                   case AceType.AccessAllowed:
                       implicitAllowDacl.Add(ace);
                       break;

                   case AceType.AccessDenied:
                       implicitDenyDacl.Add(ace);
                       break;

                   case AceType.AccessAllowedObject:
                       implicitAllowObjectDacl.Add(ace);
                       break;

                   case AceType.AccessDeniedObject:
                       implicitDenyObjectDacl.Add(ace);
                       break;
               }
           }
       }

       Int32 aceIndex = 0;
       RawAcl newDacl = new RawAcl(descriptor.DiscretionaryAcl.Revision, descriptor.DiscretionaryAcl.Count);
       implicitDenyDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
       implicitDenyObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
       implicitAllowDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
       implicitAllowObjectDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));
       inheritedDacl.ForEach(x => newDacl.InsertAce(aceIndex++, x));

       if (aceIndex != descriptor.DiscretionaryAcl.Count)
       {
           System.Diagnostics.Debug.Fail("The DACL cannot be canonicalized since it would potentially result in a loss of information");
           return;
       }

       descriptor.DiscretionaryAcl = newDacl;
       objectSecurity.SetSecurityDescriptorSddlForm(descriptor.GetSddlForm(AccessControlSections.Access));
   }

 

I hope this code saves someone out there a lot of trouble and time. Until next time …

 

 

What software developer can start anything new without the proverbial "Hello, World". Since I'm new to this side of things, this will serve as my introduction. My name is Patrick Carnahan, and I'm currently a developer on a product called Team Foundation Server, of which I focus on the Build Automation functionality. More specifically, I mainly design and work on the server code, including web services, data access, and SQL. This isn't where I started my almost 4 year career at Microsoft, however. I was originally hired on to the Version Control team within the same server product in June of 2005. At that point in time I knew nothing about .NET application development, ASP.NET web services, and very little about SQL development. This product has allowed me to interact with these technologies much more than I ever would have working on projects for myself. I have become proficient in SQL, including query performance analysis and debugging. My knowledge of .NET libraries continues to grow every day. I deal with distributed programming models all the time, which keeps my job extremely interesting.  

I am very passionate about the product that I work on and hope to deliver compelling releases in the future. Hopefully this spot will serve as a more general discussion of programming topics, but I will most likely include targeted posts helping users of Team Foundation Server get the most out of the product. I have learned quite a bit since starting my tenure with this company, and hopefully I can begin to share my knowledge with others.

 
Page view tracker