Thursday, September 18, 2008

Recursive Copy in TFS MSBuild

In working with TFS I just discovered the Well-Known Item Metadata (bad name in my opinion as I don't they they are really all that "Well-known") which can be used in MSBuild. One of these is %(RecursiveDir) .

From MSDN:

If the Include attribute contains the wildcard **, this metadata specifies the path to the file, beginning at the location of the wildcard. For more information on wildcards, see How to: Use Wildcards to Build All Files in a Directory. 
This example has no RecursiveDir metadata, but if the following example was used to include this item, the item would contain a RecursiveDir value of MyProject\Source\. 
    <MyItem Include="C:\**\Program.cs" /> 
If the following example was used to include this item, the RecursiveDir value of the item would remain MyProject\Source\. 
    <MyItem Include="C:\**\Source\Program.cs" /> 

This gives a lot of flexibility when using MSBuild. It allows you to create a ItemGroup element which can include and exclude files in a directory recursively and then copy those files recursively to another directory!

This allowed me to do the following to copy the build out put of a web site (which naturally has a lot of sub-folders) easily up to the server.

<Target Name="AfterDropBuild" >
  <Message Text="AfterDropBuild being fired" />
    <Compile Include="$(DropLocation)\$(BuildNumber)\Mixed Platforms\Release\_PublishedWebsites\User Interface\**\*.*"
             Exclude="$(DropLocation)\$(BuildNumber)\Mixed Platforms\Release\_PublishedWebsites\User Interface\connectionStrings.config"/>
  <Copy SourceFiles="@(Compile)" DestinationFolder="\\ServerName\MyFolder\%(RecursiveDir)"></Copy>
  <Message Text="Completed coping files" />

MSBuild takes awhile to learn all the tricks, but the more tricks I learn the more I like it.

Monday, August 25, 2008

Custom TFS Task to Change WorkItem State

I recently had to create a custom task to update the state all work items associated with a changeset were in. Here is the code I came up with to accomplish this.

First we start of by creating a general class that includes a few properties. The properties will allow us to pass in variables to the task. LocalPath and BuildURI are variables which TFS build provides for you. I also included the using statements you need to have for this task.

   1: using Microsoft.Build.Utilities;
   2: using Microsoft.TeamFoundation.Client;
   3: using Microsoft.TeamFoundation.VersionControl.Client;
   4: using Microsoft.TeamFoundation.WorkItemTracking.Client;
   5: using Microsoft.TeamFoundation.Build.Client;
   8: namespace MyCustomStuff.TFS.Tasks
   9: {
  10:     public class UpdateWorkItemStatus : Task
  11:     {
  12:         // A value for each one of these properties must be supplied in the XML build file. 
  13:         // LocalPath and BUILDURI can use the MSBuild variables of $(TeamFoundationServerURL) and $(BuildURI)
  14:         [Required]
  15:         public string LocalPath { get; set; }
  16:         [Required]
  17:         public string BuildURI { get; set; }
  18:         [Required]
  19:         public string NewStatus { get; set; }
  21:         // This is the status the work item must be in for the status to be updated. This prevents us from updating all 
  22:         // workitems that where checked in associated to a buid.
  23:         public string ConditionalStatus { get; set; }
  25:         public override bool Execute()
  26:         {
  28:         }
  29:     }
  30: }

Now we are set up and ready to build out the task. What I wanted to do was connected to TFS and get a list of all the workitems associated with the build that was just run (this is why you have to pass in the Build URI). If you insert the follow code in the line 27 from above you get a working custom task.

   1: try
   2: {
   3:     // Get the TFS server
   4:     TeamFoundationServer server = TeamFoundationServerFactory.GetServer(LocalPath);
   7:     // Get the WorkItem store
   8:     WorkItemStore wiStore = (WorkItemStore)server.GetService(typeof(WorkItemStore));
   9:     //BuildStore buildServer = (IBuildServer)server.GetService(typeof(IBuildServer));
  10:     IBuildServer buildServer = (IBuildServer)server.GetService(typeof(IBuildServer));
  11:     IBuildDetail build = buildServer.GetAllBuildDetails(new Uri(BuildURI, UriKind.Absolute));
  13:     List<IWorkItemSummary> assocWorkItems = InformationNodeConverters.GetAssociatedWorkItems(build);
  14:     EventLog.WriteEntry("UpdateWorkItemStatus", string.Format(string.Format("Found {0} workitems for Build {1} : BuildURI = {2} LocalPath = {3}",assocWorkItems.Count, build.BuildNumber, BuildURI,LocalPath), EventLogEntryType.Information));
  16:     // Loop through each work item and update the status
  17:     foreach (IWorkItemSummary wItem in assocWorkItems)
  18:     {
  19:         WorkItem wi = wiStore.GetWorkItem(wItem.WorkItemId);
  20:         // Only update the work Item if no conditional status was provided or if the conditional status matches
  21:         if (ConditionalStatus == string.Empty || wi.State.ToLower().Trim() == ConditionalStatus.ToLower().Trim())
  22:         {
  23:             EventLog.WriteEntry("UpdateWorkItemStatus", string.Format("WorkItem {0} was updated from {1} to {2}", wItem.Id, wItem.Status, NewStatus), EventLogEntryType.Information);
  24:             // Update the state and add a note in the history stating the build process made this change.
  25:             wi.History.Insert(0, string.Format("Build process changed state from {0} to {1}", ConditionalStatus, NewStatus));
  26:             wi.State = NewStatus;
  27:             wi.Save();
  28:         }
  29:     }
  31:     return true;
  32: }
  33: catch (Exception ex)
  34: {
  35:     EventLog.WriteEntry("UpdateWorkItemStatus", ex.Message, EventLogEntryType.Error);
  36:     return false;
  37: }

For this task I get the list of all workitems associated with the changeset and loop through that list. As I loop through I get the details for each workitem and than perform a check to see rather or not I should update the workitem. If the workitem's state matches the conditional state parameter that was passed in, than I update the workitem to the new state which was also passed in to my task. That is all there is to it. Now lets take a look at how you call it via your TFS build script.

Once you build the above code and deploy it to your build server you can call that task from any TFS build which uses that build server. To call the task you have to make a couple additions to the XML in your TFS build. First you need to add a node that says you are using a new task:

   1: <!-- This is the custom task that will update all associated work items to a new status -->
   2: <UsingTask TaskName="UpdateWorkItemStatus" AssemblyFile="$(MSBuildExtensionsPath)\MyCustomTFSTask.dll"/>

This allows the rest of your build process to now call a custom task of "UpdateWorkItemStatus." The $(MSBuildExtensionsPath) is a global parameter in TFS build and is for the MSBuild folder under the Program Files directory. This location is a useful place to put custom target files (For example, your targets files could be installed at \Program Files\MSBuild\). Now all you need to do is call the task after the build is done. To do this you would think you could use the "AfterBuild" target but you cannot (at least I have never gotten it to work). You actually need to use the "TeamBuild" target. This target is fired off after the build has completed and can be called with the follow XML.

   1: <Target Name="TeamBuild" Condition=" '$(IsDesktopBuild)'!='true' "
   2:           DependsOnTargets="$(TeamBuildDependsOn)">
   3:     <Message Text="TeamBuild is firing!"/>
   4:     <!-- Now update all associated work items to the right status -->
   5:     <UpdateWorkItemStatus NewStatus="Sys Test" LocalPath="$(TeamFoundationServerUrl)" BuildURI="$(BuildUri)" ConditionalStatus="Dev Complete" />
   7:  </Target>
This is where you declare the value of all your parameters. Now each time at team build fires off any associated workitem that is a match will be updated.

Monday, July 21, 2008

Silverlight Webcasts

Here is a great list of Silverlight webcasts to help get people started. Really great work from Mike Taulty!

  1. Silverlight - Hello World
  2. Silverlight - Anatomy of an Application
  3. Silverlight - The VS Environment
  4. Silverlight - Content Controls
  5. Silverlight - Built-In Controls
  6. Silverlight - Width, Height, Margins, Padding, Alignment
  7. Silverlight - Using a GridSplitter
  8. Silverlight - Grid Layout
  9. Silverlight - StackPanel Layout
  10. Silverlight - Canvas Layout
  11. Silverlight - Databinding UI to .NET Classes
  12. Silverlight - Simple Styles
  13. Silverlight - Custom Types in XAML
  14. Silverlight - Binding with Conversion
  15. Silverlight - List Based Data Binding
  16. Silverlight - Simple User Control
  17. Silverlight - Templating a Button
  18. Silverlight - Resources from XAP/DLL/Site Of Origin
  19. Silverlight - Animations & Storyboards
  20. Silverlight - Uploads with WebClient
  21. Silverlight - Downloads with WebClient
  22. Silverlight - Calling HTTPS Web Services
  23. Silverlight - Calling Web Services
  24. Silverlight - Making Cross Domain Requests
  25. Silverlight - Using HttpWebRequest
  26. Silverlight - File Dialogs and User Files
  27. Silverlight - Using Sockets
  28. Silverlight - Using Isolated Storage
  29. Silverlight - .NET Code Modifying HTML
  30. Silverlight - Using Isolated Storage Quotas
  31. Silverlight - Calling JavaScript from .NET
  32. Silverlight - Evaluating JavaScript from .NET Code
  33. Silverlight - Handling HTML Events in .NET Code
  34. Silverlight - Handling .NET Events in JavaScript
  35. Silverlight - Calling .NET from JavaScript
  36. Silverlight - Displaying a Custom Splash Screen
  37. Silverlight - Passing Parameters from your Web Page
  38. Silverlight - Loading Media at Runtime
  39. Silverlight - Dynamically Loading Assemblies/Code
  40. Silverlight - Reading/Writing XML
  41. Silverlight - Multiple Threads with BackgroundWorker
  42. Silverlight - Insert/Update/Delete with the DataGrid
  43. Silverlight - Getting Started with the DataGrid
  44. Silverlight - Embedding Custom Fonts

Monday, June 23, 2008

Copy Multiple Files via TFS build

bI have recently been doing a lot of work with TFS and want to write about a few things I had to figure out how to do. As part of my build process I had a number of dlls I needed to copy out to my staging environment. To do this I overrode the AfterCompile target. In order to override this Target you have to import the Microsoft.Common.targets file.

<Import Project="$(MSBuildBinPath)\Microsoft.Common.targets" />

In MSBuild you can call Copy which will move a file from one location to another location. The key word here is that it will move "a file", it will not move "files." So you can call something like this:
<Copy SourceFiles="$(DeployStagePath)\my.dll"
          DestinationFolder="$(DeployPath)" />
But you cannot use code like this to copy all dll files:
<Copy SourceFiles="$(DeployStagePath)\*.dll"
          DestinationFolder="$(DeployPath)" />

To copy all dlls in a folder you need to create a new item. Here is the code I used to do this.

<Target Name="AfterCompile" DependsOnTargets="CopyFiles"> 
    <Message Text="The after build event sets up the compiled code for ClickOnce Deployment" Importance="high"/>

    <!-- Set up the items for doing a copy of a collection of files -->
    <CreateItem Include="$(DeployStagePath)\*.dll">
      <Output TaskParameter="Include" ItemName="DLLsToCopy"/>

    <Copy SourceFiles="@(DLLsToCopy)" DestinationFolder="$(DeployPath)" />

Wednesday, May 21, 2008

Silverlight Style at MIX

This is a great recording from MIX that covers styling in Silverlight 2. I though it was great so I wanted to share it and save it for future reference.

Creating Rich, Dynamic User Interfaces with Silverlight 2

Tuesday, May 20, 2008

MOSS 2007 Templates

I needed to have a quick reference guide to the different templates in MOSS 2007 and what each one is for. So here it is.

Template Category Template Name Description
Collaboration Team Site
A site for teams to quickly organize, author, and share information. It provides a document library, and lists for managing announcements, calendar items, tasks, and discussions. 
Blank Site
A blank site for you to customize based on your requirements.
Document Workspace
A site for colleagues to work together on a document. It provides a document library for storing the primary document and supporting files, a tasks list for assigning to-do items, and a links list for resources related to the document.
Wiki Site
A site for a community to brainstorm and share ideas. It provides Web pages that can be quickly edited to record information and then linked together through keywords
Collaboration Blog
A site for a person or team to post ideas, observations, and expertise that site visitors can comment on.
Basic Meeting Workspace
A site to plan, organize, and capture the results of a meeting. It provides lists for managing the agenda, meeting attendees, and documents.
Blank Meeting Workspace
A blank meeting site for you to customize based on your requirements.
Decision Meeting Workspace
A site for meetings that track status or make decisions. It provides lists for creating tasks, storing documents, and recording decisions
Social Meeting Workspace
A site to plan social occasions. It provides lists for tracking attendees, providing directions, and storing pictures of the event.
MultiPage Meeting Workspace
A site to plan, organize, and capture the results of a meeting. It provides lists for managing the agenda and meeting attendees in addition to two blank pages for you to customize based on your requirements.
Document Center
A site to centrally manage documents in your enterprise
Records Center
This template creates a site designed for records management. Records managers can configure the routing table to direct incoming files to specific locations. The site prevents records from being modified after they are added to the repository.
Site Directory
A site for listing and categorizing important sites in your organization. It includes different views for categorized sites, top sites, and a site map.
Report Center
A site for creating, managing, and delivering Web pages, dashboards, and key performance indicators that communicate metrics, goals, and business intelligence information.
Search Center With Tabs
A site for delivering the search experience. The welcome page includes a search box with two tabs: one for general searches, and another for searches for information about people. You can add and customize tabs to focus on other search scopes or result types.
My Site Host
A site used for hosting personal sites (My Sites) and the public People Profile page. This template needs to be provisioned only once per Shared Service Provider, please consult the documentation for details.
Search Center
A site for delivering the search experience. The site includes pages for search results and advanced searches.
Collaboration Portal
A starter site hierarchy for an intranet divisional portal. It includes a home page, a News site, a Site Directory, a Document Center, and a Search Center with Tabs. Typically, this site has nearly as many contributors as readers and is used to host team sites.
Publishing Portal
A starter site hierarchy for an Internet-facing site or a large intranet portal. This site can be customized easily with distinctive branding. It includes a home page, a sample press releases subsite, a Search Center, and a login page. Typically, this site has many more readers than contributors, and it is used to publish Web pages with approval workflows.

Monday, April 28, 2008

WPF Localization - LocBaml

In my last post I talked about using resource files (resx) to localize your WPF application. This time it is about using LocBaml to localize your WPF application. The big difference in the two approaches is that LocBaml allows you to localize your application after the fact. That is, most applications you have to plan up front to do localization, but with LocBaml you can still localize your application after development. While lots of people will say this I don't really believe it. The reason this is not true in my opinion is because LocBaml only works if all your strings are sourced in XAML. This means that if you use strings that are located in a constant file or a resx file LocBaml  will not work to localize your application. Unless you are building a smaller application or you just happen to set up your architecture so all strings are sourced in XAML you are out of luck. So really when they say you can localize your application after the fact you really can only do it if you happened to conform your application to the requirements of LocBaml. On that note lets get into preparing the application with UIDs.

In my previous example I had three labels in my application. The first label had its text defined in XAML. The other two used resx files to define their text. In this walk through we will update the first label so we can localize it as well. The first thing we want to take note of is the current XAML layout of our label.

<Label Height="28" Margin="10,13,33,0" 
  Name="lblFromXAML" Content="Text from XAML" 
You will notice nothing special about the XAML here. Now we will run the LocBaml commands. To do this we open a visual studio command prompt and run msbuild with the updateuid command switch (msbuild /t:updateuid wpflocalization.csproj). After running this command we get the follow results in the command windows.


Now if you look at the XAML that makes up the label you will see the addition of UID attribute.

<Label x:Uid="lblFromXAML" Height="28" Margin="10,13,33,0" 
Name="lblFromXAML" Content="Text from XAML" 
This UID attribute tags each localization part of the application and will allow the LocBaml utility to find each one of these areas for localization. Now when you build the application you will have a .resource.dll file in the en-US folder. It is now time to jump through hoop 1 (really hoop two because setting up UIDs should be hoop 1). You must get the SDK and build the LocBaml project to get the LocBaml.exe. Once you have done this it is time for hoop 2. Run LocBaml against the US resource dll using the following syntax: LocBaml.exe /parse en-US/WpfLocalization.resources.dll /out:UsText.csv. This will export all the XAML fields marked with a UID to the csv file. The csv file that is created has  a few lines per UID item. Here is what the csv file looks like. Had enough yet? Sorry we are not done. You should notice that in the csv file only has the text for one of our three labels (lblFromXAML). The text that is the resource file is not pulled out into the csv file. This is because LocBaml only works when all strings are sourced in XAML (something you have to take into account at design time). Now you need to pass this csv file off to someone to translate. Now Hoop 3, it is time to run LocBaml again to create our de-DE resources.dll. Once that is done you can drop that dll into your resources directory and change your localization to de-DE and the new text should show up.

If you could not tell there are a lot of hoops to get through doing localization this way. Personal, I think the process is pretty ridiculous. I have no idea why someone would do localization this way instead of using resx files. I see only cons to this approach and no advantages over the resx approach. If you have a different opinion I would really like to hear your thoughts.