17

我们在 Visual Studio 2010 中有一个项目,它在构建后事件中运行批处理文件。该批处理从 Microsoft SDK 调用 signtool.exe 以对二进制文件进行签名和时间戳记。

然而,时间戳服务器(我们使用http://timestamp.verisign.com/scripts/timstamp.dll )由于某种原因往往不可靠,有时会失败。这导致构建失败。

然后我们实现了一个更高级的批处理脚本(基于代码),拆分签名和时间戳,并允许在失败时重试时间戳操作。

这是批处理脚本 (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

构建后的事件代码如下所示:

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

因此,如果时间戳失败,它会以 2 秒的间隔重试 10 次。

但是,我们观察到的是,如果时间戳从第一次尝试开始就很好,那么一切都很好。但是,如果第一次尝试失败,那么整个构建后事件将失败,代码为 -1,即使在下一次尝试时时间戳成功。

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 ==========

因此,如您所见,即使从 signfile.bat 返回的错误代码为 0,Visual Studio 仍认为它是 -1 并导致事件失败。

所有清除错误标志的尝试,比如ver>nul在这里和那里添加,或者exit 0在最后添加(当然是在signfile.bat之前添加“call”)都没有帮助,因为看起来Visual Studio不仅检查了错误级别,还检查了一些东西别的。实际上,批处理和 signfile.bat 仅在出错时返回 0 或 1,而不是 -1。如果 signtool.exe 一次返回错误,似乎没有办法说服 Visual Studio 不要让构建后事件失败。

4

2 回答 2

27

在花了很多时间试验和搜索之后,找到了一篇文章,在评论中提到了这里。看起来 Visual Studio 扫描输出,搜索一些特殊关键字。Signtool.exe 输出EXEC : SignTool error : An error occurred,这似乎足以提醒 Visual Studio 存在错误。

因此,提出的解决方案是将输出和错误流重定向到 nul as 2>nul 1>nul。仍然会设置错误级别,因此您将能够确定是否发生错误。但是您可能需要打印一些额外的消息才能查看状态:

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

现在 Visual Studio 很高兴:

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 ==========

事实上,只需添加2>nul就足以修复它。仍然会打印错误流:Number of errors: 1,但不会导致问题。

于 2013-10-01T18:53:48.300 回答
2

我遇到了一个非常相似的问题,但增加的困难是,我们的后期构建步骤不是直接调用 SignTool,而是调用本身调用 SignTool 的 PowerShell 脚本。我在 PowerShell 脚本中设置了一些重试逻辑(尝试各种时间戳服务器,有延迟),但发现如果第一次尝试失败,那么整个构建将失败,即使错误被正确捕获和处理以及随后的签名尝试成功了。

我很欣赏这并不是最初的问题所要问的,但我在最终制定解决方案之前浪费了数小时和数小时(偶然发现了这个问答 - 谢谢!),所以我希望发布此信息它会帮助别人。

在 PowerShell 中,对 SignTool 的标准调用是这样的:

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

如果 SignTool 返回错误代码,这将立即使构建失败(如 TeamCity 屏幕截图所示),如果脚本能够自动重试,这是不可取的:

TeamCity 屏幕截图 - 失败

为了防止构建失败,可以使用 PowerShell 重定向语句抑制 PowerShell 错误流(类似于已接受答案中的解决方案),2>&1如下所示:

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

但是,这意味着当 SignTool 返回错误代码时,您看不到错误输出,这显然是没有帮助的。您可以通过将输出写入主机来解决此问题,如下所示:

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

$output | Write-Host

但随后 Visual Studio / MSBuild 将继续尝试“聪明”并再次失败构建!

我发现的唯一允许在不失败构建的情况下显示 SignTool 错误消息的解决方案是写入主机之前重定向错误流混淆错误输出,如下所示:

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

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

然后错误消息是人类可读的,但不会使构建失败,如下所示:

TeamCity 截图 - 成功

当然,如果所有重试尝试都失败,那么您需要从 PowerShell 脚本返回适当的非零退出代码,这样构建才会失败。

再次感谢最初的问答,我希望关于 PowerShell 的这些额外信息对其他人有所帮助。

(PS:已接受答案中的链接之一已损坏,但引用的文章在此处存档:http://web.archive.org/web/20180729111947/http: //blog.robertromito.com/2010/08/忽略来自-visual-studio-post.html 的错误

于 2021-02-04T11:00:01.940 回答