Событие PostBuild завершается сбоем в Visual Studio после ошибки SignTool.exe

У нас есть проект в Visual Studio 2010, который запускает пакетный файл в событии после сборки. Этот пакет вызывает signtool.exe из Microsoft SDK для подписи и отметки времени двоичного файла.

Однако серверы меток времени (мы используем 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)"

Таким образом, если отметка времени не удалась, она повторяется 10 раз с интервалом в 2 секунды.

Но мы заметили, что если временная метка прошла хорошо с первой попытки, все было в порядке. Однако если первая попытка завершилась неудачно, то все событие после сборки завершилось неудачно с кодом -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 в конце концов (конечно, с добавлением "call" перед signfile.bat) это не помогло, так как казалось, что Visual Studio проверила не только уровень ошибок, но и что-то еще. На самом деле, пакет и signfile.bat возвращают только 0 или 1 в случае ошибки, но не -1. И если signtool.exe возвращает ошибку один раз, кажется, что нет никакого способа убедить Visual Studio не провалить событие после сборки.

2 ответа

Потратив много времени на эксперименты и поиски, нашел статью, упомянутую здесь в комментарии. Похоже, Visual Studio сканирует выходные данные в поисках некоторых специальных ключевых слов. Signtool.exe выводит среди других EXEC : SignTool error : An error occurredКажется, этого достаточно, чтобы предупредить Visual Studio об ошибке.

Таким образом, предлагаемое решение состояло в том, чтобы перенаправить потоки вывода и ошибок в nul как 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, но это не вызывает проблемы.

Я столкнулся с очень похожей проблемой, но с дополнительными трудностями, поскольку вместо прямого вызова SignTool наш шаг после сборки вызывает сценарий PowerShell, который сам вызывает SignTool. Я настроил некоторую логику повторных попыток в сценарии PowerShell (чтобы попробовать различные серверы меток времени с задержками), но обнаружил, что если первая попытка не удалась, то вся сборка завершится ошибкой, даже если ошибка была обнаружена и обработана должным образом, а последующая подпись попытка удалась.

Я понимаю, что это не совсем то, что задает первоначальный вопрос, но я потратил на это часы и часы, прежде чем в конечном итоге нашел решение (наткнувшись на этот вопрос и ответ - спасибо!), поэтому я публикую эту информацию в надежде что это поможет другим.

Стандартный вызов SignTool из PowerShell будет примерно таким:

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

Если SignTool вернет код ошибки, это немедленно приведет к сбою сборки (как показано на снимке экрана 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

Сообщение об ошибке становится понятным для человека, но не приводит к сбою сборки, как показано здесь:

Конечно, если все повторные попытки завершились неудачно, вам нужно вернуть соответствующий ненулевой код выхода из сценария PowerShell, чтобы сборка не удалась.

Еще раз спасибо за оригинальные вопросы и ответы, и я надеюсь, что эта дополнительная информация о PowerShell поможет кому-то еще.

(PS: одна из ссылок в принятом ответе не работает, но упомянутая статья заархивирована здесь: http://web.archive.org/web/20180729111947/http://blog.robertromito.com/2010/08/ игнорировать-ошибку-от-visual-studio-post.html )

Другие вопросы по тегам