Событие 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 )