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.

 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.

 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

 Friday, December 14, 2007

TFS Deployer and TFS 2008

Mitch Denny from Readify made two great tools available for Team Foundation Server last year. TFS Integrator provides Continuous Integration for TFS by registering for check-in events then triggering Team Builds and Dependency Replication. TFS Deployer provides automated deployment for TFS projects by registering for build quality change events then executing associated PowerShell scripts. Both of these tools were developed for TFS 2005.

TFS 2008 has just RTMed and it already includes check-in triggered builds in the box so TFS Integrator is less useful (except maybe the Dependency Replication but this can be done via other methods). TFS Deployer, though, is still very useful in a TFS 2008 environment... assuming you can get it to work.

TFS Deployer was originally developed against the TFS 2005 client libraries and, of course, we've uninstalled VS and TFS 2005 from most of our machines already so Deployer just falls over because it can't find the libraries.

TFS Deployer is not open-source (yet) so I can't fix it myself. However, in a comment Mitch suggests using binding redirects in the TFS Deployer config file to enable TFS 2008 support. Unfortunately he doesn't give specifics.

I decided to drop the TFS Deployer assemblies into Reflector and find the list of TFS libraries it uses so I could write the redirects. I updated the .config file and changed the build quality on some existing builds and happily discovered that it works. If you want to use TFS Deployer with TFS 2008, here is the section to add to the TfsDeployer.exe.config file:

<runtime>
  <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.TeamFoundation.VersionControl.Client" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
      <bindingRedirect oldVersion="8.0.0.0" newVersion="9.0.0.0"/>
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.TeamFoundation.VersionControl.Common.Integration" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
      <bindingRedirect oldVersion="8.0.0.0" newVersion="9.0.0.0"/>
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.TeamFoundation.Build.Common" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
      <bindingRedirect oldVersion="8.0.0.0" newVersion="9.0.0.0"/>
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.TeamFoundation" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
      <bindingRedirect oldVersion="8.0.0.0" newVersion="9.0.0.0"/>
    </dependentAssembly>
    <dependentAssembly>
      <assemblyIdentity name="Microsoft.TeamFoundation.Client" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
      <bindingRedirect oldVersion="8.0.0.0" newVersion="9.0.0.0"/>
    </dependentAssembly>
  </assemblyBinding>
</runtime>

 Wednesday, December 12, 2007

MSBuildTasks, TfsVersion and TFS 2008

Jeremy Miller says it best:

"NEVER build and deploy from a developer environment"

In his article, Jeremy puts into concrete words all the reasons why I felt deploying from the workstation was a bad idea.

In my current project at work I had an opportunity to setup continuous integration and a good developer workflow from the beginning and I already had building, testing, and deploying automated on the build server. But, in the same article Jeremy shows an example for using source control version labels for .NET assembly version numbers.

Until now we were just using the default compiler generated build and revision numbers, which are days since January 1st 2000 and seconds since midnight respectively if I remember correctly. Unfortunately, while this ensures increasing and unique version numbers, it doesn't help map a deployed assembly back to it's source version.

I decided to find an alternative to Jeremy's CC.NET+NAnt build script to suite our TFS2008+MSBuild environment. I knew replacing the version numbers in the AssemblyInfo.* files would be trivial but I needed to get the TFS changeset from somewhere.

The popular open-source MSBuild Community Tasks Project already includes a TfsVersion task to grab various TFS property values and make them available to the script. However, I quickly discovered that the TfsVersion task expects the 2005 version of the TFS client libraries.

Although documentation is slim, all the MSBuildTasks source code is available online in a Subversion repository and I was able to download the code and work on porting it to TFS 2008.

Thankfully I didn't have to. All the TFS related code in MSBuildTasks is done via late-bound proxies and there is a TfsLibraryLocation property on the TfsVersion task that can be pointed to the "...\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\" folder.

I also found Richard Banks' post on Versioning Builds with TFS and MSBuild to be very helpful for getting the build targets right.

 Thursday, November 29, 2007

Upgrading to Team Foundation Server 2008 Workgroup Edition

This week I took upon myself the challenge to upgrade our TFS 2005 SP1 single-server installation to the newly released 2008 version. When I was first introduced to our Team Foundation server it was running Beta 3 of TFS 2005. Since then it has mysteriously become my duty to upgrade to 2005 RTM, 2005 SP1, and now 2008 RTM.

I have gathered much needed experience on my previous upgrades to know how hard it would probably be. Beta 3 was originally running on SQL Server 2005 Developer Edition and the TFS beta wasn't the Workgroup Edition. Needing to upgrade the beta database structures, switch the edition of SQL Server, and even migrate the whole lot to another physical machine has given me the confidence to deal with any TFS install.

I prepared for the 2008 upgrade by backing everything up, running the Best Practices Analyzer, then following the pre-upgrade checklist to the letter. So far everything is great. I inserted the TFS 2008 media, and began the setup. The setup prescan said everything was ok. It detected the existing installation, asked me for a few more settings (account passwords and mail server) then performed the upgrade.

