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="&quot;$(SignToolPath)&quot; sign /d &quot;App Setup&quot; /t http://timestamp.digicert.com /a &quot;@(SignBundleEngine)&quot;" />
  </Target>

  <Target Name="SignBundle" DependsOnTargets="UsesSignTool">
    <Exec Command="&quot;$(SignToolPath)&quot; sign /d &quot;App Setup&quot; /t http://timestamp.digicert.com /a &quot;@(SignBundle)&quot;" />
  </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)

  1. 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" %*
  2. 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" %*
  3. 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)
  4. 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 the Sign 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="&quot;$(SignToolPath)signtool.exe&quot; sign /d &quot;MyApp Setup&quot; /fd SHA256 /td SHA256 /a /f &quot;MyApp Code Certificate.pfx&quot; /p CertPassword /tr http://timestamp.digicert.com /a &quot;@(SignBundleEngine)&quot;" />
  </Target>

  <!-- Sign the final bundle -->
  <Target Name="SignBundle" DependsOnTargets="FindSignTool">
    <Exec Command="&quot;$(SignToolPath)signtool.exe&quot; sign /d &quot;MyApp Setup&quot;  /fd SHA256 /td SHA256 /a /f &quot;MyApp Code Certificate.pfx&quot; /p CertPassword /tr http://timestamp.digicert.com /a &quot;@(SignBundle)&quot;" />
  </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