I have a project for which I have built a WiX msi file. I also have a WiX bootstrapper (exe file) that checks for the existence of C++ 2005, installs it if not found and th
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
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)
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" %*
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" %*
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 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.
<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
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>