如何在不硬编码路径的情况下,在Visual Studio 2013的后期构建事件中使用signtool?

9
我已经创建了一个后构建事件,用以下后构建脚本对应用程序进行代码签名,在成功构建后执行。
copy $(TargetPath) $(TargetDir)SignedApp.exe
signtool sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe

我遇到了这个错误:'signtool' 不是内部或外部命令。 看起来构建事件使用的路径并没有指向 signtool 实用程序。当我运行 VS2013 x86 Native Tools Command Prompt 时,我可以运行 signtool,因为它包含指向以下路径的路径:
C:\Program Files (x86)\Windows Kits\8.1\bin\x86

我可以将这个路径硬编码到我的构建事件中

"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\signtool" sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe

但这似乎不太可移植。如何在不硬编码的情况下使用与本地命令提示符定义相同的路径来执行我的后期构建事件?我查看了宏列表,但没有找到有用的内容。


  1. 在您的代码库中包含signtool。
  2. 包含一个exe文件,通过定位signtool来进行签名,也许https://github.com/Microsoft/vswhere可以帮助您。
  3. 设置具有固定路径的构建服务器(这是我最终采取的措施)。
  4. 添加一个批处理文件,不在您的版本控制系统中,每个用户都必须编辑(理想情况下只需一次)。
- Dennis Kuypers
4个回答

16

我最初找到了这个问题,所以我会发布我最终选择的答案。

在此过程中,我查看了另一个答案和一些文档:

使用Visual Studio 2012时SignTool.exe或“Windows Kits”目录的路径

https://learn.microsoft.com/en-us/visualstudio/msbuild/property-functions?view=vs-2017

我的解决方案最终是将以下大型PropertyGroup添加到csproj文件中:

