In a perfect world, we would all be working on high quality greenfield projects using all the latest technologies and development practices. But often (in my experience at least) we are called on to support applications that have grown uncontrollably for many years, carrying enough technical debt to make Greece blush.

But even these monstrosities can benefit from modern ALM practices such as automated build and deployment. So in this post I’m going to talk about automated deployment of a real-life legacy web application using scripting, Psexec and other command-line tools – all coordinated by TFS 2010.

The Scenario

The application had been developed chaotically by many people over a decade or more using every Microsoft technology you could name: C++, VB, VBScript, VBA, C#, .NET 1.1, .NET 2.0, ASP, ASP.NET… you name it, it was in there somewhere. There had never been a rewrite, re-architecting or even any hints of refactoring. Yet development on this key application continued, and while there was an appreciation that things needed to improve there was certainly no appetite for any major rework.

The Challenge

The goal was to improve quality and productivity, and reduce risk. I know it’s a cliché, but the way to eat an elephant is one bite at a time, and that is how I approached this problem. The steps below were taken to tame this beast, tentatively, over several months while development continued relatively uninterrupted:

  1. Migrated source control from Visual SourceSafe to TFS 2010 (there is plenty of documentation on this, just make sure you practice before doing the real migration).
  2. Productised the application – defined the elements that it is (and isn’t) made of, and got them all organised in source control to establish a “single source of truth”.
  3. Migrated issue tracking from a proprietary system to TFS 2010 Work Items so changes going forward are transparently tracked.
  4. Got the application to compile with a minimal (but fully documented) set of components required to be installed. Later work cleaned up referenced dependencies, which were scattered and sometimes in conflict.
  5. Introduced TFS automated builds, starting with continuous integration.

So far, so good. By this point, we knew what we were dealing with and were building it on every check-in. There were even unit tests – which were patchy in coverage and quality, but much better than nothing.

The next step was to get this caged monster deploying to a test environment, fully automated, starting from a standard base configuration. The existing  process was documented as manual steps over dozens of pages, and it still wouldn’t work first time – days would be lost “setting it up” after following the install procedure. If you can’t easily deploy your application onto fresh hardware, then something is seriously wrong.

The benefits of a clean and endlessly repeatable deployment process are many, but getting there wouldn’t be easy.

WebDeploy

An early decision was made to restrict the deployment to a collection of ASP.NET web applications, as this was the more modern part of the app, and under constant development. The latest and greatest technology for this is the unimaginatively named Web Deployment Tool, also known as WebDeploy and MSDeploy, which is built on top of MSBuild – Microsoft’s core compilation technology.

We spent a good deal of time working with WebDeploy, but it turned into a battle. The constraints of old application and OS technology, and multiple web applications were a big headache and the pretty Visual Studio tooling soon fell short, leaving us working directly with MSBuild files.

Personally, I like the change of direction in TFS 2010 to restrict MSBuild to compilation, and use Windows Workflow for build “orchestration”. Once you go beyond compilation and directly related activities, changing XML-based MSBuild scripts feels awkward compared to the intuitive free-flow of WF (the abbreviation is not WWF – I think even Microsoft is scared of the World Wide Fund for Nature). So we ditched WebDeploy and decided to roll our own TFS Build and scripting solution.

The Solution

I came across many issues as I put our deployment solution together, so I will set the whole process out here and pick out the problems.

When to Deploy

The default TFS build process template is necessarily complex, and I intuitively put an Invoke Process activity to start the deployment after the steps which compile and test the solution. However, our solution had functional tests (calling high-level methods which use web services and the database) so you would get test failures because the tests relied on the deployment itself. So I simply moved the deployment step before the tests, and ensured any parts which the tests depended on would run synchronously so they finished before the tests started (more on this later).

