The Robocopy exit code and MSBuild postbuild error problem

The recent blog post of my friend Jeroen Pluimers about "Robocopy exit codes 0 and 1 usually mean success" reminded me of a problem I had with this fact a few years ago.

Robocopy behaves strange...
Just to sum his blog post up again: Robocopy returns exit code 0 if it successfully had nothing to do (i.e. all target files were already up to date), and returns with exit code 1 if it successfully copied something. Values larger than 1 hint to a potential problem while values equal or greater 8 indicate failure. In fact, the return values are a bit mask and only values that have bits 4 or 5 set indicate a real error situation.

... and doesn't go well with MSBuild
Now, since exit codes other than 0 mean success and 0 is in fact pretty seldom, only indicating that nothing had to be done, robocopy is a very problematic thing to call in your MSBuild pre and post build targets.

MSBuild interprets the exit codes of the pre and post build calls and if something is not okay, the whole build process errors out. And 'not okay' for MSBuild is any other exit code other than zero. So, when robocopy tells the caller "Hey, I copied your files and was successful in doing that: 1!", MSBuild goes and reads: "1? Oh darn, something went wrong. I need to abort".

What to do about that?
To solve this problem, there are - like so often - many ways.

First of all, the not-so-elegant solution would be to wrap the call to robocopy into a custom batch file (.bat or .cmd). This in turn would call robocopy (just pass through all arguments one to one), examines the return code and if robocopy inidcates success (probably even with codes up to 7), calls 'exit 0' itself. In any other case it could write a better condition on the console and exit 1 to indicate an error. See this table (also mentioned by Jeroen) for the list of conditions. This is not-so-elegant because you won't get more detailed information about error conditions than what your script returns and it is not very easy to configure. The even worse solution would be, to hard code the files to copy in your batch file, so that whenever your project changes in what to copy, you need to update your file.

Then there is the question, if calling the external robocopy is required at all. You can do a lot with the normal copy task that ships with MSBuild. It allows you to copy multiple files to multiple locations, do hardlinks instead of copying, check if the destination is newer and don't copy then and a lot more. You also can control the copy task much more easier using simple item groups and properties in msbuild so that you simply could add files to your copy task by adding some meta information to your project files.

If you really, really need to rely on robocopy because you need it's even more advnced features, then there is a full-featured MSBuild task wrapping the robocopy calls in a MSBuild-ish way in the MSBuild extension pack.

As so often, a tasks that seem so easy brings up some unexpected problems and strange error messages. Even besides the three options mentioned above, there are of course a lot of other possible ways to achieve what you need to copy from your MSBuild project. This post is merely an explanation of the root cause of those problems and a hint on what you can do to avoid the problems arising from the strange error-code behaviour of robocopy.