I've created a post build event to do code signing of the application after a successful build with the following post build script.
copy $(TargetPath) $(TargetDir)SignedApp.exe
signtool sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe
I get the error 'signtool' is not recognized as an internal or external command.
So it seems the path used for the build event doesn't point to the signtool utility. When I run the VS2013 x86 Native Tools Command Prompt I can run signtool as it includes a path which points to:
C:\Program Files (x86)\Windows Kits\8.1\bin\x86
I could hard-code this path into my build event
"C:\Program Files (x86)\Windows Kits\8.1\bin\x86\signtool" sign /t http://timestamp.verisign.com/scripts/timestamp.dll /a $(TargetDir)SignedApp.exe
However that seems non-portable. How do I get the same path defined for the Native Command Prompt to be used by my post build event without hard coding it? I've looked at the list of macros but haven't found any that would be useful.
I found this question first so I'll post the answer I eventually went with.
Along the way I looked at this other answer and some docs:
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
My solution ended up being this big PropertyGroup added into the csproj file:
<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>
I need lots of the extra intermediary properties because the Windows SDK on my machine doesn't install signtool.exe
in <root>\bin\x64\signtool.exe
but rather under another directory level that is the version of the SDK which I definitely do not want to hard-code.
And then in the post-build I can use this "$(SignToolPath)signtool.exe"
The solution I decided on was:
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
This was a variation on @Dennis Kuypers suggestion #4. The developer must set environment variable SIGNTOOL to the correct location. If they fail to do so then one known possible location is attempted. If that fails then error is reported instructing them to set SIGNTOOL env var appropriately.
I did discover there is an environment variable WindowsSdkDir
WindowsSdkDir=C:\Program Files (x86)\Windows Kits\8.1\
But again, this was set only when running the Native Command Prompt and thus was not defined when running the post build event script.
I had the same issue in Visual Studio 2012 and found an easier way to resolve this. Instead of starting Visual Studio direct start "Developer Command Prompt for VS2012" and then type 'devenv' in the command prompt to start Visual Studio. After that signtool worked fine for me.