45

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 then installs the msi package. My project includes Crystal Reports as an msm file which is installed with the msi but requires C++ 2005 to install properly.

On the MSI project I have included the following post build event to digitally sign the msi file.

  sign  /f "$(ProjectDir)\myPFXFile.pfx" /p mySecretKey/d "My Program" /t http://timestamp.verisign.com/scripts/timstamp.dll /v "MyProgram.msi"

If I install just the msi it correctly identifies the Publisher when requesting elevated permission to install.

I have tried adding the same post-build event to the bootstrapper project as follows:

  sign  /f "$(ProjectDir)\myPFXFile.pfx" /p mySecretKey/d "My Program" /t http://timestamp.verisign.com/scripts/timstamp.dll /v "MyProgram Setup.exe"

When I attempt to install the exe file it correctly identifies the Publisher but then fails to install with the following from the log file:

[1604:2574][2013-12-04T11:49:51]i001: Burn v3.7.1224.0, Windows v6.2 (Build 9200: Service Pack 0), path: C:\Users\.....\MyProgram Setup.exe, cmdline: ''
[1604:2574][2013-12-04T11:49:51]i000: Setting string variable 'WixBundleLog' to value 'C:\Users\.....\MyProgram_20131204114951.log'
[1604:2574][2013-12-04T11:49:51]i000: Setting string variable 'WixBundleOriginalSource' to value 'C:\Users\.....\MyProgram Setup.exe'
[1604:2574][2013-12-04T11:49:51]i000: Setting string variable 'WixBundleName' to value 'MyProgram'
[1604:2574][2013-12-04T11:49:51]i100: Detect begin, 2 packages
[1604:2574][2013-12-04T11:49:51]i000: Setting string variable 'vcredist_x86' to value '1'
[1604:2574][2013-12-04T11:49:51]i000: Setting string variable 'vcredist_x64' to value '1'
[1604:2574][2013-12-04T11:49:51]i052: Condition 'vcredist_x86 AND (vcredist_x86 >= 1)' evaluates to true.
[1604:2574][2013-12-04T11:49:51]i101: Detected package: vcredist_x86, state: Present, cached: None
[1604:2574][2013-12-04T11:49:51]i101: Detected package: MyProgram, state: Absent, cached: None
[1604:2574][2013-12-04T11:49:51]i199: Detect complete, result: 0x0
[1604:2574][2013-12-04T11:49:53]i200: Plan begin, 2 packages, action: Install
[1604:2574][2013-12-04T11:49:53]w321: Skipping dependency registration on package with no dependency providers: vcredist_x86
[1604:2574][2013-12-04T11:49:53]i000: Setting string variable 'WixBundleRollbackLog_MyProgram' to value 'C:\Users\.....\MyProgram_20131204114951_0_MyProgram_rollback.log'
[1604:2574][2013-12-04T11:49:53]i000: Setting string variable 'WixBundleLog_MyProgram' to value 'C:\Users\.....\MyProgram_20131204114951_0_MyProgram.log'
[1604:2574][2013-12-04T11:49:53]i201: Planned package: vcredist_x86, state: Present, default requested: Present, ba requested: Present, execute: None, rollback: None, cache: No, uncache: No, dependency: None
[1604:2574][2013-12-04T11:49:53]i201: Planned package: MyProgram, state: Absent, default requested: Present, ba requested: Present, execute: Install, rollback: Uninstall, cache: Yes, uncache: No, dependency: Register
[1604:2574][2013-12-04T11:49:53]i299: Plan complete, result: 0x0
[1604:2574][2013-12-04T11:49:53]i300: Apply begin
[1FF8:10F8][2013-12-04T11:49:58]i360: Creating a system restore point.
[1FF8:10F8][2013-12-04T11:49:59]i361: Created a system restore point.
[1FF8:10F8][2013-12-04T11:50:00]i000: Caching bundle from: 'C:\Users\.....\{6ab8eece-89c6-4417-905f-6d9c5136519d}\.be\MyProgram Setup.exe' to: 'C:\ProgramData\Package Cache\{6ab8eece-89c6-4417-905f-6d9c5136519d}\MyProgram Setup.exe'
[1FF8:10F8][2013-12-04T11:50:00]i320: Registering bundle dependency provider: {6ab8eece-89c6-4417-905f-6d9c5136519d}, version: 2.0.0.0
[1604:2FB4][2013-12-04T11:50:00]i336: Acquiring container: WixAttachedContainer, copy from: C:\Users\.....\MyProgram Setup.exe
[1604:2FB4][2013-12-04T11:50:00]i000: Setting string variable 'WixBundleLastUsedSource' to value 'C:\Users\.....'
[1604:24F8][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to extract all files from container.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to wait for operation complete.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to open container.
[1604:2FB4][2013-12-04T11:50:00]e000: Error 0x80004005: Failed to open container: WixAttachedContainer.
[1604:2FB4][2013-12-04T11:50:00]e312: Failed to extract payloads from container: WixAttachedContainer to working path: C:\Users\.....\{6ab8eece-89c6-4417-905f-6d9c5136519d}\C7C1FB4E513C19E0F5E8F6856FF2ACC4D7D143A2, error: 0x80004005.
[1604:2574][2013-12-04T11:50:00]e000: Error 0x80004005: Failed while caching, aborting execution.
[1FF8:10F8][2013-12-04T11:50:00]i330: Removed bundle dependency provider: {6ab8eece-89c6-4417-905f-6d9c5136519d}
[1FF8:10F8][2013-12-04T11:50:00]i352: Removing cached bundle: {6ab8eece-89c6-4417-905f-6d9c5136519d}, from path: C:\ProgramData\Package Cache\{6ab8eece-89c6-4417-905f-6d9c5136519d}\
[1604:2574][2013-12-04T11:50:00]i399: Apply complete, result: 0x80004005, restart: None, ba requested restart:  No

I then found another alternative to signing the exe by adding the following to the end of the .wixproj file:

  <Target Name="SignBundleEngine">
    <Exec Command="&quot;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\signtool.exe&quot; sign /f &quot;$(ProjectDir)\sigFile.pfx&quot; /p sigKey /d &quot;My Program&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll &quot;@(SignBundleEngine)&quot;" />
  </Target>
  <Target Name="SignBundle">
    <Exec Command="&quot;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin\signtool.exe&quot; sign /f &quot;$(ProjectDir)\sigFile.pfx&quot; /p sigKey /d &quot;My Program&quot; /t http://timestamp.verisign.com/scripts/timstamp.dll &quot;@(SignBundle)&quot;" />
  </Target>
  <PropertyGroup>
    <PostBuildEvent>"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe"  sign  /f "$(ProjectDir)\sigFile.pfx" /p sigKey /d "My Program" /t http://timestamp.verisign.com/scripts/timstamp.dll /v "MyProgram Setup.exe"</PostBuildEvent>
  </PropertyGroup>

Using this method the setup file executes and installs everything correctly but doesn't identify the publisher when requesting elevated permission to install, it says "Publisher: Unknown".

Anyone know how to get the digital signing to work on this bootstrapper?

Here is my Bundle.wxs and vcredist.wxs files:

Bundle.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">


  <Bundle Name="My Program"
          Version="2.0.0"
          Manufacturer="My Company"
          UpgradeCode="PUT-GUID-HERE"
          HelpUrl="http://www.mycompany.com"
          AboutUrl="http://www.mycompany.com"
          HelpTelephone="888 888 8888"
          IconSourceFile="Resources\program.ico">

    <BootstrapperApplicationRef Id="WixStandardBootstrapperApplication.HyperlinkLicense">
      <Payload SourceFile="Resources\Bootstrapper Screen.png" />
    </BootstrapperApplicationRef>

    <WixVariable Id="WixStdbaLicenseUrl" Value=""/>

    <WixVariable Id="WixStdbaThemeXml" Value="Resources\CustomHyperlinkTheme.xml"/>
    <WixVariable Id="WixStdbaThemeWxl" Value="Resources\CustomHyperlinkTheme.wxl"/>

    <Chain>

      <!-- Define the list of chained packages. -->
      <PackageGroupRef Id="vcredist"/>
      <MsiPackage Id="MyProgram"
                  SourceFile="$(var.MyProgramSetup.TargetPath)"
                  ForcePerMachine="yes" />
    </Chain>
  </Bundle>
</Wix>

vcredist.wxs

<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
     xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
  <Fragment>
    <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\10.0\VC\VCRedist\x86" Value="Installed" Variable="vcredist_x86" />
    <util:RegistrySearch Root="HKLM" Key="SOFTWARE\Microsoft\VisualStudio\10.0\VC\VCRedist\x64" Value="Installed" Variable="vcredist_x64" />
    <PackageGroup Id="vcredist">
      <ExePackage Id="vcredist_x86"
        Cache="no"
        Compressed="yes"
        PerMachine="yes"
        Permanent="yes"
        Vital="yes"
        Name="vcredist_x86.exe"
        SourceFile="vcredist3.5_x86.exe"
        InstallCommand="/q"
        DetectCondition="vcredist_x86 AND (vcredist_x86 &gt;= 1)">
      </ExePackage>
    </PackageGroup>
  </Fragment>
</Wix>
1
  • How do your post build events know how to sign the file without a reference there to signtool.exe? Is something missing here? Commented Apr 15, 2016 at 15:12

4 Answers 4

41
  <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
Sign up to request clarification or add additional context in comments.

16 Comments

I tried what you have above with one minor mod to accommodate my environment: point to ...Windows\v7.1A as I don't have a ...\v8.0 on my machine and v7.1.A is where signtool.exe resides. I get a similar error message as before of "The command """ sign /d "App Setup" /t timestamp.digicert.com /a "obj\Debug\MyProgram.exe"" exited with code 9009.". I am running Windows 8.1, do I need a newer version of signtool perhaps?
So you are having issues finding the installed SDK. You need to either tweak the UsesFrameworkSDK target to probe to find yours, or optionally hard code the path (yuck).
I hardcoded the <SignToolPath> to C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Bin\signtool.exe and now the project builds, EXE is digitally signed and installs correctly. The only nagging issue I have is that during installation when prompted for elevated permission the prompt shows the Publisher as "Unknown" instead of showing my company name. Any idea how to fix this?
Could you please provide the parent element with with your xml?
It may be obvious to most but for me there was a crucial bit of information left out here...the "SignBundleEngine" and "SignBundle" targets won't do anything unless you add a SignOutput property to the project file as well, e.g. <PropertyGroup> <SignOutput>true</SignOutput> </PropertyGroup>
|
30

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.

5 Comments

If you're getting this error when trying to sign engine.exe: SignedCode::Sign returned error: 0x800700C1, make sure your $(TargetFileName) isn't signed first.
Josh, you pointed me to the right direction. My msbuild script was not cleaning the Release folder and Wix was skipping the compile target as there was no changes.
this helps me. I used nmake to manage wix project.
Do we need to purchase the certificate for signing the MSI?
@TapasRanjanSingh if you want to distribute it for users, yes you do. If you just want to sign it for testing you can create a developer certificate.
13

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>

2 Comments

This works but the problem is you are including a signing certificate password in a file that would normally get checked into source control. Any ideas how to externally reference this password?
Nevermind, I learned how to add my cert to my personal store using Certmgr.exe. That solved my problem.
10

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.