Team Foundation Server 2015 Update 2 includes Release Management. However, I found myself in a position where a client hadn't upgraded yet, and during development I still wanted to publish ASP.NET MVC and WebApi projects to a test server. I did this through a combination of Build tasks running the msbuild publish command line, and Robocopy.
Also, while TFS Build includes steps for staging artifacts, an ASP.NET project doesn't simply move all files to a bin folder. Many files are taken directly from the project source folders, requiring me to use the MSBuild Publish command line switches.
The TFS Build Definition
In my Build, I'm going to do the following steps:
- Get the source code
- Get the NuGet packages
- Build and Test <= if anything fails up to this point, I don't want to publish
- Use msbuild to publish locally (to the Build agent workspace)
- Use Robocopy to copy the files to another computer running IIS
The first three steps are pretty standard, so I won't cover them. If you don't know how to set up a build, you'll need to read up on that. The last two are the fun ones.
Publishing ASP.NET from command line
In Visual Studio, it's easy (and nice) to publish an ASP.NET project to the file system. But what if you want to publish in an ad-hoc manner? It can be done, but the info is hard-won.
msbuild ProjectFile.csproj /p:Configuration=Release ^ /p:Platform=AnyCPU ^ /t:WebPublish ^ /p:WebPublishMethod=FileSystem ^ /p:DeleteExistingFiles=True ^ /p:publishUrl=c:\output Or if you are building the solution file: msbuild Solution.sln /p:Configuration=Release ^ /p:DeployOnBuild=True ^ /p:DeployDefaultTarget=WebPublish ^ /p:WebPublishMethod=FileSystem ^ /p:DeleteExistingFiles=True ^ /p:publishUrl=c:\output
In my Build definition, I add a Visual Studio Build step. (I could probably also use an MS Build step, but I haven't tested that.)
In the Solution field, enter the path to the web project you're going to build. The MSBuild Arguments are shown below. Note the variables used for both the build configuration and the destination folder. I'm publishing locally first.
/p:Configuration=$(BuildConfiguration) /p:DeployOnBuild=True /p:DeployDefaultTarget=WebPublish /p:WebPublishMethod=FileSystem /p:publishUrl="$(build.artifactstagingdirectory)\deploy\web"
Note If I was also publishing a WebApi project in the same definition, I'd have a separate build step, and the publishUrl might be
Copy the site to its destination using Robocopy
Why am I not using the publishUrl, above, to publish to the destination server? In many cases, I could, and that's fine. But what if I expect a destination file to be in use, and isn't normally updated? For example, I had a site using the NHunspell.dll. That file would often be in use, and wasn't changing. If I used publishUrl, the publish would always fail.
Microsoft's Robocopy ("Robust Copy"), included in all Windows editions, will skip and retry files it can't copy. This will still show as an error, but I'll set the step to "continue on error". It's not a perfect solution, but fine for many environments.
Add a Command Line step to the definition.
- Tool = robocopy.exe
- Arguments =
"$(build.artifactstagingdirectory)\deploy\web" "\\iis-server\wwwroot$\my-web-site" /e /r:2 /w:5
The Robocopy arguments say:
- Copy from the local deploy\web folder to the remote server folder
/e= Copies subdirectories. Note that this option includes empty directories.
/r:2= Retry busy files twice.
/w:5= Wait five seconds between retry attempts.
When the step runs, Robocopy outputs a nice report of what happened, very useful when viewing the build log.
While not ideal, the above is a useful workaround for publishing ASP.NET build artifacts to a remote machine. It also shows the flexibility that can be obtained through using other tools.