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

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 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