Nasty things start to happen on Windows systems when you start having paths that are longer than 254 characters. When NTFS is the underlying filesystem you can have up to 32767 unicode characters in the path, however in parts of Windows (and therefore .NET), a local file path (i.e. "C:\MyDir\MyFile.txt") is limited to 260 characters. Some older applications use 255 bytes for the path, while .NET's wrappers over the native file system API's seem to make the issue worse rather than better.
The problem is that this limitation is pretty easily bumped up against when performing a build as your build itself will use up a lot of those characters, and so if you start your build deep in a directory structure (i.e. using the default build working directory of "C:\Documents and Settings\username\Local Settings\Temp\Build Definition Name") then you quickly run out of room. Aaron Hallberg has a great post talking about this in relation to build working directories - however at a recent customer, I ran into a similar problem because their build drop locations were quite long.
Deep Drop Locations
The CoreDropBuild target in Team Build is responsible for copying files from the $(BinariesRoot) folder to the Drop Location. The Drop Location *must* be specified as a UNC path to a network share. CoreDropBuild, the target in Team Build that performs the copying, uses the standard MSBuild Copy task which is implemented in .NET.
Some folks (especially large organizations with multiple domains) use fully qualified domain names for all their shares. Also, they have a corporate standard directory structure. Therefore they end up needing drop locations to be something like "\\servername.domain.company.com\Project\SubProject\Build Results\". Now, UNC paths should not present a problem to us - the 260 character limit in a local path thing, UNC style paths should be able to go up to 32,767 characters. However, the .NET API for file paths forces a 260 character limit even if the path is a UNC style path - if you try to copy files using the MSBuild Copy task into a deep drop location you will get the error:
"The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters."
Therefore to copy these files, we need to get out of .NET - fortunately MSBuild allows us to easily call other processes using the Exec task. At first, I considered using the venerable xcopy. Xcopy has been around and since the early days of OS/2 and network aware MS DOS/Windows it has been able to copy to network shares using UNC style paths. The only issue is that XCopy doesn't work well with a path greater than 254 characters. If you try copying a folder to a path that takes you longer than that limit you get the following exception when you hit the long file:
Insufficient memory
Robocopy to the Rescue
In Windows Vista, XCopy has been depreciated in favor of Robocopy. I have a somewhat one-sided love / hate relationship with Robocopy. It can do nearly anything you would ever want to do with files (it is a bit like rsync for Windows platforms) - but the syntax for it is pretty painful to use. It also does strange things with the error codes that it returns. That said it now ships by default with Vista and Windows Server 2008 - and it is also available as part of the Windows Server 2003 resource kit. More importantly, it can handle UNC paths longer than the 260 character barrier. Therefore we can override the default CoreDropBuild target that Team Build provides, with one that uses Robocopy as shown below:
<Target Name="CoreDropBuild">
<Exec Command="robocopy "$(BinariesRoot)" "$(DropLocation)\$(BuildNumber)" /E /NP" IgnoreExitCode="true">
<Output TaskParameter="ExitCode" PropertyName="RobocopyExitCode" />
</Exec></Target>
Simply copy/paste that target into your TFSBuild.proj and hopefully you can have a long UNC path for your drop location that works. That said - bear in mind that their are other parts of TFS that the long path could cause problems with. For example, if the DropLocation path itself was getting really close to the 260 character limit then you will get problems accessing <DropLocation>\BuildLog.txt and the test results which get put in <DropLocation>\TestResults\. That said - for most people this should not be a problem and so the CoreDropBuild override method described above is the way to go.
The MAX_PATH limitation is not a limitation of NTFS. Rather it is a limitation of the .NET Framework I/O wrappers. Many APIs however adopt this limitation. The Windows Shell API is a good example. But it is not a limitation in NTFS or of the native File Management API which can support over 32 thousand Unicode characters in local file paths.
Kenny,
Very true, I guess I should not say that it is a file system limitation at all. I kinda knew this which is why I talk about local paths rather than talking about the \\?\C:\ way of working.
It was my understanding that the native Windows shell support 3 types of path:
Taking the Win32 GetFullPathName function for example, it has two versions - the ANSI one (GetFullPathNameA) and the Unicode one (GetFullPathNameW).
Are we saying that the fault in .NET is that it is calling the ANSI versions of the Win32 classes rather than the Unicode variants? If you use a local path (C:\) with the unicode variants are you still limited to MAX_PATH (260 characters) and would need to use the Long UNC style to use up to 32k?
Anyway - it is clear that this is not an underlying file system issue but rather an API one. It is also clear that .NET makes the underlying Win32 issues even worse rather than the same or better?
I know that it is probably too late for .NET 4.0 but I would love to file a bug in Connect around this issue and be as precise about the issue as possible to stand a chance of getting the issue fixed in the BCL of a later version of .NET.
Thanks for the correction - I will re-word the introduction to this post.
This is a simple work around to a very annoying problem.