<PropertyGroup>
  <!-- Find Windows Kit path and then SignTool path for the post-build event -->
  <WindowsKitsRoot>$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot10', null, RegistryView.Registry32, RegistryView.Default))</WindowsKitsRoot>
  <WindowsKitsRoot Condition="'$(WindowsKitsRoot)' == ''">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot81', null, RegistryView.Registry32, RegistryView.Default))</WindowsKitsRoot>
  <WindowsKitsRoot Condition="'$(WindowsKitsRoot)' == ''">$([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows Kits\Installed Roots', 'KitsRoot', null, RegistryView.Registry32, RegistryView.Default))</WindowsKitsRoot>
  <SignToolPath Condition="'$(SignToolPath)' == '' And '$(Platform)' == 'AnyCPU' and Exists('$(WindowsKitsRoot)bin\x64\signtool.exe')">$(WindowsKitsRoot)bin\x64\</SignToolPath>
  <SignToolPath Condition="'$(SignToolPath)' == '' And Exists('$(WindowsKitsRoot)bin\$(Platform)\signtool.exe')">$(WindowsKitsRoot)bin\$(Platform)\</SignToolPath>
  <SignToolPathBin Condition="'$(SignToolPath)' == ''">$([System.IO.Directory]::GetDirectories('$(WindowsKitsRoot)bin',"10.0.*"))</SignToolPathBin>
  <SignToolPathLen Condition="'$(SignToolPathBin)' != ''">$(SignToolPathBin.Split(';').Length)</SignToolPathLen>
  <SignToolPathIndex Condition="'$(SignToolPathLen)' != ''">$([MSBuild]::Add(-1, $(SignToolPathLen)))</SignToolPathIndex>
  <SignToolPathBase Condition="'$(SignToolPathIndex)' != ''">$(SignToolPathBin.Split(';').GetValue($(SignToolPathIndex)))\</SignToolPathBase>
  <SignToolPath Condition="'$(SignToolPath)' == '' And '$(SignToolPathBase)' != '' And '$(Platform)' == 'AnyCPU'">$(SignToolPathBase)x64\</SignToolPath>
  <SignToolPath Condition="'$(SignToolPath)' == '' And '$(SignToolPathBase)' != ''">$(SignToolPathBase)$(Platform)\</SignToolPath>
</PropertyGroup>

我需要许多额外的中间属性,因为我的计算机上的Windows SDK没有将signtool.exe安装在<root>\bin\x64\signtool.exe下,而是安装在SDK版本的另一个目录级别下,我绝对不想硬编码它。

然后在构建后,我可以使用这个"$(SignToolPath)signtool.exe"


1
根据我多个小时的谷歌和 Stack Overflow 搜索,我认为这是正确的答案,不仅适用于 VS2013(我在评论时使用的是 VS2019)。感谢 @webjprgm。此外,我现在知道了 msbuild 属性函数,很酷的东西! - entiat
我同意@entiat的观点,这是解决方案,即使对于Visual Studio的后续版本也是如此(我仍在使用VS 2015和VS 2019)。谢谢。 - DrMarbuse

4
我决定采用的解决方案是:
REM If SIGNTOOL environment variable is not set then try setting it to a known location
if "%SIGNTOOL%"=="" set SIGNTOOL=%ProgramFiles(x86)%\Windows Kits\8.1\bin\x86\signtool.exe
REM Check to see if the signtool utility is missing
if exist "%SIGNTOOL%" goto OK1
    REM Give error that SIGNTOOL environment variable needs to be set
    echo "Must set environment variable SIGNTOOL to full path for signtool.exe code signing utility"
    echo Location is of the form "C:\Program Files (x86)\Windows Kits\8.1\x86\bin\signtool.exe"
    exit -1
:OK1
echo Copying $(TargetFileName) to $(TargetDir)SignedApp.exe
copy $(TargetPath) $(TargetDir)SignedApp.exe
"%SIGNTOOL%" sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe

这是对@Dennis Kuypers建议#4的变化。开发人员必须将环境变量SIGNTOOL设置为正确的位置。如果他们未能这样做,则会尝试一个已知的可能位置。如果失败,则报告错误指示他们适当地设置SIGNTOOL env变量。
我发现有一个环境变量WindowsSdkDir
WindowsSdkDir=C:\Program Files (x86)\Windows Kits\8.1\

但是需要注意的是,这个设置只在运行本机命令提示符时生效,因此在运行后构建事件脚本时未定义。


4
我今天看了很多不同年代的帖子,大部分问题是它们在几年后就无法持续工作,或者涉及大量注册表读取,随后是大量硬编码路径。
最终我找到了一个(我认为)可以持续一段时间的解决方案。至少从2019年开始,直到有什么东西出了问题。
我将我的“Post Build”事件设置为以下内容:
call "$(VSAPPIDDIR)..\Tools\VsDevCmd.bat"
signtool.exe sign /p <whatever> /f <whatever>.pfx "$(TargetFileName)"

第一行代码会设置所有的环境变量,就好像你在开发者命令提示符中一样。第二行代码使用该环境进行签名。

我看到这确实可以工作,但是在我的VS2019版本16.11.5中,在编辑后生成事件时,我没有看到可用的宏列表中列出$(VSAPPIDDIR)宏。你是如何找到$(VSAPPIDDIR)宏的?在哪里找到的? - JonN
1
有两种方法 - 首先,我执行了一个后构建事件,其中只包含SET命令,以便我可以查看环境中定义了什么(该命令被传播到shell)。然后,我搜索了VS宏,并找到了一个列出它的网站。 - user1664043

1

我在Visual Studio 2012中遇到了同样的问题,并找到了一个更简单的解决方法。不要直接启动Visual Studio,而是启动“VS2012开发人员命令提示符”,然后在命令提示符中键入“devenv”以启动Visual Studio。之后,signtool对我来说就可以正常工作了。


2
我看到这个解决方案存在的问题是,如果另一个开发人员(也许是多年以后)来构建项目,它会失败而没有任何指示他们做错了什么。这需要一种非常规的启动 VS 的方法,并要求开发人员知道始终以这种方式启动 VS。我认为我更喜欢我的解决方案,因为它可以向开发人员提供有关当事情不起作用时该怎么做的反馈,一旦设置了环境变量,VS 可以按照常规方式启动。 - JonN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接