There is a potential downside to this, which is that the compile and test steps are within a for-each loop for each solution and configuration combination. If you compile more than one solution and/or configuration in your build definition (which I personally don’t like to do) it will get messy. The deployment would run multiple times, but if you added a condition of some sort to ensure it runs only after the last compilation, it might be too late for any tests which depend on it. I didn’t need to deal with this issue, but controlling the order of compilation and breaking up the deployment seems like a solution. Another option would be to have multiple builds, each with a single solution and configuration, and have one build trigger the next.

Passing Parameters

It is very useful to pass information from the build definition and build system through to the deployment scripts. To achieve this I created a handful of new arguments in a new “Deployment” category, including:

  • An on/off toggle
  • Email addresses to notify of build completion
  • Processes to invoke to do the deployment
  • Text about the deployment to display in the build log

The processes argument could include placeholders in curly braces, which the build template was customised to recognise and replace appropriately. For example, placeholders of {BUILDNUMBER} and {BUILDID} would be substituted by the actual build number and numeric build ID respectively. The scripts could use this information to stamp the website with a build number and send an email with a link to the TFS Web Access version of the build log. (Incidentally, it’s an annoyance for a pedant like me that the build number is not a number, that’s the build ID – am I alone in being bothered by that?)

.

Strategy

I settled on a fairly simple process that worked for the first application and all those it was extended to. It used a file share on the target server as a working folder, and went like this:

  1. Clear the file share on the target server
  2. Copy the scripts and any other files to the file share on the target server
  3. Repeat steps 1 and 2 if there are multiple servers in the deployment
  4. Stop application websites and start the maintenance website on the remote server(s). I used Psexec for this initially but found it unreliable (more on that later) so switched to iisweb with much better results
  5. Package the application into one or more zip files.
    1. I used the 7-Zip command line tool 7za.exe, and a combination of copy and xcopy. Temporarily I used the incredibly flexible robocopy to deal with the horrible consequences of referencing different versions of the same assembly from different projects in the same solution – the application was fixed properly later.
    2. Any web.config or app.config transforms will need to take place before this step. I posted previously about how to trigger this without a full publish action.
  6. Copy the zip package(s) to the target server(s)
  7. Run the deployment actions in an inside-out sequence. So in a three-tier architecture: database, then the application layer, then the UI layer.
    • I used Psexec to asynchronously hand over the deployment to each target server. This takes the load off the build server and means the deployment runs as a process local to the server, which can be helpful for security and performance reasons.
    • If you want the build server to wait for the some or all of the deployment to finish, either run the deployment remotely from the build server or use a signal such as a “semaphore file” to tell the build server when the process is complete (see the ALM Rangers build guidance for more on that).
    • If using 7-Zip to extract zip files use the -y switch to avoid prompts hanging the process.
  8. When the deployment actions were complete, the final server in the deployment sequence was scripted to:
    1. Stop the maintenance website and start the application’s website
    2. Copy all the generated logs into the build drop folder
    3. Send an email notification of the deployment

Preparatory steps and signposting of deployment steps were output into the build log itself, but process calls with extensive output were set to use log files instead. The build log doesn’t lend itself well to very verbose logging.

Psexec

This is a command line tool from Sysinternals which performs the magic of triggering a process on a remote machine. It is very temperamental about how it is used but not unreliable once you find a stable usage pattern. Here are the lessons I learned:

  • Admin shares (\\server\C$ style file shares) need to be enabled on the remote machine.
    • This requires some one-time config changes if the server has been hardened against it (which should only be done on an internal network with appropriate approval). I needed to set Server and Computer Browser services to run automatically, set registry key HKLM\SYSTEM\CurrentControlSet\Services\lanmanserver\parameters to 1, and kill/restart the explorer.exe process.
  • The full path to Psexec is needed for it to be found by the build agent (even if it is under the PATH environment variable)
  • cmd /c is necessary to run Psexec from an InvokeProcess build activity as it is a console command, not a standalone executable
  • Psexec returns its own output as StdErr, causing the build log to contain an error for every line of output – and failing the whole build. You can use output handler 2>&1 to push StdErr into StdOut.
  • Psexec likes to output empty lines but you can strip them out within (or before) a WriteBuildMessage activity
  • For reliability I found I needed switches -d and -accepteula to run non-interactively and accept the embedded license agreement (the latter is common to Sysinternals tools).
  • I used Psexec only once per server, which seemed necessary to avoid overlapping processes (I don’t know why that should have been a problem, but it caused poor reliability). Having this constraint actually helped ensure the deployment process was well structured.
  • If you don’t specify a user account, Psexec uses impersonation which may result in the remote machine lacking access to network resources. This can be avoided by explicitly passing the username and password.
  • To pass the output of a remotely executed command to a log requires a middle-man script on the remote server. If you try to log on the calling server, you will get the output of Psexec itself, not the command being called.
