Unlocking other people’s files. Yes, again.

You’re going about your business, trying to refactor some code, when suddenly things come to a crashing halt.  “The item foo.cs is locked for check-out by DOMAIN\user in workspace ws1.”  Since Microsoft shipped the first public betas of TFS in 2005, there have probably been more blog posts on this pitfall than any other -- I’ll pick on James for an example.  The frustration also spawned Status Sidekick (now part of TFS Sidekicks), the first 3rd-party TFS tool to my knowledge. 

The arrival of the Fall 2008 Powertools hinted at yet another possibility based on Powershell.  Script-friendly unlocking is particularly enticing for scenarios like the one I found myself in last week: transitioning a team from exclusive checkouts to an edit-merge-commit workflow.  Turning off the exclusive settings on the Team Project is the obvious first step, but what to do about the hundreds of existing pending changes?  I can’t ask everyone to suddenly checkin their work (which may be far from ready to share, or even build), but I also can’t reorganize the tree unless every lock is removed.  Thus:

del temp.hat 2> $null foreach ($pendingSet in get-tfspendingchange -user *) { $workspace = '"' + $pendingSet.Name + '";' + $pendingSet.OwnerName $pendingSet.PendingChanges | where {$_.IsLock} | foreach { $item = '"' + $_.ServerItem + '"' "lock /lock:none /workspace:$workspace $item" | out-file temp.hat -append } } tf "@temp.hat" del temp.hat

This isn’t the cleanest solution in the world.  You couldn’t just wire up the “find all locks in the system” example I provided for Brian’s post to a Remove-TfsLock cmdlet -- even if the Power Tools provided one (which they don’t yet).  By convention, a pipeline of TFS cmdlets transmits QualifiedItems, but to unlock an arbitrary item we need two additional pieces of information from the PendingSet object.  So we fall back on good old procedural loops around tf lock.

Could we improve the script?  Sure.  We could batch ~12 items at a time* for a better performance, though the gain is not nearly as big as using a @hatfile in the first place.  We could also be a little more robust.  Get-TfsPendingChange returns a full PendingSet in the general case, but sometimes the return type decays to a simple PendingChange[].  If our query returns exactly 1 workspace, then our logic for populating $workspace won’t work. 

Of course, we could also do a lot worse!  I originally tried to add the wrapper quotes in-place, but quickly got into syntax trouble.  After consulting the relevant Powershell blog post I eventually got it right, but it was an unreadable mess.  For example, line 4 read:

$workspace = "`"$($pendingSet.Name)`";$($pendingSet.OwnerName)"


*3200 character limit on each line of a tf.exe script file; TFS paths are 260 chars max

    Leave a Reply