Error MSB3021 and Team Build

The error message "error MSB3021: Unable to copy file "<filename>" to "<output location>". Access to the path '<output location>' is denied" occurs when the MSBuild Copy task cannot overwrite an existing read-only file. 

Typically in Team Build this error will occur because (a) there seems to be an issue in MSBuild where the same file can get included more than once in the list of files to get copied, and (b) these files will typically be read-only in a Team Build since they are retrieved from version control (and not checked out).  The workarounds for the issue depend on the version of Team Build (and MSBuild) you are using.

Team Build 2005 / MSBuild 2.0

There are two approaches to fixing the issue in Team Build 2005 / MSBuild 2.0 - removing the duplicate entries from the items copied or removing the read-only bit from the files before they are copied. 

For the first possibility, two suggestions for fixing the involved targets (from Microsoft.Common.targets) can be found in an MSBuild forum thread here.  The trick in using these is that you'll need to either (a) modify Microsoft.Common.targets in place, or (b) override the relevant targets in each of your project (e.g. *.csproj) files.  Modifying Microsoft.Common.targets in place is not a great idea, since you'll need to do so on every build machine, your changes will get wiped out on upgrade, etc.  Overriding the targets in each project is annoying as well, since it can potentially require modification of lots of project files.

The second possibility, then, may be a bit easier.  For example, the following target override in your TfsBuild.proj file should work in most cases:

   <Target Name="AfterGet">
    <Exec Command="attrib -r *.* /S" WorkingDirectory="$(SolutionRoot)" />
  </Target>

This logic simply turns off the read-only bit on every file downloaded from version control.  If the only files you copy are *.png files, you can change the file mask for the attrib command to speed things up a bit, etc.  If you are doing an incremental get, you may need to reset the read-only bit at the end of the build or at the beginning of the next build - something like the following should do the trick:

   <Target Name="BeforeGet">
    <Exec Command="attrib +r *.* /S" WorkingDirectory="$(SolutionRoot)" />
  </Target>

Team Build 2008 / MSBuild 3.5

In MSBuild 3.5, an OverwriteReadOnlyFiles property was added that can be set to true to allow Copy tasks involved in the build process to overwrite read-only files in cases like the one outlined here.  As such, a third workaround is possible in Team Build 2008 / MSBuild 3.5.  Note that this workaround will only work for projects that use the 3.5 version of Microsoft.Common.targets - because of the multi-targeting feature available in MSBuild, this will not necessarily be every project built by Team Build 2008.   

To set the OverwriteReadOnlyFiles property to true globally, you can either:

  • Add the text "/p:OverwriteReadOnlyFiles=true" to TfsBuild.rsp for your build definition, or
  • Add the following property group to TfsBuild.proj for your build definition.
   <PropertyGroup>
    <CustomPropertiesForBuild>OverwriteReadOnlyFiles=true</CustomPropertiesForBuild>
  </PropertyGroup>