Shared Deployment Scripts

This deployment strategy was later reused for other applications. To avoid duplication of componentised scripts and other shared items, I moved them to their own source folder and included them in the workspace of the build definition. You can’t map two source folders into the same working folder, so you have to figure out the correct relative paths, but it’s a small price to pay for the elimination of duplication (it’s evil!)

Summary

I will readily admit that this deployment approach is a little archaic. It’s also littered with gotchas, but new technology is not immune from that either.

Going forward, WebDeploy is still my first choice whenever possible. But if faced again with a trade-off between extensive MSBuild scripting and relatively transparent TFS Build + Windows scripting, I would still go for the latter.

 

Part I: Why Branching Doesn’t Work
Part II: Labelling, Branching’s Poor Cousin
Part III: Apps Without Source Integration

In part one of this series I gave a detailed definition of what (in my opinion) constitutes a config file. There are grey areas, so I tend to use heuristics instead of cut-and-dried principles. Here are my rules-of-thumb:

  • Config doesn’t get compiled into an application
  • Config files are data rather than executable code
  • Config is uploaded or copied over (in some way) to be made available to an application
  • Config may be preferable to code so that business rules or settings can be amended without an application release
  • Config files may be worked on by non-programmers: analysts, professionals and subject-matter experts
  • Configuration files might be edited in Visual Studio but also plain text editors, custom-built apps, or a vast range of third-party applications

The last point is the subject of this post – how to access TFS source control from applications with no TFS integration. There are a few options, some better than others – depending on the particular circumstances. I will set out all the ones I have used or considered.

TFS 2010 MSSCCI Provider

MSSCCI is Microsoft’s clunkily-named standard for source control integration. It originated in the dark days of Visual SourceSafe but grew and gained support from many third-party applications. TFS does not use MSSCCI as it’s native source control API, but there is a free provider available which you install (in addition to Visual Studio or Team Explorer) on each client machine where you want to use it. Third-party apps with MSSCCI support can then be configured to talk to the TFS provider, which in turn talks to TFS.

MSSCCI is functional (it basically works) but is at the mercy of client applications. For example, Microsoft Access relies on MSSCCI and the integration logic can be frustrating: it might decide to check-out every object it has under source control, which it does one by one – a painfully slow experience compared to the simple operation of checking out whole folders using Visual Studio. Problems like that can be eased by following one of the check-out strategies below.

SvnBridge

I explored this option but didn’t use it, so can only suggest it as a possibility. SvnBridge is an open-source tool which allows client applications to access TFS as if it were Subversion. Effectively, it gets TFS to emulate Subversion. TFS doesn’t know it is being accessed by a client application built to work against Subversion, and the client application doesn’t know it’s not accessing Subversion – SvnBridge handles all the translation. Subversion is a popular open-source source control server and there are third-party applications with support for it, but not for TFS (e.g. BusinessObjects LifeCycle Manager). If you use one of those apps, give SvnBridge a try and let me know if it works out for you.

Note that if you are using Eclipse, Team Explorer Everywhere is a better solution.

No Source Integration

