WiX: Digitally Sign BootStrapper project
Solution 1:
<Target Name="UsesFrameworkSdk">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="FrameworkSdkPath" />
</GetFrameworkSdkPath>
<PropertyGroup>
<Win8SDK>$(registry:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SDKs\Windows\v8.0@InstallationFolder)</Win8SDK>
</PropertyGroup>
</Target>
<Target Name="UsesSignTool" DependsOnTargets="UsesFrameworkSdk">
<PropertyGroup>
<SignToolPath Condition="('@(SignToolPath)'=='') and Exists('$(FrameworkSdkPath)bin\signtool.exe')">$(FrameworkSdkPath)bin\signtool.exe</SignToolPath>
<SignToolPath Condition="('@(SignToolPath)'=='') and Exists('$(Win8SDK)\bin\x86\signtool.exe')">$(Win8SDK)\bin\x86\signtool.exe</SignToolPath>
</PropertyGroup>
</Target>
<Target Name="SignBundleEngine" DependsOnTargets="UsesSignTool">
<Exec Command=""$(SignToolPath)" sign /d "App Setup" /t http://timestamp.digicert.com /a "@(SignBundleEngine)"" />
</Target>
<Target Name="SignBundle" DependsOnTargets="UsesSignTool">
<Exec Command=""$(SignToolPath)" sign /d "App Setup" /t http://timestamp.digicert.com /a "@(SignBundle)"" />
</Target>
This works well for me. Either you do it during the build, or you need to use insignia.
Ex:
http://wixtoolset.org/documentation/manual/v3/overview/insignia.html
insignia -ib bundle.exe -o engine.exe
... sign engine.exe
insignia -ab engine.exe bundle.exe -o bundle.exe
... sign bundle.exe
Solution 2:
For me using WiX's in-built tool insignia
is the most straight-forward. Here's the steps I made to do code-sign a WiX MSI and bootstrap installer:
(steps 1 & 2 are just set up to make 3 & 4 read easy and more reusable and updatable! Steps 3 & 4 are the actual signing)
- Set up the
signtool
as a batch file in my PATH so that I can call it and change it easily. I'm running Windows 10 and so my "signtool.bat" looks like this:"c:\Program Files (x86)\Windows Kits\10\bin\x64\signtool.exe" %*
- Set up
insignia
as a batch file in my PATH too so you can change it with new WiX builds as they come. My "insignia.bat" looks like this:"C:\Program Files (x86)\WiX Toolset v3.10\bin\insignia.exe" %*
-
Sign my MSI in a post-build event (MSI Project -> Properties -> Build Events) by calling this:
signtool sign /f "c:\certificates\mycert.pfx" /p cert-password /d "Your Installer Label" /t http://timestamp.verisign.com/scripts/timstamp.dll /v $(TargetFileName)
-
Sign my bundle in a post-build event for the bootstrap project like this:
CALL insignia -ib "$(TargetFileName)" -o engine.exe
CALL signtool sign /f "c:\certificates\mycert.pfx" /p cert-password /d "Installer Name" /t http://timestamp.verisign.com/scripts/timstamp.dll /v engine.exe
CALL insignia -ab engine.exe "$(TargetFileName)" -o "$(TargetFileName)"
CALL signtool sign /f "c:\certificates\mycert.pfx" /p cert-password /d "Installer Name" /t http://timestamp.verisign.com/scripts/timstamp.dll /v "$(TargetFileName)"
Further notes and thoughts:
I have also signed the application (I think) by just doing
Project Properties -> Signing
and enabling click-once manifests, selecting the certificate and checking theSign the assembly
option.Specifying CALL is necessary in post-build events when calling a batch file or only the first one gets called.
Solution 3:
Updating for VS2019, and based on @jchoover's answer, here is what I got working.
This leverages some MSBuild property function work by @webjprgm here that makes finding signtool.exe more generic across Windows Kit versions. As mentioned by @karfus in a comment above, adding the SignOutput section is the incantation that kicks everything off.
This goes at the end of your bootstrap.wixproj file, before the closing /Project tag.
<!-- SignOutput must be present in some PropertyGroup to trigger signing. -->
<PropertyGroup>
<SignOutput>true</SignOutput>
</PropertyGroup>
<!-- Find Windows Kit path and then SignTool path for the post-build event -->
<Target Name="FindSignTool">
<PropertyGroup>
<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>
</Target>
<!-- Sign the bundle engine -->
<Target Name="SignBundleEngine" DependsOnTargets="FindSignTool">
<Exec Command=""$(SignToolPath)signtool.exe" sign /d "MyApp Setup" /fd SHA256 /td SHA256 /a /f "MyApp Code Certificate.pfx" /p CertPassword /tr http://timestamp.digicert.com /a "@(SignBundleEngine)"" />
</Target>
<!-- Sign the final bundle -->
<Target Name="SignBundle" DependsOnTargets="FindSignTool">
<Exec Command=""$(SignToolPath)signtool.exe" sign /d "MyApp Setup" /fd SHA256 /td SHA256 /a /f "MyApp Code Certificate.pfx" /p CertPassword /tr http://timestamp.digicert.com /a "@(SignBundle)"" />
</Target>
Solution 4:
Further @jchoover's answer, you have 3 options when signing bundles:
Build the bundle unsigned, then sign it later. However, you also need to sign the engine exe which is embedded within the bundle. As @jchoover states, you can use insignia to get around this by extracting the engine to a file. You can then sign the file using your normal process (for example, with signtool.exe) and then import it back into the bundle
Add the SignBundle and SignBundleEngine targets to your project(s). You can do this by opening them up in a text editor, and editing the underlying MSBuild code. @jchoover's answer describes how you can do this.
-
Create a .targets file with the SignBundle and SignBundleEngine targets, and passing the path using the CustomAfterWixTargets property:
msbuild your.sln /p:CustomAfterWixTargets=customafterwix.targets /p:SignOutput=true