Wednesday, 19 January 2011

Centralising global TFS alerts/notifications

TFS email alerts are critical to keeping your team informed about changes in the TFS projects they care about. At the very least, most users probably want to know when they've been assigned a work item.

Although Visual Studio 2010 includes the Alerts Explorer (Team –> Alerts Explorer) for signing yourself up for alerts, there's no obvious way to do the same thing for a group of users. Adding custom business logic into the mix really means you'll need to find a better alternative.

Behold! My latest Codeplex project is live, I call it TFS Global Alerts:

http://tfsalerts.codeplex.com/

When we last faced this problem with Visual Studio Team System 2008, we landed on a solution from vertigo.com--I'd link to the original blog post but it's been removed from the site. Moving forward a few years, it was recently time to upgrade our TFS 2008 install to TFS 2010 and the question naturally arose about what we do with alerts: continue using the Vertigo web service or not?

The custom logic originally added to the Vertigo solution was flailing a bit due to the kludge of time so we decided to refactor the current solution to better meet our needs and keep everything working with TFS 2010. I've uploaded a generalised version of the end result for your alerting enjoyment as an example of handling TFS events in a web service (you may want to follow the links below for alternatives based on Windows services and event listeners).

The Solution

TFS Global Alerts is, at its core, a web service containing a single public method:

[WebMethod]
public void Notify (string eventXml)

Notify() accepts a single parameter, a string containing XML detailing the change that occurred and some information about the work item itself. The Notify() method signature changed slightly from TFS 2008: it previously included a second string parameter, tfsIdentityXml. I removed this parameter to get things wired up successfully with TFS 2010.

Most elements in the eventXml look a bit like this—note the OldValue and NewValue child elements:

<Field>
<Name>State</Name>
<ReferenceName>System.State</ReferenceName>
<OldValue>Active</OldValue>
<NewValue>Resolved</NewValue>
</Field>

With the Notify() method called and most of the data you need at hand, sending an email to the relevant parties is then quite simple. TFS Global Alerts does some additional work to exclude the person who made the change from receiving a notification about that change (presumably they know what they've just done!) and avoid sending notifications about code check-ins linked to a work item.

The only real complexity in this code is retrieving an email address for each user from Active Directory. In our production environment I found TFS listed users by display name however in development, with TFS running as a service account in the dev domain, users were returned as domain\username. In production, if a user had multiple accounts (some of our admins), their name was returned as "First Last (domain\username)".

In terms of deployment, I simply deploy the web service to the web server hosting TFS itself and TFS web access.

The notification system hangs off TFS' own event subscription tool, bissubscribe.exe (you'll find it at C:\Program Files\Microsoft Team Foundation Server 2010\Tools\bissubscribe.exe). TFS raises events for all sorts of different happenings and bissubscribe.exe allows you to subscribe an email address or SOAP web service to handle those events.

Since I had some problems creating a server-level subscription, here's the command I use to create a project collection subscription (which captures all events in our case):

bissubscribe /eventType WorkItemChangedEvent /address http://localhost:8080/{vdir_path}/Alerts/WorkItemChanged.asmx /collection http://localhost:8080/tfs/{project_collection_name}

Result:

TF50001:  Created or found an existing subscription. The subscription ID is 36

Debugging

TFS 2010 only sends alerts every two minutes by default and events subscribed to via bissubscribe.exe adhere to the same policy. This can be a bit annoying when you're developing so you may want to dial down the batch wait time. I've included a Powershell script in the solution to configure this but otherwise, check out chrisid's post on the subject.

For additional help debugging bissubscribe, Grant Holliday has some useful tips.

Finally, don't forget to actually configure your TFS server to send email!

Resources

If you found this post helpful, please support my advertisers.

Wednesday, 12 January 2011

How to change the service accounts used by TFS 2010

As with many Microsoft products running with specific service accounts, TFS offers an easy way to change the service account it uses or update the account password.

You can do this manually by mucking with application pool identities and database permissions but there's a much easier way using the TFS Administration Console.

Assuming you have the right permissions, fire up the console and click into the Application Tier node; in the Application Tier Summary you'll find two links: Change Account and Reapply Account.

tfs_change_account

Change Account allows you to change the service account used by the system while Reapply Account allows you to change the password for the currently configured account. You can easily switch from Network Service to a local or domain account (or vice versa) but be sure to grant any local/domain account the Log on as service permission.

If you found this post helpful, please support my advertisers.

Tuesday, 11 January 2011

How to run the Visual Studio Remote Debugger as a Service

I've previously outlined a fairly clunky approach to getting the VS remote debugger installed and configured to start automatically (e.g. in a dev environment); since writing that post, I've since unearthed the remote debugger wizard and the joys (and ease) of running it as a service.

