Friday, August 01, 2008

Adelaide Geek Dinner August 2008 Announcement

As promised in May, it is time for the August Geek Dinner, specifically the time will be 6:30pm on Saturday 16th August. Everyone seemed to enjoy the food and atmosphere of Caffe Amore on the corner of Pulteney and Pirie Streets last time so we'll be going there again this time.

If you're already on the geek dinner mailing list from previous times expect an invitation shortly. If you've never been invited before or you think your invite may be lost in the mail, just contact me and I'll make sure you get included.

Please RSVP before Wednesday 13th August so I can finalise the booking with the restaurant. Hope to see you all there!

 Saturday, July 05, 2008

TFS Deployer Least Privilege

The original TFS Deployer attempted to access various restricted resources than effectively required it to run as an administrator user. Since some changes were made in February, Deployer can now be executed under a standard user account with some additional considerations which follow.

Team Foundation Server Permissions

The TFS Deployer service account must be a member of the Team Foundation Valid Users server-level group on the server specified by the TeamFoundationServerUrl config setting and also a member of the Readers project-level group for the Team Projects that Deployer will be monitoring.

If you intend to make use of the Retain Build feature, the Deployer service account will also need the Write To Operational Build Store project-level permission on applicable Team Projects.

PowerShell Execution Policy

TFS Deployer can execute Windows Command Scripts if you really need it to but PowerShell scripts are the preferred option. However, PowerShell is built to be secure-by-default and installs with script-running capabilities disabled. It is left for the user to enable scripts after PowerShell has been installed and can be performed by opening the PowerShell console as an administrator and executing either:

Set-ExecutionPolicy AllSigned ; # or
Set-ExecutionPolicy RemoteSigned

The former choice will require you to sign all your deployment scripts in source control with a certificate trusted by the Deployer. Scott Hanselman has a comprehensive post on his blog describing the signing process. The latter choice will not impose this requirement because files retrieved from source control by Deployer are not marked with a zone identifier.

HTTP Namespace Reservation

TFS Deployer utilises self-hosted WCF over HTTP to subscribe to Build Quality Change events from TFS. This technique relies on the Windows HTTP Server API which requires the Deployer service account to have been granted rights to listen on the portion of the HTTP URL namespace it uses for event notification. By default only local administrators have access to the entire namespace and permissions must be granted using the httpcfg tool (on Windows XP and Server 2003) or the netsh tool (on Vista and Server 2008).

Using the netsh tool, from an administrator command prompt, the following command (as a single line) will grant rights to the appropriate URL for TFS Deployer (assuming you are using the default port number):

netsh http add urlacl url=http://+:8881/BuildStatusChangeEvent user=DOMAIN\TfsDeployerUser

Using the httpcfg tool is more difficult as it requires an ACL string in Security Descriptor Definition Language (SDDL). However, Dominick Baier has created a tool to simplify this process and I can recommend it.

Additional Permissions

All deployment scripts executed by TFS Deployer will be executed under the context of the service account unless the scripts explicitly contain alternate credentials. If the scripts copy files to a network share, configure a web site in IIS, or even upgrade the schema of a SQL database, ensure that the service account also has the minimum privileges to perform those actions too.

Any problems, questions, or suggestions... let me know.

 Wednesday, July 02, 2008

Explaining new features in TFS Deployer

Since I began helping Mitch with changes to TFS Deployer to make it compatible with TFS 2008, I have slowly been adding small features that seemed useful in my work environment. However, unless you've read the code, you probably wouldn't know they exist so I'll describe them here.

Quality Mapping Wildcard

TFS Deployer operates on executing scripts for particular build quality transitions. Normally transitions like "Test" to "Production" are common. However you may wish to have a script that runs when the quality is set to "QA" regardless of what is was before. You can now specify asterisk (*) in place of the value of either of the OriginalQuality or the NewQuality attributes in the DeploymentMappings.xml file to specify a match on any quality.

Retain Build

