TFS 2012 Build “Access to Path Denied”

As others mentioned, this happens when performing multithreaded builds with a common destination directory and the file copy task happens to encounter a simultaneous conflict with a copy task running for a different project.

Normally this should result in a “file used by another process” exception (which is handled and retried by the file copy task) but sometimes the file operation results in an “Access is denied” exception instead. (I’m still not sure why)

Some suggest that you should “solve the duplication”, but I don’t see that as being feasible for cases where all the projects need to directly reference a library like log4net.

Obviously one way to prevent the issue is to explicitly run msbuild with /p:BuildInParallel=false or /m:1 or /maxcpucount:1 (or omit the argument entirely) to force single-threaded mode.

However, in TFS 2013, the default build template automatically always passes /m (use all cores) to msbuild, which silently overrides any single-thread setting you can manually pass in. (Determined by my own experimentation and examining diagnostic logs)

Another workaround I attempted was to manually pass /p:AllowedReferenceRelatedFileExtensions=none to msbuild, which prevents all pdb and xml files from being copied from referenced libraries. (Since for a while I only ever saw xml files having this issue.) But then I kept having problems with log4net.dll.

The ultimate workaround that I used was one I discovered by decompiling the source code for Microsoft.Build.Tasks.Copy:

if (hrForException == -2147024891)
{
    if (!Copy.alwaysRetryCopy)
        throw;
    else
        this.LogDiagnostic("Retrying on ERROR_ACCESS_DENIED because MSBUILDALWAYSRETRY = 1", new object[0]);
}

If error -2147024891 (0x80070005 access is denied) occurs, the Copy task will check a special variable to see if it should retry. That value is set via an environment variable:

Copy.alwaysRetryCopy = Environment.GetEnvironmentVariable("MSBUILDALWAYSRETRY") != null;

After setting the environment variable MSBUILDALWAYSRETRY = 1 (and restarting the build server), the problem went away. And I also periodically started seeing “Retrying on ERROR_ACCESS_DENIED…” as warnings in the build logs, proving that the setting was taking effect, (instead of the builds merely coincidentally succeeding).

(Note that this environment variable is not well documented, use as appropriate.)

Update: Apparently TFS 2015 no longer overrides your /m:1 with /m (even on legacy/XAML build definitions), which should make /m:1 a valid fix again.

Leave a Comment