Firstly, the remote debug wizard is not shipped with VS2010 so you'll need to download the rdbgsetup file appropriate for your environment. Run the installer and you'll be prompted to configure the remote debugger once installation completes.

The wizard will present you with the option to Run the "Visual Studio 2010 Remote Debugger" service; check the box and supply an account with Log on as a service rights (the wizard will complain if the account isn't configured correctly).

To grant Log on as a service rights, do this in Windows Server 2008:

  1. Administrative Tools –> Local Security Policy;
  2. Security Settings\Local Policies\User Rights Assignment;
  3. Open Log on a service and add the user you want to configure.

The wizard will also configure the firewall on the machine to be debugged so you'll need to tell it what kind of users you want to allow in.

Once complete, you'll find the Visual Studio 10 Remote Debugger service listed in the Services applet and configured to start automatically. It runs as msvmon100 in the Services tab of Windows Task Manager.

Finally, launch visual studio and connect to the target server (Tools –> Attach to Process or CTRL+ALT+P and enter the name of your server in the Qualifier box.

If you found this post helpful, please support my advertisers.

Tuesday, 21 December 2010

grepWin–A lightweight replacement for Windows Search

I don't know if Windows Search on my Windows Server 2008 R2 machine is crippled because it's a server machine (I'm all set up to run Search) but I invariably have trouble finding strings I know exist in files. In other words, search in files or search in content doesn't seem to work despite my configuration, rebuilding the search index, and pulling my hair out.

Having yet another service running that indexes the majority of the non-system files on my computer also doesn't really appeal and for that reason (among others) I've also decided I don't want to add more search indexing with the likes of Google Desktop Search.

In fact, what I want is a lightweight search application that I can occasionally use with regular expressions. It should be fast and convenient to use, preferably without a command line. I want to be able to specify a start directory and optionally have it search subdirectories and file contents.

And here is a tool that meets all of the above criteria: http://tools.tortoisesvn.net/grepWin.html

(FWIW, this is not an infomercial—I haven no affiliation with Stefan or grepWin and am not paid to write this)

grepWin is 64-bit compatible and Stephan offers an installable version that offers Explorer context menu integration or a portable version that doesn't need to be installed.

There's no indexing involved so you'll likely want to narrow the start location of your search as much as possible but it's still fast.

Your search can be case sensitive or not and you can limit file sizes. A search can also exclude directories.

The results pane lists file details (size, path, date, etc) and also offers a content view so you can preview the match.

Search settings are automatically persisted so using the tool is incredibly convenient.

grepWin

If you found this post helpful, please support my advertisers.

Wednesday, 15 December 2010

How to disable web part chrome

In the context of a highly-branded WCM site, the default "chrome" (border and title) SharePoint wraps around your custom web parts can be ugly and inconvenient. At worst, it will make your site look SharePoint-y and require every web part added to a page to have its Chrome settings manually adjusted.

Chrome Type

That's no way for a web part to behave in content management system but what to do?

In the past I've set the chrome type explicitly for each custom web part, either in the .webpart/.dwp file itself or programmatically:

<property name="ChromeType" type="chrometype">None</property>

this.ChromeType = PartChromeType.None;

To avoid doing this per web part (or cluttering up your nice little base web part class), you can set the PartChromeType property on the WebPartZone declared in your page layout:

<WebPartPages:WebPartZone id="wpzLeftColumn" runat="server" title="Left Column" PartChromeType="None">

Any web parts added to that zone with a default Chrome Type (aptly named "Default") will inherit this setting from the web part zone. Of course individual web parts can override this as required.

If you found this post helpful, please support my advertisers.

Enumeration-based property with initialiser fails to load in tool pane

I encountered what is likely an obscure issue today while using an existing enumeration to populate a drop down list in the tool pane of a SharePoint web part.

The web part didn't require a custom toolpart so I'm referring to a web part property defined within the web part class itself:

[SPWebCategoryName ("TWA")]
[FriendlyNameAttribute ("Region Override")]
[WebDescription ("Explicitly configure the region displayed when the mode is set to Region.")]
[Personalizable (PersonalizationScope.Shared, false)]
[WebBrowsable (true)]
public WARegions RegionOverride { get; set; }

Notice how the public RegionOverride property has a type of WARegions. Normally, the end result is an additional custom property displayed in the web part tool pane; in this case, the WARegions type is an enumeration and SharePoint therefore renders this as a drop down list:

Enumeration DDL

I say normally because today I was only getting a disabled DDL containing a single item ("Perth"). SharePoint was also kind enough to present me with this vaguely useful error message:

Some of the properties for this Web Part cannot be displayed properly. For more information, see your site administrator.

The WARegions enumeration is an existing enum I chose to reuse for the sake of convenience. Upon further inspection, I noticed the first element in the enum (Perth) was explicitly assigned a value of 1. Acting on my suspicion, I removed the explicit assignment and the DDL would then display as expected.

As a final test to confirm whether the explicit initialisation was somehow behind the single list item being displayed, I set default values for all elements in the enum but the error returned. Fortunately in my case I can do away with the explicit initialisation (which I'm not keen on—I prefer to initialise enums myself).

If you found this post helpful, please support my advertisers.

Monday, 13 December 2010

My custom Visual Studio keyboard shortcuts

I do all of the builds in my position as Development Coordinator but with the Visual Studio project context menus out of control and nearly unusable with clutter from various plugins (including ReSharper, PowerCommands, and Productivity Power Tools), I've finally cracked.

Since we don't (yet) have a build server, a normal manual build workflow goes something like "do stuff, do more stuff, BUILD, do stuff, OPEN DEPLOYMENT FOLDER IN EXPLORER, copy files to drop folder, etc. Before installing ReSharper, the VS team finally had all context menu items displaying on a single screen, with no scrolling required; ReSharper has ruined all of that again and I now find myself forever scrolling the context menu from the top (Rebuild) to the bottom (Open Folder in Windows Explorer).

Because I'm forever installing Visual Studio in different environments/upgrading/etc, I'm loathe to define custom keyboard mappings (likely a hangover from my days of using the product before Import and Export Settings… was introduced). Nonetheless, these are the shortcuts I've finally created:

Project.OpenFolderinWindowsExplorer – Alt+ O

Build.RebuildSolution – Alt+ R (Why rebuild instead of build? Personal preference)

If you found this post helpful, please support my advertisers.

Friday, 3 December 2010

Relative URLs, the Rich Text Editor, and Reusable Content

We recently started leveraging the Reusable Content list to supply user-editable content to one of our interactive online forms and outbound emails. This allows the content owner to tweak the content as required as this content isn't locked away in custom code or resource files—no build and deploy required and the content management system is even used as such (whatdya know?!).

The Reusable Content list is provisioned by SharePoint when the Publishing feature is enabled; it allows users to define content snippets that can be maintained in a single location and updated automatically wherever they're used. You can optionally treat a reusable content snippet as a template—an editable copy is inserted into the page instead of a read-only, auto-updating view.

Reusable Content list items are pretty straightforward and, most importantly, contain a single HTML (rich text) field. SharePoint naturally displays its rich text editor around this field in edit mode so the edit user experience is similar to editing page content.

Unfortunately HTML fields in SharePoint are smarter than they should be and (in MOSS 2007), the product will mangle some content. I recently discovered it refuses to play with background-image style—they're silently removed whether they're inline or in an embedded stylesheet. (And yes, I know, inline styles are evil but this snippet was actually being plugged into an email so everything had to be self-contained).

Despite the tricks SharePoint plays on you with "managed" URLs, it seems the rich text field also stores URLs pointing to content within the current site as relative URLs. Absolute URLs are converted automagically but you'll never really see this until you pull the content out via the API.

We hunted for a way within SharePoint to convert all relative URLs in this content to absolute URLS but without much luck. I think there may be a Javascript function in one of the client scripts to do so for a chunk of content but there's nothing obvious in the server-side API.

To address this, we replace all relative URLs using the regex below (note the URL group) (is using regex to parse HTML bad? You be the judge). You may want to use SPUtility.GetFullUrl() to convert individual URLs.

@"(?:<\s*(?:a|img)\s+[^>]*(?:href|src)\s*=\s*[\""'])(?!http)(?<url>[^\""'>]+)[\""'>]"

If you found this post helpful, please support my advertisers.

Thursday, 18 November 2010

DateTime format safe for file system

Couldn't find anything similar when I searched so thought I'd throw this out there for use in file names in a Windows file system:

DateTime.Now.ToString ("yyyyMMddHHmmss")

Will output "201001312359"

Add a ".txt" or ".dat" if you're hard-core and you're all set.

If you found this post helpful, please support my advertisers.

Wednesday, 17 November 2010

How to determine if a SPPublishingPage is published

You can programmatically determine if the SPPublishingPage you're dealing with is in a published state by retrieving the SPFileLevel of the page's corresponding SPFile:

if (page.ListItem.File.Level == SPFileLevel.Published) …

You can also access this through the PublishingPage.ListItem.Level property.

The same SPFileLevel enumeration will also indicate if the page is in draft mode (checked in but not published) or checked out but in my experience this property will return SPFileLevel.Published or SPFileLevel.Draft more often than not.

To determine whether the page is checked out, use the SPFile.CheckOutStatus property in WSS 3.0 or the  SPFile.CheckOutType property in SP2010. Any value other than None means the page is checked out.

If you found this post helpful, please support my advertisers.