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

4

10

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.

Vaughan answered 14/6, 2017 at 23:49 Comment(1)
1. Include the signtool in your repo 2. Include a exe that does the signing by locating the signtool for you maybe github.com/Microsoft/vswhere can help 3. Setup a build server that has fixed paths (this is what i ended up doing) 4. Add a batch file that is not in your VCS that each user has to edit (ideally once)Amadoamador
V
4

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.

Vaughan answered 15/6, 2017 at 18:35 Comment(0)
W
17

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://learn.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"

Wiskind answered 24/10, 2018 at 20:57 Comment(2)
IMO, and based on many hours of Googling and Stack-Overflowing, THIS is the correct answer, and not just for VS2013 (I'm using VS2019 at time of this comment). Thanks, @webjprgm. Also, I now know msbuild property functions, cool stuff!Chadd
I agree to @entiat, this is the SOLUTION even for later versions of Visual Studio (I still use VS 2015 and VS 2019). Thank YouRenin
V
4

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.

Vaughan answered 15/6, 2017 at 18:35 Comment(0)
O
4

I've been looking at a lot of posts of various vintages today, and the problem with most of them is that they haven't been consistently working after a couple of years, or they involved a lot of registry reads followed by a lot of conditional hard coded paths.

I finally hit on something that (I think) will work for a while. At least 2019 going forward until something breaks.

I make my Post Build event something to the effect of

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

The first line gets all the environment variables set up as though you were in the Developer Command Prompt. The second does the signing using that environment

Orthopterous answered 1/11, 2021 at 18:25 Comment(2)
I see this does work but I'm not seeing the macro $(VSAPPIDDIR) listed in the available Macros when I edit Post Build Events in my version 16.11.5 of VS2019. How and where did you find the $(VSAPPIDDIR) macro?Vaughan
Two ways - First I did a post build event that consisted of just the SET command, so I could see what was defined in the environment (that one got propagated to the shell). Then I googled for the VS macros and found a site that had it on the list.Orthopterous
A
1

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.

Amersham answered 10/1, 2018 at 20:6 Comment(1)
The issue I see with this solution is that when another developer comes along (perhaps years later) and tries to build the project it will fail without any indication of what they did wrong. This requires a non-conventional method of starting VS and it requires that the developer know to always start VS this way. I think I prefer my solution as it gives feedback to the developer as to what to do when things don't work and once the environment variable is setup then VS can be started in the conventional manner.Vaughan

© 2022 - 2024 — McMap. All rights reserved.