Team Build in TFS 2008 now has Retention Policies to clean out old builds. If you deploy a build with TFS Deployer you don't want the policy deleting the build outputs. You may also want builds to become subject to policy again after you retire them from failed acceptance testing. You can add a RetainBuild attribute to a Mapping element in the D*M*.xml file. A value of "true" will mark the build to be kept upon successful execution of the deployment script. A value of "false" will unmark it, and an absent attribute will leave the retain flag alone.

Shared Deployment Resources

With multiple Team Builds defined and multiple deployment configurations you're bound to end up with scripts with common components you'd like to refactor out to a shared location. You can now specify a TFS path (ie $/MyProj/TeamBuildTypes/Shared/Deployment/) in the TfsDeployer.exe.config file in the SharedResourceServerPath application setting. If provided TFS Deployer will get the files in this directory and place them in the same folder as the deployment script on each script run.

The first two features are in the latest release drop on CodePlex. The latter is only in the latest change set, so check the Source Code tab if you intend to use shared resources. Any problems, questions, or suggestions... let me know.

 Saturday, May 24, 2008

Adelaide Geek Dinner May 2008 Summary

Photo of the guests The May Geek Dinner was long overdue but well worth the wait. I invited a record high number of people, fifteen, but due to a variety of circumstances, a record low number of people made it to the dinner: five, including myself.

I was joined by the always entertaining regulars: Paul Stovell, Keith Zerna, and Jim Burger. We were also pleased to be joined by Nigel Spencer for the first time since I started organising the dinners last year.

Aside from the usual .NET subjects, many other topics were discussed on the night, here are some links in no particular order:

I am writing this summary a week since the night of the dinner and I've managed to forget some of the night's events. Perhaps the other attendees could leave some comments with links to the interesting topics I've missed.

Watch this blog for details of the August 2008 Dinner which I'll likely post after Code Camp SA.

Using IDisposable and Lambdas for temporary values

What follows is some code I wrote last week that I couldn't find anywhere else and I thought was useful. First, is a test method demonstrating my use case, followed by the implementation of the TemporaryValue classes to fulfil the requirement.

The property get lambda expression can be an instance or static property as long as it is read-write. In this implementation there is no exception handling and no multithreading considerations.

[TestMethod]
public void Original_property_value_should_be_restored_after_temporary_value_block()
{
    const string OriginalValue = "_original_value_";
    const string NewValue = "_new_value_";
    var f = new Foo {Bar = OriginalValue};

    Assert.AreSame(OriginalValue, f.Bar);

    using (TemporaryValue.For(() => f.Bar, NewValue))
    {
        Assert.AreSame(NewValue, f.Bar);
    }

    Assert.AreSame(OriginalValue, f.Bar);
}

public static class TemporaryValue 

    public static TemporaryValue For(Expression<Func> propertyGet, TValue newValue) 
    { 
        return new TemporaryValue(propertyGet, newValue); 
    } 

 
public class TemporaryValue : IDisposable 

    private PropertyInfo _propertyInfo; 
    private object _target; 
    private TValue _original; 
 
    public TemporaryValue(Expression<Func> propertyGet, TValue newValue) 
    { 
        var memberExpression = (MemberExpression)propertyGet.Body; 
        _propertyInfo = (PropertyInfo)memberExpression.Member; 
 
        var targetExpression = memberExpression.Expression
        if (null != targetExpression) 
        { 
            _target = Expression.Lambda<Func<object>>(targetExpression).Compile().Invoke(); 
        } 
 
        _original = propertyGet.Compile().Invoke(); 
        Set(newValue); 
    } 
 
    private void Set(TValue value) 
    { 
        _propertyInfo.SetValue(_target, value, null); 
    } 
 
    public void Dispose() 
    { 
        if (null != _propertyInfo) 
        { 
            Set(_original); 
            _propertyInfo = null
            _target = null
            _original = default(TValue); 
        } 
    } 
}

After learning to manipulate lambda expressions in this way I decided it would be a good idea to use a similar approach to remove the need for string literals in an INotifyPropertyChanged implementation. Unfortunately, while I was offline for a few days, Paul got a head start.

 Sunday, April 27, 2008