If your editor doesn’t have a source control integration that will talk the language of TFS, MSSCCI or Subversion, you can still use TFS source control but it will require some experimentation to figure out the best way to do it, and potentially some application-switching during the work.

First, you will need a way to execute source control operations. You can use Visual Studio or Team Explorer, which is fine if you already use it for other work. If you don’t know Visual Studio/Team Explorer and have no other need for it, it might feel like a pretty heavyweight option when you just want to get to TFS source control. In that case, the Windows Shell Extension that comes with TFS Power Tools works well. An added bonus is that applications that use a standard Windows file dialog should allow files to be checked out using the dialog itself – removing the need to switch applications.

Second, you should consider defining a strategy for how you are going to check-out from source control. If you are used to Visual Studio you may well ask why a “strategy” is necessary. Well, Visual Studio has various options for checking out files when you start editing them, which generally means you can forget about checking-out and just get on with the work. Without that you are going to have to decide what to check out and when. Here are the main possibilities:

Check Out On Demand

This means you check out files just before you need to edit them. That should be fine if your editor has a simple file-editing design, but some applications modify files behind the scenes at times and for reasons you cannot easily predict. For check-out on demand to work, you need to be sure the editor will complain if files are read-only and give you an opportunity to check them out. If you can’t be sure of that, this strategy won’t work.

Check Everything Out

This means you check out all the files that could possibly be modified, do your work, then check them all back in. TFS should detect which files have changed and only include those in the resulting changeset, and undo the checkout for files which haven’t changed. The downside is that you have to checkout files you will not modify, which might confuse other users.

Work Offline

This is an interesting option to consider if you don’t want to check everything out. It makes using TFS somewhat similar to a distributed revision control system such as Git or Mercurial. Visual Studio/Team Explorer will offer to take a solution offline automatically if it cannot connect to TFS, and give a manual option to go back online. The GoOffline extension exposes an option to go offline manually.

That’s fine if you are using a Visual Studio solution, but that’s unlikely if VS isn’t your editor. In that case, the TFS Power Tools provide a useful command line tool TFPT which can be used to work offline and go back online cleanly. Here’s a suggested process:

  1. Use TFPT scorch to ensure there are no orphaned files in your workspace. This is necessary because when you go online later, add operations will be created for all files not under source control, replicating in source control any mess built under your local machine’s working folders.
  2. Remove the read-only flag from all files which might be modified.
  3. Modify the files as needed.
  4. Use TFPT online to turn the edits into pending changes, then check-in as normal.
  5. A final TFPT scorch /preview will check if there are any remaining differences between source control and the local file system.

Note: the complete TFPT syntax is not shown above. For example, you will normally want to include a restrictive filespec parameter to only include specific folders in the operations.

A downside to working offline is that file renames will be interpreted as a delete of the old file and an add of a completely unrelated file, so are best avoided.

Summary

Versioning config files can be a challenge but there is much to gain in traceability and risk reduction if you get it working smoothly. I hope these articles help you do that.

 

Part I: Why Branching Doesn’t Work
Part II: Labelling, Branching’s Poor Cousin
Part III: Apps Without Source Integration

In the first part of this series I explained why branching might not be the best strategy for versioning “configuration” files. These are items that control or influence an application but aren’t an integral (compiled) part of it, and often have a different development and release cycle. In this part I offer a solution and then, ahem, gripe about the tooling for it.

The solution is labelling – a completely flexible way of identifying specific versions of specific files. The concept is very similar to tagging photos on a photo-sharing site or posts on a blog. The label itself is a simple text string and can be anything you like. You can apply one or more label to any version of any files. Unlike changesets, which cannot be modified after creation, labels can always be updated. Unlike branches, which are driven by changesets, labels don’t push you into a rigid work flow or strategy. About the only thing you can’t do is apply the same label to more than one version of the same file, but I can’t imagine a realistic scenario where you would want to do that.

