如何在 post 构建事件中使用 signtool for Visual Studio 2013 而无需硬编码路径?

How do I use signtool in post build event for Visual Studio 2013 without hard coding path?

我创建了一个 post 构建事件,以便在使用以下 post 构建脚本成功构建后对应用程序进行代码签名。

copy $(TargetPath) $(TargetDir)SignedApp.exe
signtool sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe

我收到错误 'signtool' 未被识别为内部或外部命令。 因此,用于构建事件的路径似乎没有指向 signtool 实用程序。当我 运行 VS2013 x86 本机工具命令提示符 我可以 运行 signtool 因为它包含一个指向的路径:

C:\Program Files (x86)\Windows Kits.1\bin\x86

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

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

然而,这似乎不可移植。如何获得为 本地命令提示符 定义的相同路径以供我的 post 构建事件使用,而无需对其进行硬编码?我查看了宏列表,但没有找到任何有用的。

我决定的解决方案是:

REM If SIGNTOOL environment variable is not set then try setting it to a known location
if "%SIGNTOOL%"=="" set SIGNTOOL=%ProgramFiles(x86)%\Windows Kits.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.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 var。

我确实发现有一个环境变量 WindowsSdkDir

WindowsSdkDir=C:\Program Files (x86)\Windows Kits.1\

但同样,这仅在 运行 本机命令提示符 时设置,因此在 运行 post 构建时未定义事件脚本。

我在 Visual Studio 2012 年遇到了同样的问题,并找到了更简单的方法来解决这个问题。而不是启动Visual Studio直接启动"Developer Command Prompt for VS2012"然后在命令提示符中输入'devenv'启动Visual Studio。在那个 signtool 对我来说很好用之后。

我先发现了这个问题,所以我会 post 我最终得到的答案。

一路上我查看了这个其他答案和一些文档:

Path to SignTool.exe or "Windows Kits" directory when using Visual Studio 2012

https://docs.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 没有在 <root>\bin\x64\signtool.exe 中安装 signtool.exe,而是在另一个目录级别下,即我绝对不想硬编码的 SDK。

然后在 post-build 中我可以使用这个 "$(SignToolPath)signtool.exe"

我今天看了很多不同年份的帖子,其中大多数的问题是它们在几年后没有持续工作,或者它们涉及大量注册表读取接下来是很多条件硬编码路径。

我终于找到了(我认为)可以用一段时间的东西。至少在 2019 年出现问题之前。

我让我的 Post Build 活动达到

的效果
call "$(VSAPPIDDIR)..\Tools\VsDevCmd.bat"
signtool.exe sign /p <whatever> /f <whatever>.pfx "$(TargetFileName)"

第一行设置了所有环境变量,就像您在开发人员命令提示符中一样。第二个使用该环境进行签名