Smooth. No errors. Database schema upgrade succeeded. And it was quick too. Again, I followed all the post-upgrade checklist items one-by-one and I was almost giddy at how easy it all was. I went back to my developer workstation and played around with work items and checked out and in some files. Perfect! I was so happy I went and upgraded our build servers too.

The next day my work colleague tried to undo a pending change and received a nasty error. The undo was failing and crashing the version control component on the server. I chased it down to the prc_iiUndoPendingChanges stored procedure experiencing a collation mismatch. The proc is encrypted but with SQL Profiler I was able to verify that it was a clashing collation between the TfsVersionControl database and SQL's tempdb database.

How do you change the collation on the tempdb database to match the others? You don't... you change the collation on the master database. How do you change the collation on the master database? You drop all your databases, rebuild the master using setup.exe on the SQL install media, then put all your databases back.

Still not that easy though. TFS 2008 requires SQL Server SP1. If you rebuild the master database with the RTM media you get an RTM master database. You can't get or make SP1 slipstreamed install media and you can't reapply SP1 after rebuilding master because all the other parts of SQL are already patched.

So, in the end you backup all your databases, uninstall all of SQL Server, reinstall it with the right collation, reapply the service pack, restore all your databases, rebuild the data warehouse and generally do all the rest of the necessary steps.

It was almost the perfect upgrade. Unfortunately collation settings were missed by the TFS team on one of their temp table definitions, it wasn't picked up by testing, dogfooding, or the beta program, and I get all this hassle.

Still, all the new features in TFS 2008 make it worth it and I'd do it again. And will probably have to.

 Tuesday, September 04, 2007

Debug.Assert and Team Build

Today I was setting up a nightly build for a new project. After creating the Team Build Type and writing the script to trigger the build on the build machine, I ran the script to ensure it would work. As usual Team Build retrieved the sources, compiled the projects and began running the tests. Half an hour passed and the tests were still running.

I knew the tests couldn't take that long so I stopped the build and opened the solution in Visual Studio to run the tests locally. One of the tests managed to create just the right combination of values in a project class to violate what should otherwise be a class invariant. This particular invariant was verified by a call to Debug.Assert inside the production code.

It has been a very long time since I have written a Debug.Assert statement with an expression that failed the assertion. Today I was reminded that it does not simply throw some kind of AssertException when the expression evaluates to False. Instead a rather hefty dialog box is presented to the user with a full stack trace of the code that failed and a choice of options to proceed.

The problem is that tests running under the service of the build server don't have a visible window station to display the dialog box and there is nothing to automatically click the buttons so the build just hangs indefinitely.

For now I'm just replacing any Debug.Assert statements I find with exception throwing or additional unit tests as appropriate for each case. It may be possible to modify the app.config file to use a different listener to redirect the Debug.Assert output to something non-blocking but that is going to require some investigation.

 Saturday, July 28, 2007

Visual Studio versus MSBuild

Hammer I have recently setup a TFS Team Build server for our work projects after realising that the secondary reference issue is not as problematic as first thought. We haven't been using Visual Studio's Remove Unused Reference feature as much as expected and we need to prepare our projects for Visual Studio 2008.

With a build server configured to perform automatic builds every night we are on our way to full continuous integration with builds on check-in. However, there are several other differences between Visual Studio and MSBuild that are creating slight issues for our TFS build agent.

For what I understand of the situation, Visual Studio utilises an in-process variation of the MSBuild engine to support specific features like background compilation but has failed to maintain full compatibility.

For example, installation of SQL Server 2005 on your development machine will offer four new Business Intelligence project types: Analysis Services, Integration Services, Report Server, and Report Model. None of these project types will build from the command line in MSBuild or therefore Team Build. We have had to move these project into separate solutions that we exclude from the build server's automated processing.

Also, strongly-typed DataSets and other designer-based project items are usually based on a metadata file (like an XSD) and use a custom tool to generate an appropriate VB or C# code file before the project is compiled. The project files contain the information about which generator tool to use to update the code files from the metadata but again MSBuild does not support it. Any changes to the metadata that is checked-in will not be reflected in the Team Build output.

The idea to have an IDE and a build engine based on the same technology and file formats is brilliant. The need to only manage build settings in one place is the biggest reason for not moving to NAnt or another offering where I'd need to maintain both the Visual Studio projects and the third party scripts manually.

Unfortunately I feel that Microsoft made two major mistakes with MSBuild and Visual Studio:

  • APIs were provided to allow Visual Studio to be extended to support new project items and project types but the API doesn't force the extension to be callable by MSBuild.
  • Microsoft then utilised this API to extend Visual Studio for SQL Server projects and others too and also ignored MSBuild compatibility themselves.
 Monday, June 04, 2007

Capture To Team Foundation

Dudu Shmaya has just released a plugin for Cropper to create TFS work items from screen captures. You can also use it to add screen capture attachments to existing work items. We already use both Cropper and TFS, and this new plugin should be very useful for creating bug reports.

Dudu says he plans to submit the source code for his plugin to the CodePlex project for Cropper plugins soon. Looks like there are some other nice plugins already on their too.