So that’s sounding nice and simple, right? Just label up the config files you want to release together, modify the labelled files if something changes or needs fixing, and when it’s all ready get the labelled files out of source control and deploy. Easy. Well, it would be if the Visual Studio support for labelling wasn’t so cursory. It seems like all the effort went into branching, and labelling was dismissed as the poor man’s configuration management – a clumsy oaf stamping random files with ill-named tags, compared to the sophisticated beard-stroking intellectual that is branching. Don’t get me wrong, branching is great, but not everybody is versioning branch-friendly application source code (and dependant artifacts) alone.

Here’s what’s not up to scratch in the Visual Studio 2010 support for labels:

  • The Source Control Explore context menu option Apply Label doesn’t apply an existing label, it creates a new one. The dialog that pops up is called New Label with a button named Create so it knows its own purpose, even though the menu item doesn’t. Understandably, people try to use this to apply an existing label to additional files. When they do (and don’t read the next dialog carefully) they wipe out the existing label completely and start from scratch with a new label. One mistake like that is enough to convince people labels are too risky to depend upon.
  • Apply Label only allows one file or folder to be selected. As Apply Label can only be used once per label (see above) why not let users label multiple files at the same time?
  • You can’t select files in source control and apply an existing label.
  • You can’t select a version of a file from the history and create a label or apply an existing label.
  • You can’t easily apply a label to every file in a changeset.
  • The label editor (the only way to modify a label) is hidden all the way under File –> Source Control –> Label –> Find Label. Yes, it’s also accessible from the label tab of a file’s history, but to get to a specific label that way you need to know a file that has it applied and navigate to it.
  • The label editor has a Change Labelled Version button but a more direct Update to Latest feature would be nice – much of the time when you change the labelled version that’s what you want to do.

There’s a lot that could be better, but despite it all the TFS labelling implementation is sound at it’s core, and despite its awkwardness the tooling is usable. If someone could crank out a VS extension that fills the GUI gaps it would be perfect. Watch this space…

Once you have your label correctly applied you will want to get the right versions of the right files so they can be deployed. You can simply Get the label from the root of a workspace to clear out other files and get the labelled files alone. You probably won’t want to use your normal workspace for that, as the operation may well interfere with normal work (and vice versa). You could manually create a second workspace for getting labels, but I find a batch script is cleaner:


@ECHO OFF
SET ProjectCollection=http://MyServer:8080/tfs/MyProjectCollection
SET TeamProject=MyTeamProject

REM Prompt for label name
SET Label=
SET /P Label=Enter a label to get from TFS:
IF "%Label%"=="" GOTO Exit

REM Setup workspace (fails non-destructively if it already exists)
C:
CD \
MD TFS
CD TFS
MD DeploymentWorkspace
CD DeploymentWorkspace
"%VS100COMNTOOLS%..\IDE\tf" workspace /new /collection:%ProjectCollection% DeploymentWorkspace /noprompt
CLS

ECHO Getting labelled files to C:\TFS\DeploymentWorkspace...
ECHO.
"%VS100COMNTOOLS%..\IDE\tf" get . /version:L"%Label%"@$/%TeamProject% /force /recursive

REM Check if label exists (error TF14064)
"%VS100COMNTOOLS%..\IDE\tf" dir . /version:L"%Label%"@$/%TeamProject% /recursive >LabelContents.txt 2>&1
SET /p LabelContents=<LabelContents.txt
DEL LabelContents.txt
SET LabelContents=%LabelContents:~0,7%
IF [%LabelContents%]==[TF14064] GOTO Exit

ECHO.
ECHO Copying to Desktop folder %Label%...
CD "%USERPROFILE%\Desktop"
MD "%Label%"
CD \TFS\DeploymentWorkspace
xcopy %TeamProject%\* "%USERPROFILE%\Desktop\%Label%\" /s
ECHO.

:Exit

pause

That’s it! The next part looks at the challenges of versioning config files that are not edited within Visual Studio.

© 2012 Andy Geldman Suffusion theme by Sayontan Sinha