Adelaide Geek Dinner May 2008

After the success of the local geek dinner in early February, I intended to organise another one in late March but I became a little overwhelmed at home and work and completely forgot to organise it. My apologies to those who had been looking forward to it.

However, I am now officially organising another dinner for Saturday May 17th. Instead of the usual location (where customer satisfaction has taken a turn for me) I will be relocating the dinner to another restaurant. I will strive to choose a replacement that is still located centrally, has good parking and public transport access, and affordable, enjoyable food with the option to split the bill.

At this point I'm considering Caffe Amore on the corner of Pulteney and Pirie Streets in the CBD, about 400m from the previous venue. I enjoyed dinner there with friends a few weeks ago and was quite pleased with their food and service. Assuming I can book a table there for all the guests and no one suggests a better alternative, this will probably be the new venue.

I will be sending an email invitation to all the previous dinner guests but if you don't receive yours before May or haven't attended before and would like to join us, please don't hesitate to contact me as soon as possible so I can give the restaurant an accurate number of guests to expect.

I hope to see a few people who made it to Code Camp Oz this year. I was unfortunately double-booked and unable to attend and would like to hear all about it.

 Sunday, April 13, 2008

TFPT TreeClean tamed with PowerShell

Update: Philip Kelley from Microsoft, creator of TFPT, has kindly informed me that the July 2008 release of the TFS Power Tools is now available for download. This new version includes enhancements to TFPT TreeClean that allow you to specify which files to include or exclude and as such solves the main problem my TreeClean PowerShell script was created for. The output format of the new TreeClean also renders this script incompatible but the general concepts used by the script may still be useful.


I like the Team Foundation Server 2008 Power Tools, there are some great additions in there. One particular utility, TreeClean, has a great concept but is a little overzealous for my tastes.

The purpose of TreeClean is to find all local files in your workspace folders that do not exist in source control and then allow you to delete all of them. The problem is that it includes *.user files in its find results and the delete option is all or nothing. The list of files can also be rather overwhelming.

Thankfully we can get some more control by piping the results through PowerShell, starting with a simple script like this:

$ProgFiles = $Env:ProgramFiles ;
$ProgFiles32 = (Get-Item "Env:ProgramFiles(x86)").Value ;
if (-not [String]::IsNullOrEmpty($ProgFiles32)) { $ProgFiles = $ProgFiles32 ; }

