PostBuild Event fails in Visual Studio after SignTool.exe error
Asked Answered
H

2

18

We have a project in Visual Studio 2010 that runs a batch file in the post-build event. That batch calls to signtool.exe from Microsoft SDK to sign and timestamp the binary.

Timestamp servers (we use http://timestamp.verisign.com/scripts/timstamp.dll), however, tend to be unreliable for some reason, failing sometimes. This caused build to fail.

We implemented a more advanced batch script then (based on this code), splitting signing and timestamping, and allowing to retry the timestamp operation, if it failed.

Here is a simplified version of the batch script (signfile.bat):

@echo off

REM sign the file...
signtool.exe /f Authenticode.pfx /p PASS %1

if %errorlevel% neq 0 exit /b %errorlevel%

set timestamp_server=http://timestamp.verisign.com/scripts/timstamp.dll

for /L %%a in (1,1,10) do (

    REM try to timestamp the file...
    signtool.exe timestamp /t %timestamp_server% %1

    if errorlevel 0 if not errorlevel 1 GOTO succeeded

    REM wait 2 seconds...
    ping -n 2 127.0.0.1 > nul
)

REM return an error code...
echo signfile.bat exit code is 1.
exit /b 1

:succeeded
REM return a successful code...
echo signfile.bat exit code is 0.
exit /b 0

And the post-build event code would look like:

signfile.bat "$(OutDir)$(TargetName)$(TargetExt)"

So, if the timestamping fails, it retries 10 times with 2-seconds intervals.

But, what we observed was, if the timestamping went fine from the first attempt, everything was OK. However, if the first attempt failed, then the whole post-build event failed with code -1, even though the timestamping succeeded on the next try.

1>------ Build started: Project: myproject, Configuration: NonOptimized x64 ------
1>  Done Adding Additional Store
1>  Successfully signed: E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll
1>  
1>EXEC : SignTool error : The specified timestamp server either could not be reached
1>  or returned an invalid response.
1>    This may happen if you specify an RFC 3161 timestamp URL but used
1>    the /t option or you specified a legacy Authenticode timestamp URL
1>    but used the /tr option.
1>EXEC : SignTool error : An error occurred while attempting to timestamp: E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll
1>  
1>  
1>  Number of errors: 1
1>  
1>  Successfully timestamped: E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll
1>  
1>  signfile.bat exit code is 0.
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppCommon.targets(113,5): error MSB3073: The command "signfile.bat "E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll"
1>C:\Program Files (x86)\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppCommon.targets(113,5): error MSB3073: :VCEnd" exited with code -1.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

So, as you can see, even though the error code returned from signfile.bat is 0, Visual Studio thinks it is -1 and fails the event.

All attempts to clear the error flag, like adding ver>nul here and there, or adding exit 0 in the end (certainly with adding "call" before signfile.bat) didn't help since it seemed like Visual Studio checked not just for errorlevel but also for something else. In fact, the batch as well as signfile.bat only return 0 or 1 in case of error, but not -1. And if signtool.exe returns an error once, it seems like there is no way to convince Visual Studio not to fail the post-build event.

Harleyharli answered 1/10, 2013 at 18:53 Comment(0)
H
28

After spending much time experimenting and searching, found an article, mentioned here in a comment. It looks like Visual Studio scans the output, searching for some special keywords. Signtool.exe outputs among the other EXEC : SignTool error : An error occurred, which seems like enough to alert Visual Studio that there was an error.

So, the solution proposed was to redirect output and error streams to nul as 2>nul 1>nul. Errorlevel will still be set, so you will be able to figure out if error occured. But you may have to print some extra messages to see the status:

REM try to timestamp the file...
signtool.exe timestamp /t %timestamp_server% %1 2>nul 1>nul

if errorlevel 0 if not errorlevel 1 (
    echo Successfully timestamped: %1
    GOTO succeeded
)
echo Timestamping failed for %1

Now Visual Studio is happy:

1>------ Build started: Project: myproject, Configuration: NonOptimized x64 ------
1>  Done Adding Additional Store
1>  Successfully signed: E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll
1>  
1>  Timestamping failed for "E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll"
1>  Successfully timestamped: "E:\tfs\MySolution\bin\x64\NonOptimized\myproject.dll"
1>  signfile.bat exit code is 0.
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

In fact, just adding 2>nul would be enough to fix it. Error stream will still be printed: Number of errors: 1, but it does not cause a problem.

Harleyharli answered 1/10, 2013 at 18:53 Comment(1)
That happens when the software tries to be too smart for its own good. Thanks for this solution.Cattail
S
2

I experienced a very similar problem, but with the added difficulty that rather than calling SignTool directly, our post-build step calls a PowerShell script which itself calls SignTool. I set up some retry logic in the PowerShell script (to try various timestamp servers, with delays), but found that if the first attempt failed then the whole build would fail, even though the error was being caught and handled properly and a subsequent signing attempt was succeeding.

I appreciate this isn't exactly what the original question is asking, but I wasted hours and hours on this before eventually working out a solution (having stumbled upon this Q&A - thank you!), so I'm posting this info in the hope that it will help others.

From PowerShell, the standard call to SignTool would be something like this:

$output = & $signToolPath sign `
    /n $companyName `
    /d $productName `
    /du $productWebsite `
    /t $timestampServer `
    /sha1 $shaHash `
    $filePath

If SignTool returns an error code, this will immediately fail the build (as shown in the TeamCity screenshot), which is not desirable if the script is capable of automatically retrying:

TeamCity screenshot - failure

To prevent the build failing, the PowerShell error stream can be suppressed (similarly to the solution in the accepted answer) with the PowerShell redirect statement 2>&1 as follows:

$output = & $signToolPath sign `
    <switches and args as above>
    $filePath 2>&1

However, this means you can't see the error output when SignTool returns an error code, which is obviously unhelpful. You can address this by writing the output to the host like this:

$output = & $signToolPath sign `
    <switches and args as above>
    $filePath 2>&1

$output | Write-Host

but then Visual Studio / MSBuild will resume trying to be "clever" and fail the build again!

The only solution I have found which allows SignTool error messages to be displayed without failing the build is both to redirect the error stream and to garble the error output before writing to the host, something like this:

$output = & $signToolPath sign `
    <switches and args as above>
    $filePath 2>&1

$output -split "([a-z0-9])" -join " " | Write-Host

The error message is then human-readable but doesn't fail the build, as shown here:

TeamCity screenshot - success

Of course, if all the retry attempts fail then you need to return an appropriate non-zero exit code from the PowerShell script so that the build does fail.

Thanks again for the original Q&A, and I hope this extra info on PowerShell helps someone else.

(PS: one of the links in the accepted answer is broken, but the referenced article is archived here: http://web.archive.org/web/20180729111947/http://blog.robertromito.com/2010/08/ignore-error-from-visual-studio-post.html )

Stoughton answered 4/2, 2021 at 11:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.