December 27, 2011 6:20 PM | Comments [1] | by Ed Blankenship
Updates – I plan on making updates to this blog article from time to time as I learn more and changes are made to TFS & SmartAssembly to smooth out some of the rough edges. Come back again for the latest & greatest!
I really like tools that provide assistance with the release management & maintenance cycles of ALM. I think some of the features really add a particular shine to your application. It’s something that I’m currently writing an article about but I wanted to share how to integrate one of those tools into your Team Foundation Server 2010 Build Process. This first tool to be reviewed is Red Gate’s SmartAssembly product.
SmartAssembly is a product that can help you out with obfuscation if you need it but I primarily want to focus on two of its other major features:
You don’t have to worry about any of the details because once you run your assemblies through SmartAssembly, it instruments all of the necessary functionality automatically for you. If you acquire the Professional edition, you can customize the reporting experience including the ability to host your own web server to accept the error & feature usage reports.
Aside: Too many teams & companies have blindly adopted obfuscation for their assemblies in the past without taking into consideration the true “total cost” of obfuscating your applications. I’m all for obfuscation where it makes sense to protect IP as long as the value of the protection of that IP is worth more than the extra cost, resources, and maintenance complexity to truly support an obfuscated product. Each team & company is going to have to make that decision based on the resources available and the value of the IP to be protected – just don’t go into it blindly.
FYI – PreEmptive’s Dotfuscator tool is a competing product line with a similar feature set that I hope to be covering in a future blog post.
Ignoring obfuscation, these two features are absolutely great for gaining visibility about your application once it has been released. For all of those teams that aren’t traditional software vendors but building applications for internal use, these are great features for those applications as well. Software engineering teams building internal applications are very much in need of the same type of information as ISVs about how their internal “customers” are interacting with their applications. Internal applications don’t necessarily need obfuscation but they can definitely benefit from automated error & feature usage reporting!
One part that I absolutely love about SmartAssembly is that even though the tool instruments and changes your assembly, it also provides the ability to produce a set of matching symbols (.PDBs) that are extremely important for several scenarios in TFS, the Visual Studio ALM family of tools, as well as basic debugging.
I am going to be spending some time in this blog article to walkthrough how to integrate SmartAssembly into your automated TFS build process so that your teams can take advantage of these features. I am going to take the approach of not creating any custom workflow activities for this particular effort. Jim Lamb has a good discussion about when to make customizations to the MSBuild file (essentially the Visual Studio project file) and when to make your customizations in the Windows Workflow-based build process template. As much as I very much prefer customizing my build process templates using custom workflow activities, in this case I choose to do a little customization of both without using any custom workflow activities. I would much rather have done this using only native Windows Workflow activities but I’ll talk more about that a little later.
Disclaimer: As a Microsoft MVP, I have been a part of the Friends of Red Gate group for the last four years and I have been provided Not For Resale licenses of the Red Gate family of products though I reserve the right to offer unbiased opinions and criticisms. I was not paid for these contributions. However, I may or may not get a complimentary round the next time I see the Red Gaters at the pub in Cambridge. Works on My Machine Disclaimer: Everything in this blog article works on my machine when I wrote it. I have the latest version of SmartAssembly and TFS 2010 installed & configured correctly. I’ve done my best to make this as reusable as possible for most team’s scenarios but I can’t tell you that it will work for you. Hopefully it gets you started on the right path though! Please don’t contact me and let me know that my code killed your cat. I feel for you… I do – I just can’t do anything about it. You’ve been warned. I take the same approach that Scott does with blog contributions.
Disclaimer: As a Microsoft MVP, I have been a part of the Friends of Red Gate group for the last four years and I have been provided Not For Resale licenses of the Red Gate family of products though I reserve the right to offer unbiased opinions and criticisms. I was not paid for these contributions. However, I may or may not get a complimentary round the next time I see the Red Gaters at the pub in Cambridge.
Works on My Machine Disclaimer: Everything in this blog article works on my machine when I wrote it. I have the latest version of SmartAssembly and TFS 2010 installed & configured correctly. I’ve done my best to make this as reusable as possible for most team’s scenarios but I can’t tell you that it will work for you. Hopefully it gets you started on the right path though! Please don’t contact me and let me know that my code killed your cat. I feel for you… I do – I just can’t do anything about it. You’ve been warned. I take the same approach that Scott does with blog contributions.
SmartAssembly has actually been designed out of the box to handle the single-developer team scenario. If you are using TFS, you are likely not a single-developer team so you’ll want to a few things to get SmartAssembly setup for use with a team. The architecture for SmartAssembly can best be described with this architecture diagram:
Source: http://www.red-gate.com/products/dotnet-development/smartassembly/team-package
You’ll need to get the Professional edition of SmartAssembly since it allows you to store everything in a shared SQL Server database. One nice thing is that each developer who will need to interact with error & feature usage reports only needs a Developer edition license instead of a full Professional edition license. You’ll need to install & configure the Professional edition on each of your build servers. You might as well go ahead and create a build agent tag called “SmartAssembly” to indicate which build agents in your build farm are hosted on servers that have SmartAssembly installed.
When you first start SmartAssembly, you will want to setup the desktop machines & build servers to use the same SQL connection settings for the shared SmartAssembly database. I even like to use the friendly TFS DNS names that I already have setup for my particular TFS environment. Remember that if you are using the limited use license of SQL that is included with TFS, you won’t be able to house the SmartAssembly database on that instance. You’ll need to purchase a legitimate SQL Server license. It’s a great time to upgrade to the SQL Enterprise edition if you can for TFS! TFS will definitely take advantage of several of the features.
It is pretty easy to setup from there:
Be sure to also indicate that you want to use relative paths. Relative paths will be very important when you are using it in a team environment with Team Foundation Server.
BTW, if you need to setup SmartAssembly to use SQL Authentication instead of Windows Authentication, you can do that using this particular article. You do this by basically updating the settings configuration file available on a Windows 7 machine at C:\ProgramData\Red Gate\SmartAssembly\SmartAssembly.settings.
I am going to make this easy by just using a quick Windows Forms application however you are able to process any type of assembly including Silverlight apps, ASP.NET web applications, class libraries, etc. using SmartAssembly.
You will want to compile your assembly at least once and then start a new SmartAssembly project. It actually doesn’t matter where the source & destination location of the assembly is set to in the configuration but you might want to pick a location that all of the developers will be using. Don’t worry about the build server locations because we will override those later in the build process! To keep it simple, I’m only going to enable the following features in my SmartAssembly configuration file:
You can research more on the other options that are available but I am going to keep this walkthrough very simple. Once you are satisfied with your settings, click the “Save As…” button and save the configuration file in the same folder as your Visual Studio project file. I even like to include the file in my Visual Studio project so that I can work with it and check it into the version control repository along with the rest of my project. The SmartAssembly configuration file has a “.saproj” file extension.
The next thing you might want to do is open the configuration file using the XML Editor in Visual Studio to verify all of the settings look correct. You can use the “Open With…” context menu command from the Solution Explorer window to help you out.
The main thing you want to do is be very mindful of using relative file paths everywhere in the configuration file since the location of the source code location changes on the build server & developer machines. For example, TFS Build allows you to have multiple build agents running on any build server. I might have three build agents on a build server which means three builds could be running at any given time on the build server. You isolate each build agent on a build server by setting the working directory to something that will be a unique value. The default setting is $(SystemDrive)\Builds\$(BuildAgentId)\$(BuildDefinitionName) but I usually change it to $(SystemDrive)\Builds\$(BuildAgentId)\$(BuildDefinitionId) to give me a few extra characters since we also have path length limitations to go up against.
At this point, we are going to define a few custom MSBuild properties that we are going to use to trigger the SmartAssembly functionality. The table lists the properties I am going to define in this process.
For many of the common project types, Visual Studio project files are in fact actually MSBuild scripts under the covers. What we are going to do is add some custom functionality at the end of the project file that we will later “turn on” during the build process. You could modify this so that you could “turn on” the functionality at development time locally but this additional script excerpt will leave it turned off during normal development.
To edit a Visual Studio Project file, you can “unload” the project from the context menu in Solution Explorer and then double-click it to open it in a new editor document window. You will add the following excerpt close to the bottom of your Visual Studio project file just before the final </Project> ending tag. In my case it is a .csproj file.
<!-- Red Gate SmartAssembly Custom Post-Compile Processing for TFS Builds --> <UsingTask TaskName="SmartAssembly.MSBuild.Tasks.Build" AssemblyName="SmartAssembly.MSBuild.Tasks, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7f465a1c156d4d57" Condition="'$(TfsBuild)' == 'True' and '$(RunSmartAssembly)' == 'True'" /> <PropertyGroup Condition="'$(TfsBuild)' == 'True' and '$(RunSmartAssembly)' == 'True'"> <!-- Uncomment this next line if the configuration file is not located in the same directory and uses the same name as the project. --> <!--<SmartAssemblyConfigurationFileRelativePath>SmartAssemblyConfigurationFileName.saproj</SmartAssemblyConfigurationFileRelativePath>--> <!-- This will set the default name of the configuration file to the same name as the project name if the property is not defined elsewhere. --> <SmartAssemblyConfigurationFileRelativePath Condition="'$(SmartAssemblyConfigurationFileRelativePath)' == ''">$(ProjectName).saproj</SmartAssemblyConfigurationFileRelativePath> </PropertyGroup> <Target Name="AfterBuild" Condition="'$(TfsBuild)' == 'True' and '$(RunSmartAssembly)' == 'True'"> <!-- Archiving the original compiled assembly and matching debugging symbols file. --> <Message Text="Archiving the original compiled assembly and matching debugging symbols file." /> <Copy SourceFiles="@(_DebugSymbolsOutputPath)" DestinationFolder="$(OutDir)Original" Condition="'$(_DebugSymbolsProduced)' == 'true' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'" /> <Copy SourceFiles="@(MainAssembly)" DestinationFolder="$(OutDir)Original" Condition="'$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'" /> <!-- Process Assembly through SmartAssembly --> <SmartAssembly.MSBuild.Tasks.Build ProjectFile="$(SmartAssemblyConfigurationFileRelativePath)" Input="@(MainAssembly)" Output="@(MainAssembly)" OverwriteAssembly="True" /> </Target>
It is a modified version of the snippet from the SmartAssembly help documentation for integrating with MSBuild: http://www.red-gate.com/supportcenter/Content/SmartAssembly/help/6.5/SA_UsingSmartAssemblyWithMSBuild. You’ll see a little later where we are going to “turn on” the functionality by editing the TFS build process template. If you named your configuration file the same name as the project name and stored it in the same location in version control you actually don’t need to modify anything in the snippet at all.
Notice that the snippet keeps the original copies of the assemblies and matching symbols (.PDB) file so that they later get copied to the TFS build’s drop folder. It is copying the original assembly and matching symbols into another subdirectory named “Original” instead of just outputting the SmartAssembly instrumented assembly & matching symbols to a subfolder called “Obfuscated”, “Instrumented”, or “Protected.” I used to use the latter approach (as some people suggest) but if you are also compiling installers, it is useful to create an installer during specific builds that include the original assemblies instead of the instrumented ones. In my installer definition (like a WiX file) I’ll just refer to the regular location and it will pickup whatever version the build process created. If I want an installer to have the original assemblies then I just queue a new build and will set the SmartAssembly process parameter to false for that build. I don’t have to do anything additional in my WiX definition files to handle this scenario.
Another side effect you get by using this approach is that if your build process runs any automated tests, static code analysis, test impact analysis, etc., then it will use the instrumented versions of the assemblies as the target of the tests and other post-processing tools! There are several ways to skin this particular cat but I have fallen back to this approach after a few years of dealing with these issues.
Technically, we could just hard-code the extra MSBuild process parameters that we need using the default TFS build process template on the Process tab of the build definition editor window:
If you are okay with this approach then you don’t really need to go any further. However, we could make this a richer experience for people who will edit and queue these builds from day to day. This is where we can go through and create a custom process template.
The first thing you will want to do is create a new build process template to start your customizations. I have included mine for download at the end of this blog post but you may want to walk along. I usually start by creating a copy of the default build process template available from TFS. If you aren’t familiar with the basics of this particular process, I would highly suggest going through the walkthrough in either of these books:
You can then change your build definition over to the newly copied build process template using the following combo box.
If you click on the hyperlink, it will take you to the location in Source Control Explorer where you can get the latest version into your workspace and then open the build process template file for editing in the Windows Workflow Foundation Designer.
The first thing we can do is specify a new build process parameter that is exposed to the end user of the builds by going to the “Arguments” tab in the lower left-hand corner of the Workflow designer.
I am going to create a Boolean process parameter simply named “RunSmartAssembly” and set the default value to False. This isn’t an MSBuild property but a workflow process parameter that will be exposed to the end user when they are queuing a new build or when editing the build definition.
This next step is just to make things that much nicer. We can give the TFS Build system some additional metadata to make sure the parameter is exposed to the end user in a nice fashion. There are more details about the process parameter metadata field in either of the book chapters mentioned above in case you would like to learn more! You edit the collection information for the Metadata parameter that is already defined in the default build process template. (It’s two above the parameter we created in the previous screenshot.) Just click the ellipsis button in the default value field column to open up the metadata editor window.
Fill out the details as indicated above and save your build process template. You won’t see the changes immediately if you were to go back to the build definition editor because we haven’t checked-in the build process template back to the version control repository yet.
Whenever I architect a build that requires the use of a custom tool and it isn’t stored in version control (or even if it is but someone forgot to add that workspace mapping) I usually want to add a check in the build process to make sure that the tools are actually available to the build server. If the check doesn’t locate the tool I have it give a nice build error.
Add an If workflow activity inside the Build Agent Scope activity (labeled “Run on Agent”) but before the section that starts the compilation. It doesn’t exactly matter where as long as you get them in the agent scope but before any type of compilation begins. I am going to set my condition to something like the following:
RunSmartAssembly AndAlso Not System.IO.File.Exists(String.Format("{0}\{1}\{2}", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Red Gate\SmartAssembly 6", "SmartAssembly.exe"))
You can then add a Write Build Error activity with an appropriate message to indicate that SmartAssembly was not found. It should look something along the lines of this following example.
We can now work on passing in the additional MSBuild properties. I’m going to do this in two steps. The first step is to append the TfsBuild MSBuild property to the pre-defined workflow variable that is used for this purpose named MSBuildArguments. I’m going to do this immediately after the workflow activities we added for the previous step using another native primitive workflow activity: Assign. It’s a super simple activity that is great for this particular purpose. The assignment expression that I am going to use for the Value parameter is:
String.Format("{0} {1}", MSBuildArguments, " /p:TfsBuild=True")
After that, we will add another If activity where the conditional will be set to the RunSmartAssembly workflow parameter we created earlier. We will also add add another Assign activity and append our remaining MSBuild property to pass into the compilation process. You can use this assignment expression for the Value parameter of the Assign activity:
String.Format("{0} {1}", MSBuildArguments, " /p:RunSmartAssembly=True")
The final sequence looks similar to the following screenshot.
You may be asking “Why did we define the $(TfsBuild) MSBuild property when we could have just used the $(RunSmartAssembly) property?” That’s a great question… You don’t need it if you aren’t going to do any additional customization. However, in general, I like to always define the $(TfsBuild) MSBuild property so that you could customize the project files to modify the conditions based on whether it is occurring during a TFS Build or if it’s occurring on a developer’s machine. It’s quite handy when you need it.
Notice that we are also performing all of the SmartAssembly processing steps before the Source Server Indexing and Symbol Server Publishing phase of the build process so that both the original symbols and the symbols that match the instrumented assemblies are published correctly to Symbol Server and have the appropriate indexing for Source Server support included in those symbols. That will be extremely useful later whenever you need to debug against either the original or instrumented assemblies in the future. You can also open IntelliTrace log files & take advantage of Test Impact Analysis if you keep obfuscation turned off in the SmartAssembly configuration.
That’s it! Just save the changes to your build process template and check the file into the version control repository so it can now be used by your build definitions. Be sure to set your new custom workflow parameter to True and then queue a new build!
You’ll now notice that it runs correctly even if you have defined for your build process to compile multiple build configurations (i.e. Debug | x86, Release | AnyCPU, etc.)
If you are interested in downloading the completely customized version of the build process template, I have included a link to it below.
Download SmartAssembly Process Template
Take care,
Ed Blankenship
Remember Me
a@href@title, b, blockquote@cite, em, i, strike, strong, sub, sup, u