$TFPTEXE = Join-Path -Path $ProgFiles `
    -ChildPath "Microsoft Team Foundation Server 2008 Power Tools\TFPT.exe" ;
if (-not (Test-Path -Path $TFPTEXE)) { throw "TFPT.EXE not found." ; }

[string]$Root = Resolve-Path -Path (Get-Location) ;

& $TFPTEXE treeclean `
    | Where-Object { $_ -like ($Root + "*") } `
    | Get-Item -Force ;

Once we have this script saved we can get more information from the results. For example, we can get count and list rogue files by extension:

TreeClean.ps1 | group Extension

We can exclude directories:

TreeClean.ps1 | ? { -not $_.PSIsContainer }

And finally we can delete everything but *.user files:

TreeClean.ps1 | ? { $_.Extension -ne ".user" } | Remove-Item

Now I can clean all the junk from my workspace but keep all my user-level project settings. However, while sorting through the extension-grouped report, looking for files to check-in before cleaning, there was a lot of noise from the build outputs. My quick solution:

gci -inc *.sln -rec | % { MSBuild /t:Clean $_ }

It also has the nice side effect of significantly reducing your workspace folder size if you want to zip it up and send it somewhere.

 Friday, April 11, 2008

The Next Step For VS2008 Database Edition

I started using VSTS Database Edition back when it was called Data Dude and available as a CTP download. Since then I have slowly embraced all its features and I now use it as my complete database development solution from schema management, to data generation, and finally deployment.

DB Edition has its quirks but you learn to understand them and work with them and each new version has new features to improve the workflow. However, as I have been using DB Edition each day, an idea has been steadily stewing in my head for where I'd like to see DB Edition go next. My thought process in a nut-shell follows...

A Database Project allows you to define your schema and data generation and, from a Visual Studio context menu, deploy the database to a chosen server as a new database or as an upgrade to an existing database. You can also use the DatabaseTestService in a test project to deploy the database and test data for automated testing. And finally you can use the SqlDeploy MSBuild task to deploy the database as part of a continuous integration build.

However, all of these methods of deployment require the relative path to the Database Project and all its SQL scripts and some settings in a configuration file. This causes problems in deployed test runs and Team Build test runs where the relative path to Database Project often changes. It also restricts using multiple test databases in a single test project due to the way the configuration file works.

I propose that, at build time, DB Edition could package into a .NET assembly, the full definition of the Database Project along with some standard bootstrap code but minus the deployment configuration. Test projects could then include a reference to the DB assembly and make calls into the bootstrap code, perhaps something in the form:

MyDBAssembly.Schema.DeployTo(someConnectionString, someOptions);
MyDBAssembly.SomeDataGenPlan.DeployTo(someConnectionString);

The DB assembly will be treated as a dependency like any other references would and will naturally be moved around wherever the primary assembly gets deployed and all the necessary information will always be available to perform a completely new database deployment or to perform a schema upgrade on any compatible existing database instance.

If the DB assembly happened to double as a console application, it could be used for ad hoc command-line deployments or even included in batch, MSBuild, or PowerShell scripts for automated deployments.

I am contemplating several ways to hack a feature like this into DB Edition myself but I'm hoping someone else has already done it or maybe the DB Edition team already has it on the cards for Rosario.

 Wednesday, April 09, 2008

Custom TFS Check In Policy Responsiveness

I've used several third party Check In Policies for Team Foundation Server with both TFS2005 and 2008 and I've dabbled in writing my own too. One thing I noticed with most of them, is that they don't appear to respond to actions in the VS Pending Changes window as readily as the standard Microsoft policies.

I recently followed Jim's example Option Strict Check In Policy to write my own policy to prevent checking in code with the DataSet Designer ConnectionState Bug. The policy would always correctly evaluate when clicking the Check In button but the Policy Warnings tab didn't always update automatically when Source Files list changed.

I decided it was time to dig deeper and find out why the standard policies work nicely when compared to the custom ones. After a few minutes with Reflector I discovered there were a few more things to do beyond the instructions in the MSDN article to create a responsive custom policy.

The PolicyBase class, from which your custom policy should inherit, gets passed an instance of IPendingCheckin to its Initialize method which it persists and is made available to your subclass via a protected PendingCheckin property. The IPendingCheckin instance exposes several other objects with events that you can handle to be notified when changes relevant to your policy occur.

The methodology that worked for me was to override Initialize and register the event handler and override Dispose also to remove the handler at the end. All the handler does, after checking the base class isn't disposed, is to call the custom policy's Evaluate function and raise the base's PolicyStateChanged event.

Sample code for a policy dependent on the Source Files list, like the Option Strict policy and the ConnectionState Bug policy, follows:

Public Overrides Sub Initialize(ByVal pendingCheckin As IPendingCheckin)
    MyBase.Initialize(pendingCheckin)
    AddHandler MyBase.PendingCheckin.PendingChanges.CheckedPendingChangesChanged, _
        AddressOf CheckedPendingChangesChanged
End Sub

Private Sub CheckedPendingChangesChanged(ByVal sender As Object, ByVal e As EventArgs)
    If Not MyBase.Disposed Then
        Dim failures = Evaluate()
        MyBase.OnPolicyStateChanged(failures)
    End If
End Sub

Public Overrides Sub Dispose()
    RemoveHandler MyBase.PendingCheckin.PendingChanges.CheckedPendingChangesChanged, _
        AddressOf CheckedPendingChangesChanged
    MyBase.Dispose()
End Sub