Question

Robocopy outputs 1 upon success, unlike most programs that exit with 0 on success. Visual Studio (and MSBUILD) interprets exit code of 1 as an error.

How can Robocopy be used in Visual Studio post- and pre-build events such that its failure and success are correctly identified by the build environment?

Note: this is more or less a repost of this post.

Was it helpful?

Solution

MSBuild extensionpack contains a Robocopy task that you can use in your build process.
Can this be a solution for you instead of VS pre/postbuild events?

If so, you can extend the Visual Studio Build Process by overriding the BeforeBuild, AfterBuild targets and calling the Robocopy task (you can override other targets as well if they would suit your needs better, see the list in the linked MSDN page)
So actually you should download and install MSBuild extensionpack than open your project's csproj/vbproj file and edit the following way:

Adding following entries for importing MSBuild extensionpack's Robocopy task

<PropertyGroup>
    <TPath>$(MSBuildExtensionsPath32)\ExtensionPack\4.0\MSBuild.ExtensionPack.tasks</TPath>        
</PropertyGroup>
<Import Project="$(TPath)"/>

Overriding BeforeBuild, AfterBuild and executing the Robocopy task

<Target Name="BeforeBuild">
<Message Text="Beforebuild" />
  <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="C:\temp\robo_src1" Destination="C:\temp\robo_dest1" Files="*.*" Options="/MIR">
      <Output TaskParameter="ExitCode" PropertyName="Exit" />
      <Output TaskParameter="ReturnCode" PropertyName="Return" />
  </MSBuild.ExtensionPack.FileSystem.RoboCopy>
  <Message Text="ExitCode = $(Exit)"/>
  <Message Text="ReturnCode = $(Return)"/>
</Target>
<Target Name="AfterBuild">
  <MSBuild.ExtensionPack.FileSystem.RoboCopy Source="C:\temp\robo_src2" Destination="C:\temp\robo_dest2" Files="*.*" Options="/MIR">
      <Output TaskParameter="ExitCode" PropertyName="Exit" />
      <Output TaskParameter="ReturnCode" PropertyName="Return" />
  </MSBuild.ExtensionPack.FileSystem.RoboCopy>
  <Message Text="ExitCode = $(Exit)"/>
  <Message Text="ReturnCode = $(Return)"/>
</Target>

OTHER TIPS

Adding this answer per request. Based on Asaf's solution, and adding skrebbel's comment.

You can simplify the check to:

robocopy <opt> <src> <tgt>
if %errorlevel% leq 1 exit 0 else exit %errorlevel%

As kindly remarked in the comments, you may want to adjust the '1': It depends on what your operation should treat as an error. Have a look at the meaning of the bits that in combination make up the number returned by robocopy:

0×10 Serious error. Robocopy did not copy any files. This is either a usage error or an error due to insufficient access privileges on the source or destination directories.

0×08 Some files or directories could not be copied (copy errors occurred and the retry limit was exceeded). Check these errors further.

0×04 Some Mismatched files or directories were detected. Examine the output log. Housekeeping is probably necessary.

0×02 Some Extra files or directories were detected. Examine the output log. Some housekeeping may be needed.

0×01 One or more files were copied successfully (that is, new files have arrived).

0×00 No errors occurred, and no copying was done. The source and destination directory trees are completely synchronized.

With <src>, <tgt> being the copy source and target respectfully, and <opt> being robocopy options:

robocopy <opt> <src> <tgt>
set rce=%errorlevel%
if not %rce%==1 exit %rce% else exit 0

For instance, if we want to copy the project target to c:\temp, without retries and with all sub-directories (empty or not), we'd use:

robocopy /R:0 /E $(TargetDir) c:\temp
set rce=%errorlevel%
if not %rce%==1 exit %rce% else exit 0

Simply checking for an exit code of 1 is incorrect, as any exit code below 8 is non-erroneous:

Any value greater than 8 indicates that there was at least one failure during the copy operation.

(Just to clarify, an exit code of 8 is an error as well: Several files did not copy)

The proper code, then, should look like this:

IF %ERRORLEVEL% GEQ 8 exit 1
exit 0

Syntactically here is a one-line-per-command version that works directly within the PreBuild steps:

(robocopy "$(ProjectDir)..\Dir1" "$(ProjectDir)Dir1" "Match.*" /a+:R) ^& IF %ERRORLEVEL% GEQ 8 exit 1
(robocopy "$(ProjectDir)..\Dir2" "$(ProjectDir)Dir2" "Match.*" /a+:R) ^& IF %ERRORLEVEL% GEQ 8 exit 1
exit 0

References:

I found that it's much easier to start robocopy rather than trying to call it in-line with Visual Studio. This way Visual Studio doesn't care about the return code from robocopy.

start robocopy . ..\latestbuild

The only difference I could see is that you will see a command prompt appear and disappear to execute the robocopy command.

Using call instead of start actually doesn't open the command prompt and, even better, redirects the output from the robocopy to Visual Studio output window.

call robocopy . ..\latestbuild

For some reason this approach only works when used in Pre-build events command line.

The accepted answer is overkill IMO. Robocopy already has its exit codes defined, so we can usually assume any value of 8 or less indicates things went well.

"Any value greater than 8 indicates that there was at least one failure during the copy operation."

So let's say your command is, ROBOCOPY $(Source) $(Dest) *.*, which I'll just refer to as $(RobocopyBinCommand).

In Visual Studio for your Pre-Build or Post-Build Event, click the dropdown and select <Edit...>

Create a new line below your command, and place IF %ERRORLEVEL% LEQ 8 EXIT 0 then apply and close the Properties window, eg:

example

Advanced Exit Code Requirements

Let's say you only want the build to pass if ROBOCOPY returns 1 or 3. The if-check above won't allow you to even use the OR-like behavior supported by CMD.exe to fix the issue. You can work around this limitation multiple ways but I think this is one of the most concise ways to do it.

if %errorlevel% LEQ 3 echo %errorlevel%|findstr "1 3"

One-Liner Explanation

Basically, we're piping the result of echoing the errorlevel to findstr which is looking for either a 1 or a 3. We don't have to worry about values that have a 3 or a 1 in them like 23 or 16 because the first evaluation makes sure the value is 3 or less. Once that evaluation passes if it does indeed pass it then pipes the errorlevel to findstr which then compares errorlevel to 1 or 3. If either is detected by findstr, findstr will exit 0, otherwise it will not. If the errorlevel was not 3 or less, errorlevel will remain unchanged and the build task will exit 1 as-usual from using ROBOCOPY.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top