aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Mensching <rob@firegiant.com>2022-01-10 14:19:16 -0800
committerRob Mensching <rob@firegiant.com>2022-01-10 17:08:43 -0800
commit933d4fc340f989239b77bfef4212f80d0a4a65f2 (patch)
treedd296a9c3f009558526ead34e5cabc0f4c359c86
parentee2bb35228e8d3c12ae0e31748075777e10f9d62 (diff)
downloadwix-933d4fc340f989239b77bfef4212f80d0a4a65f2.tar.gz
wix-933d4fc340f989239b77bfef4212f80d0a4a65f2.tar.bz2
wix-933d4fc340f989239b77bfef4212f80d0a4a65f2.zip
Support "inscribing" Burn bundles
-rw-r--r--src/api/wix/WixToolset.Data/WarningMessages.cs2
-rw-r--r--src/wix/WixToolset.BuildTasks/DetachBundleEngineForSigning.cs64
-rw-r--r--src/wix/WixToolset.BuildTasks/ReattachSignedBundleEngine.cs71
-rw-r--r--src/wix/WixToolset.BuildTasks/ToolsetTask_InProc.cs2
-rw-r--r--src/wix/WixToolset.Core.Burn/BundleBackend.cs11
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs5
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnCommand.cs78
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnExtensionCommandLine.cs41
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs13
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnSubcommandBase.cs15
-rw-r--r--src/wix/WixToolset.Core.Burn/DetachSubcommand.cs80
-rw-r--r--src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs37
-rw-r--r--src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs19
-rw-r--r--src/wix/WixToolset.Core.Burn/ReattachSubcommand.cs101
-rw-r--r--src/wix/WixToolset.Sdk/tools/wix.signing.targets42
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/SigningFixture.cs112
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_bundle_engine.exebin0 -> 3015593 bytes
17 files changed, 639 insertions, 54 deletions
diff --git a/src/api/wix/WixToolset.Data/WarningMessages.cs b/src/api/wix/WixToolset.Data/WarningMessages.cs
index a592fe48..b749bcf4 100644
--- a/src/api/wix/WixToolset.Data/WarningMessages.cs
+++ b/src/api/wix/WixToolset.Data/WarningMessages.cs
@@ -279,7 +279,7 @@ namespace WixToolset.Data
279 279
280 public static Message ExternalCabsAreNotSigned(string databaseFile) 280 public static Message ExternalCabsAreNotSigned(string databaseFile)
281 { 281 {
282 return Message(null, Ids.ExternalCabsAreNotSigned, "The installer database '{0}' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to run the insignia tool as part of your build.", databaseFile); 282 return Message(null, Ids.ExternalCabsAreNotSigned, "The installer database '{0}' has external cabs, but at least one of them is not signed. Please ensure that all external cabs are signed, if you mean to sign them. If you don't mean to sign them, there is no need to inscribe the MSI as part of your build.", databaseFile);
283 } 283 }
284 284
285 public static Message FailedToDeleteTempDir(string directory) 285 public static Message FailedToDeleteTempDir(string directory)
diff --git a/src/wix/WixToolset.BuildTasks/DetachBundleEngineForSigning.cs b/src/wix/WixToolset.BuildTasks/DetachBundleEngineForSigning.cs
new file mode 100644
index 00000000..5fcd1def
--- /dev/null
+++ b/src/wix/WixToolset.BuildTasks/DetachBundleEngineForSigning.cs
@@ -0,0 +1,64 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.BuildTasks
4{
5 using Microsoft.Build.Framework;
6
7 /// <summary>
8 /// An MSBuild task to run WiX to detach bundle engine to be signed.
9 /// </summary>
10 public sealed partial class DetachBundleEngineForSigning : WixExeBaseTask
11 {
12 /// <summary>
13 /// The bundle from which to detach the bundle engine.
14 /// </summary>
15 [Required]
16 public ITaskItem BundleFile { get; set; }
17
18 /// <summary>
19 /// Gets or sets the intermedidate folder to use.
20 /// </summary>
21 public ITaskItem IntermediateDirectory { get; set; }
22
23 /// <summary>
24 /// Gets or sets the path to the output detached bundle.
25 /// </summary>
26 [Required]
27 public ITaskItem OutputFile { get; set; }
28
29 /// <summary>
30 /// Gets or sets the output. Only set if the task does work.
31 /// </summary>
32 [Output]
33 public ITaskItem Output { get; set; }
34
35 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
36 {
37 commandLineBuilder.AppendTextUnquoted("burn detach");
38
39 commandLineBuilder.AppendFileNameIfNotNull(this.BundleFile);
40 commandLineBuilder.AppendSwitchIfNotNull("-engine ", this.OutputFile);
41 commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory);
42
43 base.BuildCommandLine(commandLineBuilder);
44
45 commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions);
46 }
47
48 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
49 {
50 var exitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
51
52 if (exitCode == 0) // successfully did work.
53 {
54 this.Output = this.OutputFile;
55 }
56 else if (exitCode == -1000) // no work done.
57 {
58 exitCode = 0;
59 }
60
61 return exitCode;
62 }
63 }
64}
diff --git a/src/wix/WixToolset.BuildTasks/ReattachSignedBundleEngine.cs b/src/wix/WixToolset.BuildTasks/ReattachSignedBundleEngine.cs
new file mode 100644
index 00000000..c07c12b9
--- /dev/null
+++ b/src/wix/WixToolset.BuildTasks/ReattachSignedBundleEngine.cs
@@ -0,0 +1,71 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.BuildTasks
4{
5 using Microsoft.Build.Framework;
6
7 /// <summary>
8 /// An MSBuild task to run WiX to reattach a (signed) bundle engine to its bundle.
9 /// </summary>
10 public sealed partial class ReattachSignedBundleEngine : WixExeBaseTask
11 {
12 /// <summary>
13 /// The bundle to which to attach the bundle engine.
14 /// </summary>
15 [Required]
16 public ITaskItem BundleFile { get; set; }
17
18 /// <summary>
19 /// The bundle engine file to reattach to the bundle.
20 /// </summary>
21 [Required]
22 public ITaskItem BundleEngineFile { get; set; }
23
24 /// <summary>
25 /// Gets or sets the intermedidate folder to use.
26 /// </summary>
27 public ITaskItem IntermediateDirectory { get; set; }
28
29 /// <summary>
30 /// Gets or sets the path to the output detached bundle.
31 /// </summary>
32 [Required]
33 public ITaskItem OutputFile { get; set; }
34
35 /// <summary>
36 /// Gets or sets the output. Only set if the task does work.
37 /// </summary>
38 [Output]
39 public ITaskItem Output { get; set; }
40
41 protected override void BuildCommandLine(WixCommandLineBuilder commandLineBuilder)
42 {
43 commandLineBuilder.AppendTextUnquoted("burn reattach");
44
45 commandLineBuilder.AppendFileNameIfNotNull(this.BundleFile);
46 commandLineBuilder.AppendSwitchIfNotNull("-engine ", this.BundleEngineFile);
47 commandLineBuilder.AppendSwitchIfNotNull("-out ", this.OutputFile);
48 commandLineBuilder.AppendSwitchIfNotNull("-intermediatefolder ", this.IntermediateDirectory);
49
50 base.BuildCommandLine(commandLineBuilder);
51
52 commandLineBuilder.AppendTextIfNotWhitespace(this.AdditionalOptions);
53 }
54
55 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
56 {
57 var exitCode = base.ExecuteTool(pathToTool, responseFileCommands, commandLineCommands);
58
59 if (exitCode == 0) // successfully did work.
60 {
61 this.Output = this.OutputFile;
62 }
63 else if (exitCode == -1000) // no work done.
64 {
65 exitCode = 0;
66 }
67
68 return exitCode;
69 }
70 }
71}
diff --git a/src/wix/WixToolset.BuildTasks/ToolsetTask_InProc.cs b/src/wix/WixToolset.BuildTasks/ToolsetTask_InProc.cs
index fcf4aea9..eff117da 100644
--- a/src/wix/WixToolset.BuildTasks/ToolsetTask_InProc.cs
+++ b/src/wix/WixToolset.BuildTasks/ToolsetTask_InProc.cs
@@ -15,7 +15,7 @@ namespace WixToolset.BuildTasks
15 15
16 public partial class ToolsetTask 16 public partial class ToolsetTask
17 { 17 {
18 protected sealed override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands) 18 protected override int ExecuteTool(string pathToTool, string responseFileCommands, string commandLineCommands)
19 { 19 {
20 if (this.RunAsSeparateProcess) 20 if (this.RunAsSeparateProcess)
21 { 21 {
diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs
index 518b77c8..b179ea50 100644
--- a/src/wix/WixToolset.Core.Burn/BundleBackend.cs
+++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs
@@ -47,16 +47,7 @@ namespace WixToolset.Core.Burn
47 47
48 public bool Inscribe(IInscribeContext context) 48 public bool Inscribe(IInscribeContext context)
49 { 49 {
50 if (String.IsNullOrEmpty(context.SignedEngineFile)) 50 return false;
51 {
52 var command = new InscribeBundleCommand(context);
53 return command.Execute();
54 }
55 else
56 {
57 var command = new InscribeBundleEngineCommand(context);
58 return command.Execute();
59 }
60 } 51 }
61 52
62 public Intermediate Unbind(IUnbindContext context) 53 public Intermediate Unbind(IUnbindContext context)
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs
index 575252b0..933afc77 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/BurnReader.cs
@@ -45,11 +45,6 @@ namespace WixToolset.Core.Burn.Bundles
45 /// </summary> 45 /// </summary>
46 public Stream Stream => this.binaryReader?.BaseStream; 46 public Stream Stream => this.binaryReader?.BaseStream;
47 47
48 internal static BurnReader Open(object inputFilePath)
49 {
50 throw new NotImplementedException();
51 }
52
53 /// <summary> 48 /// <summary>
54 /// Opens a Burn reader. 49 /// Opens a Burn reader.
55 /// </summary> 50 /// </summary>
diff --git a/src/wix/WixToolset.Core.Burn/BurnCommand.cs b/src/wix/WixToolset.Core.Burn/BurnCommand.cs
new file mode 100644
index 00000000..3835bf2e
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/BurnCommand.cs
@@ -0,0 +1,78 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Threading;
7 using System.Threading.Tasks;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Burn specialized command.
13 /// </summary>
14 internal class BurnCommand : ICommandLineCommand
15 {
16 public BurnCommand(IServiceProvider serviceProvider)
17 {
18 this.ServiceProvider = serviceProvider;
19 }
20
21 public bool ShowHelp { get; set; }
22
23 public bool ShowLogo { get; set; }
24
25 public bool StopParsing { get; set; }
26
27 private IServiceProvider ServiceProvider { get; }
28
29 private BurnSubcommandBase Subcommand { get; set; }
30
31 public Task<int> ExecuteAsync(CancellationToken cancellationToken)
32 {
33 if (this.ShowHelp || this.Subcommand is null)
34 {
35 DisplayHelp();
36 return Task.FromResult(1);
37 }
38
39 return this.Subcommand.ExecuteAsync(cancellationToken);
40 }
41
42 public bool TryParseArgument(ICommandLineParser parser, string argument)
43 {
44 if (this.Subcommand is null)
45 {
46 switch (argument.ToLowerInvariant())
47 {
48 case "detach":
49 this.Subcommand = new DetachSubcommand(this.ServiceProvider);
50 return true;
51
52 case "reattach":
53 this.Subcommand = new ReattachSubcommand(this.ServiceProvider);
54 return true;
55 }
56
57 return false;
58 }
59
60 return this.Subcommand.TryParseArgument(parser, argument);
61 }
62
63 private static void DisplayHelp()
64 {
65 Console.WriteLine();
66 Console.WriteLine("Usage: wix burn detach|reattach bundle.exe -out engine.exe");
67 Console.WriteLine();
68 Console.WriteLine("Options:");
69 Console.WriteLine(" -h|--help Show command line help.");
70 Console.WriteLine(" --nologo Suppress displaying the logo information.");
71 Console.WriteLine();
72 Console.WriteLine("Commands:");
73 Console.WriteLine();
74 Console.WriteLine(" detach Detaches the burn engine from a bundle so it can be signed.");
75 Console.WriteLine(" reattach Reattaches a signed burn engine to a bundle.");
76 }
77 }
78}
diff --git a/src/wix/WixToolset.Core.Burn/BurnExtensionCommandLine.cs b/src/wix/WixToolset.Core.Burn/BurnExtensionCommandLine.cs
new file mode 100644
index 00000000..66e77888
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/BurnExtensionCommandLine.cs
@@ -0,0 +1,41 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.Collections.Generic;
7 using WixToolset.Extensibility;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services;
10
11 /// <summary>
12 /// Parses the "msi" command-line command. See <c>WindowsInstallerCommand</c>
13 /// for the bulk of the command-line processing.
14 /// </summary>
15 internal class BurnExtensionCommandLine : BaseExtensionCommandLine
16 {
17 public BurnExtensionCommandLine(IServiceProvider serviceProvider)
18 {
19 this.ServiceProvider = serviceProvider;
20 }
21
22 private IServiceProvider ServiceProvider { get; }
23
24 public override IReadOnlyCollection<ExtensionCommandLineSwitch> CommandLineSwitches => new ExtensionCommandLineSwitch[]
25 {
26 new ExtensionCommandLineSwitch { Switch = "burn", Description = "Burn specialized operations." },
27 };
28
29 public override bool TryParseCommand(ICommandLineParser parser, string argument, out ICommandLineCommand command)
30 {
31 command = null;
32
33 if ("burn".Equals(argument, StringComparison.OrdinalIgnoreCase))
34 {
35 command = new BurnCommand(this.ServiceProvider);
36 }
37
38 return command != null;
39 }
40 }
41}
diff --git a/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs
index b34d12c1..eca94f77 100644
--- a/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs
+++ b/src/wix/WixToolset.Core.Burn/BurnExtensionFactory.cs
@@ -1,4 +1,4 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. 1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2 2
3namespace WixToolset.Core.Burn 3namespace WixToolset.Core.Burn
4{ 4{
@@ -7,10 +7,21 @@ namespace WixToolset.Core.Burn
7 7
8 internal class BurnExtensionFactory : IExtensionFactory 8 internal class BurnExtensionFactory : IExtensionFactory
9 { 9 {
10 public BurnExtensionFactory(IServiceProvider serviceProvider)
11 {
12 this.ServiceProvider = serviceProvider;
13 }
14
15 private IServiceProvider ServiceProvider { get; }
16
10 public bool TryCreateExtension(Type extensionType, out object extension) 17 public bool TryCreateExtension(Type extensionType, out object extension)
11 { 18 {
12 extension = null; 19 extension = null;
13 20
21 if (extensionType == typeof(IExtensionCommandLine))
22 {
23 extension = new BurnExtensionCommandLine(this.ServiceProvider);
24 }
14 if (extensionType == typeof(IBackendFactory)) 25 if (extensionType == typeof(IBackendFactory))
15 { 26 {
16 extension = new BurnBackendFactory(); 27 extension = new BurnBackendFactory();
diff --git a/src/wix/WixToolset.Core.Burn/BurnSubcommandBase.cs b/src/wix/WixToolset.Core.Burn/BurnSubcommandBase.cs
new file mode 100644
index 00000000..62d69d4a
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/BurnSubcommandBase.cs
@@ -0,0 +1,15 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System.Threading;
6 using System.Threading.Tasks;
7 using WixToolset.Extensibility.Services;
8
9 internal abstract class BurnSubcommandBase
10 {
11 public abstract bool TryParseArgument(ICommandLineParser parser, string argument);
12
13 public abstract Task<int> ExecuteAsync(CancellationToken cancellationToken);
14 }
15}
diff --git a/src/wix/WixToolset.Core.Burn/DetachSubcommand.cs b/src/wix/WixToolset.Core.Burn/DetachSubcommand.cs
new file mode 100644
index 00000000..a1614a85
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/DetachSubcommand.cs
@@ -0,0 +1,80 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.IO;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using WixToolset.Core.Burn.Inscribe;
10 using WixToolset.Extensibility.Services;
11
12 internal class DetachSubcommand : BurnSubcommandBase
13 {
14 public DetachSubcommand(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 this.Messaging = serviceProvider.GetService<IMessaging>();
18 }
19
20 private IServiceProvider ServiceProvider { get; }
21
22 private IMessaging Messaging { get; }
23
24 private string InputPath { get; set; }
25
26 private string IntermediateFolder { get; set; }
27
28 private string EngineOutputPath { get; set; }
29
30 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
31 {
32 if (String.IsNullOrEmpty(this.InputPath))
33 {
34 Console.Error.WriteLine("Path to input bundle is required");
35 return Task.FromResult(-1);
36 }
37
38 if (String.IsNullOrEmpty(this.EngineOutputPath))
39 {
40 Console.Error.WriteLine("Path to output the bundle engine is required");
41 return Task.FromResult(-1);
42 }
43
44 if (String.IsNullOrEmpty(this.IntermediateFolder))
45 {
46 this.IntermediateFolder = Path.GetTempPath();
47 }
48
49 var command = new InscribeBundleEngineCommand(this.ServiceProvider, this.InputPath, this.EngineOutputPath, this.IntermediateFolder);
50 command.Execute();
51
52 return Task.FromResult(this.Messaging.LastErrorNumber);
53 }
54
55 public override bool TryParseArgument(ICommandLineParser parser, string argument)
56 {
57 if (parser.IsSwitch(argument))
58 {
59 var parameter = argument.Substring(1);
60 switch (parameter.ToLowerInvariant())
61 {
62 case "intermediatefolder":
63 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument);
64 return true;
65
66 case "engine":
67 this.EngineOutputPath = parser.GetNextArgumentAsFilePathOrError(argument);
68 return true;
69 }
70 }
71 else if (String.IsNullOrEmpty(this.InputPath))
72 {
73 this.InputPath = argument;
74 return true;
75 }
76
77 return false;
78 }
79 }
80}
diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs
index f835fd3a..6e071fe7 100644
--- a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleCommand.cs
@@ -2,42 +2,51 @@
2 2
3namespace WixToolset.Core.Burn.Inscribe 3namespace WixToolset.Core.Burn.Inscribe
4{ 4{
5 using System;
5 using System.IO; 6 using System.IO;
6 using WixToolset.Core.Burn.Bundles; 7 using WixToolset.Core.Burn.Bundles;
7 using WixToolset.Core.Native; 8 using WixToolset.Core.Native;
8 using WixToolset.Extensibility.Data;
9 using WixToolset.Extensibility.Services; 9 using WixToolset.Extensibility.Services;
10 10
11 internal class InscribeBundleCommand 11 internal class InscribeBundleCommand
12 { 12 {
13 public InscribeBundleCommand(IInscribeContext context) 13 public InscribeBundleCommand(IServiceProvider serviceProvider, string inputPath, string signedEngineFile, string outputPath, string intermediateFolder)
14 { 14 {
15 this.Context = context; 15 this.Messaging = serviceProvider.GetService<IMessaging>();
16 16 this.IntermediateFolder = intermediateFolder;
17 this.Messaging = context.ServiceProvider.GetService<IMessaging>(); 17 this.InputFilePath = inputPath;
18 this.SignedEngineFile = signedEngineFile;
19 this.OutputFile = outputPath;
18 } 20 }
19
20 private IInscribeContext Context { get; }
21 21
22 public IMessaging Messaging { get; } 22 private IMessaging Messaging { get; }
23
24 private string IntermediateFolder { get; }
25
26 private string InputFilePath { get; }
27
28 private string SignedEngineFile { get; }
29
30 private string OutputFile { get; }
23 31
24 public bool Execute() 32 public bool Execute()
25 { 33 {
26 var inscribed = false; 34 var inscribed = false;
27 var tempFile = Path.Combine(this.Context.IntermediateFolder, "bundle_engine_signed.exe"); 35 var tempFile = Path.Combine(this.IntermediateFolder, "~bundle_engine_signed.exe");
28 36
29 using (var reader = BurnReader.Open(this.Context.InputFilePath)) 37 using (var reader = BurnReader.Open(this.Messaging, this.InputFilePath))
30 { 38 {
31 FileSystem.CopyFile(this.Context.SignedEngineFile, tempFile, allowHardlink: false); 39 FileSystem.CopyFile(this.SignedEngineFile, tempFile, allowHardlink: false);
32 using (BurnWriter writer = BurnWriter.Open(this.Messaging, tempFile)) 40
41 using (var writer = BurnWriter.Open(this.Messaging, tempFile))
33 { 42 {
34 inscribed = writer.ReattachContainers(reader); 43 inscribed = writer.ReattachContainers(reader);
35 } 44 }
36 } 45 }
37 46
38 Directory.CreateDirectory(Path.GetDirectoryName(this.Context.OutputFile)); 47 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile));
39 48
40 FileSystem.MoveFile(tempFile, this.Context.OutputFile); 49 FileSystem.MoveFile(tempFile, this.OutputFile);
41 50
42 return inscribed; 51 return inscribed;
43 } 52 }
diff --git a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs
index a6789796..e607a28f 100644
--- a/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Inscribe/InscribeBundleEngineCommand.cs
@@ -6,28 +6,31 @@ namespace WixToolset.Core.Burn.Inscribe
6 using System.IO; 6 using System.IO;
7 using WixToolset.Core.Burn.Bundles; 7 using WixToolset.Core.Burn.Bundles;
8 using WixToolset.Core.Native; 8 using WixToolset.Core.Native;
9 using WixToolset.Extensibility.Data; 9 using WixToolset.Extensibility.Services;
10 10
11 internal class InscribeBundleEngineCommand 11 internal class InscribeBundleEngineCommand
12 { 12 {
13 public InscribeBundleEngineCommand(IInscribeContext context) 13 public InscribeBundleEngineCommand(IServiceProvider serviceProvider, string inputPath, string outputPath, string intermediateFolder)
14 { 14 {
15 this.IntermediateFolder = context.IntermediateFolder; 15 this.Messaging = serviceProvider.GetService<IMessaging>();
16 this.InputFilePath = context.InputFilePath; 16 this.IntermediateFolder = intermediateFolder;
17 this.OutputFile = context.OutputFile; 17 this.InputFilePath = inputPath;
18 this.OutputFile = outputPath;
18 } 19 }
19 20
21 private IMessaging Messaging { get; }
22
20 private string IntermediateFolder { get; } 23 private string IntermediateFolder { get; }
21 24
22 private string InputFilePath { get; } 25 private string InputFilePath { get; }
23 26
24 private string OutputFile { get; } 27 private string OutputFile { get; }
25 28
26 public bool Execute() 29 public void Execute()
27 { 30 {
28 var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe"); 31 var tempFile = Path.Combine(this.IntermediateFolder, "bundle_engine_unsigned.exe");
29 32
30 using (var reader = BurnReader.Open(this.InputFilePath)) 33 using (var reader = BurnReader.Open(this.Messaging, this.InputFilePath))
31 using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete)) 34 using (var writer = File.Open(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read | FileShare.Delete))
32 { 35 {
33 reader.Stream.Seek(0, SeekOrigin.Begin); 36 reader.Stream.Seek(0, SeekOrigin.Begin);
@@ -56,8 +59,6 @@ namespace WixToolset.Core.Burn.Inscribe
56 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile)); 59 Directory.CreateDirectory(Path.GetDirectoryName(this.OutputFile));
57 60
58 FileSystem.MoveFile(tempFile, this.OutputFile); 61 FileSystem.MoveFile(tempFile, this.OutputFile);
59
60 return true;
61 } 62 }
62 } 63 }
63} 64}
diff --git a/src/wix/WixToolset.Core.Burn/ReattachSubcommand.cs b/src/wix/WixToolset.Core.Burn/ReattachSubcommand.cs
new file mode 100644
index 00000000..ce2af2d3
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/ReattachSubcommand.cs
@@ -0,0 +1,101 @@
1// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
2
3namespace WixToolset.Core.Burn
4{
5 using System;
6 using System.IO;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using WixToolset.Core.Burn.Inscribe;
10 using WixToolset.Extensibility.Services;
11
12 internal class ReattachSubcommand : BurnSubcommandBase
13 {
14 public ReattachSubcommand(IServiceProvider serviceProvider)
15 {
16 this.ServiceProvider = serviceProvider;
17 this.Messaging = serviceProvider.GetService<IMessaging>();
18 }
19
20 private IServiceProvider ServiceProvider { get; }
21
22 private IMessaging Messaging { get; }
23
24 private string InputPath { get; set; }
25
26 private string SignedEnginePath { get; set; }
27
28 private string IntermediateFolder { get; set; }
29
30 private string OutputPath { get; set; }
31
32 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
33 {
34 if (String.IsNullOrEmpty(this.InputPath))
35 {
36 Console.Error.WriteLine("Path to input bundle is required");
37 return Task.FromResult(-1);
38 }
39
40 if (String.IsNullOrEmpty(this.SignedEnginePath))
41 {
42 Console.Error.WriteLine("Path to detached signed bundle engine is required");
43 return Task.FromResult(-1);
44 }
45
46 if (String.IsNullOrEmpty(this.IntermediateFolder))
47 {
48 this.IntermediateFolder = Path.GetTempPath();
49 }
50
51 if (String.IsNullOrEmpty(this.OutputPath))
52 {
53 this.OutputPath = this.InputPath;
54 }
55
56 var command = new InscribeBundleCommand(this.ServiceProvider, this.InputPath, this.SignedEnginePath, this.OutputPath, this.IntermediateFolder);
57 var didWork = command.Execute();
58
59 // If the detach subcommand did not encounter an error but did no work
60 // then return the special exit code that indicates no work was done (-1000).
61 var exitCode = this.Messaging.LastErrorNumber;
62
63 if (!didWork && exitCode == 0)
64 {
65 exitCode = -1000;
66 }
67
68 return Task.FromResult(exitCode);
69 }
70
71 public override bool TryParseArgument(ICommandLineParser parser, string argument)
72 {
73 if (parser.IsSwitch(argument))
74 {
75 var parameter = argument.Substring(1);
76 switch (parameter.ToLowerInvariant())
77 {
78 case "engine":
79 this.SignedEnginePath = parser.GetNextArgumentAsFilePathOrError(argument);
80 return true;
81
82 case "intermediatefolder":
83 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument);
84 return true;
85
86 case "o":
87 case "out":
88 this.OutputPath = parser.GetNextArgumentAsFilePathOrError(argument);
89 return true;
90 }
91 }
92 else if (String.IsNullOrEmpty(this.InputPath))
93 {
94 this.InputPath = argument;
95 return true;
96 }
97
98 return false;
99 }
100 }
101}
diff --git a/src/wix/WixToolset.Sdk/tools/wix.signing.targets b/src/wix/WixToolset.Sdk/tools/wix.signing.targets
index 45556e23..cdaee4a2 100644
--- a/src/wix/WixToolset.Sdk/tools/wix.signing.targets
+++ b/src/wix/WixToolset.Sdk/tools/wix.signing.targets
@@ -20,6 +20,14 @@
20 <UsingTask TaskName="InscribeMsiWithCabinetSignatures" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath)" Architecture="x86" /> 20 <UsingTask TaskName="InscribeMsiWithCabinetSignatures" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath)" Architecture="x86" />
21 <UsingTask TaskName="InscribeMsiWithCabinetSignatures" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath64)" Architecture="x64" /> 21 <UsingTask TaskName="InscribeMsiWithCabinetSignatures" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath64)" Architecture="x64" />
22 22
23 <UsingTask TaskName="DetachBundleEngineForSigning" Condition=" '$(WixTasksPath64)' == '' " AssemblyFile="$(WixTasksPath)" />
24 <UsingTask TaskName="DetachBundleEngineForSigning" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath)" Architecture="x86" />
25 <UsingTask TaskName="DetachBundleEngineForSigning" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath64)" Architecture="x64" />
26
27 <UsingTask TaskName="ReattachSignedBundleEngine" Condition=" '$(WixTasksPath64)' == '' " AssemblyFile="$(WixTasksPath)" />
28 <UsingTask TaskName="ReattachSignedBundleEngine" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath)" Architecture="x86" />
29 <UsingTask TaskName="ReattachSignedBundleEngine" Condition=" '$(WixTasksPath64)' != '' " AssemblyFile="$(WixTasksPath64)" Architecture="x64" />
30
23 <!-- Default Inscribe properties. --> 31 <!-- Default Inscribe properties. -->
24 <PropertyGroup> 32 <PropertyGroup>
25 <InscribeNoLogo Condition=" '$(InscribeNoLogo)' == '' ">$(NoLogo)</InscribeNoLogo> 33 <InscribeNoLogo Condition=" '$(InscribeNoLogo)' == '' ">$(NoLogo)</InscribeNoLogo>
@@ -230,22 +238,26 @@
230 Inputs="@(SignTargetPath)" 238 Inputs="@(SignTargetPath)"
231 Outputs="$(SignedFilePath)"> 239 Outputs="$(SignedFilePath)">
232 240
233 <Insignia 241 <DetachBundleEngineForSigning
234 BundleFile="@(SignTargetPath)" 242 BundleFile="@(SignTargetPath)"
235 OutputFile="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)" 243 OutputFile="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)"
236 ToolPath="$(WixToolPath)" 244 IntermediateDirectory="%(SignTargetPath.RootDir)%(SignTargetPath.Directory)"
245
237 NoLogo="$(InscribeNoLogo)" 246 NoLogo="$(InscribeNoLogo)"
238 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
239 SuppressAllWarnings="$(InscribeSuppressAllWarnings)" 247 SuppressAllWarnings="$(InscribeSuppressAllWarnings)"
240 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)" 248 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)"
241 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)" 249 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)"
242 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)" 250 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)"
243 VerboseOutput="$(InscribeVerboseOutput)" 251 VerboseOutput="$(InscribeVerboseOutput)"
244 AdditionalOptions="$(InscribeAdditionalOptions)"> 252 AdditionalOptions="$(InscribeAdditionalOptions)"
253
254 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
255 ToolExe="$(WixToolExe)"
256 ToolPath="$(WixToolDir)">
245 <Output TaskParameter="Output" ItemName="SignBundleEngine" /> 257 <Output TaskParameter="Output" ItemName="SignBundleEngine" />
246 </Insignia> 258 </DetachBundleEngineForSigning>
247 259
248 <!-- Explicitly add output to FileWrites to ensure they are included even when the target is up to date. --> 260 <!-- Explicitly add output to FileWrites to ensure it is included even when the target is up to date. -->
249 <ItemGroup> 261 <ItemGroup>
250 <FileWrites Include="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)" /> 262 <FileWrites Include="$(IntermediateOutputPath)%(SignTargetPath.Filename)%(SignTargetPath.Extension)" />
251 </ItemGroup> 263 </ItemGroup>
@@ -274,22 +286,26 @@
274 Inputs="@(SignTargetPath)" 286 Inputs="@(SignTargetPath)"
275 Outputs="$(SignedFilePath)"> 287 Outputs="$(SignedFilePath)">
276 288
277 <Insignia 289 <ReattachSignedBundleEngine
278 BundleFile="@(SignBundleEngine)" 290 BundleFile="@(SignTargetPath)"
279 OriginalBundleFile="@(SignTargetPath)" 291 BundleEngineFile="@(SignBundleEngine)"
280 OutputFile="@(SignTargetPath)" 292 OutputFile="@(SignTargetPath)"
281 ToolPath="$(WixToolPath)" 293 IntermediateDirectory="%(SignTargetPath.RootDir)%(SignTargetPath.Directory)"
294
282 NoLogo="$(InscribeNoLogo)" 295 NoLogo="$(InscribeNoLogo)"
283 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
284 SuppressAllWarnings="$(InscribeSuppressAllWarnings)" 296 SuppressAllWarnings="$(InscribeSuppressAllWarnings)"
285 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)" 297 SuppressSpecificWarnings="$(InscribeSuppressSpecificWarnings)"
286 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)" 298 TreatWarningsAsErrors="$(InscribeTreatWarningsAsErrors)"
287 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)" 299 TreatSpecificWarningsAsErrors="$(InscribeTreatSpecificWarningsAsErrors)"
288 VerboseOutput="$(InscribeVerboseOutput)" 300 VerboseOutput="$(InscribeVerboseOutput)"
289 AdditionalOptions="$(InscribeAdditionalOptions)"> 301 AdditionalOptions="$(InscribeAdditionalOptions)"
302
303 RunAsSeparateProcess="$(RunWixToolsOutOfProc)"
304 ToolExe="$(WixToolExe)"
305 ToolPath="$(WixToolDir)">
290 <Output TaskParameter="Output" ItemName="SignBundle" /> 306 <Output TaskParameter="Output" ItemName="SignBundle" />
291 <Output TaskParameter="Output" ItemName="FileWrites" /> 307 <Output TaskParameter="Output" ItemName="FileWrites" />
292 </Insignia> 308 </ReattachSignedBundleEngine>
293 </Target> 309 </Target>
294 310
295 <!-- 311 <!--
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/SigningFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/SigningFixture.cs
index 79896172..1da449fd 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/SigningFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/SigningFixture.cs
@@ -62,5 +62,117 @@ namespace WixToolsetTest.CoreIntegration
62 }, rows); 62 }, rows);
63 } 63 }
64 } 64 }
65
66 [Fact]
67 public void CanInscribeBundle()
68 {
69 var folder = TestData.Get(@"TestData", "SimpleBundle");
70 var signedFolder = TestData.Get(@"TestData", ".Data");
71
72 using (var fs = new DisposableFileSystem())
73 {
74 var baseFolder = fs.GetFolder();
75 var intermediateFolder = Path.Combine(baseFolder, "obj");
76 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
77 var signedExe = Path.Combine(intermediateFolder, @"signed.exe");
78 var reattachedExe = Path.Combine(baseFolder, @"bin\final.exe");
79
80 var result = WixRunner.Execute(new[]
81 {
82 "build",
83 Path.Combine(folder, "Bundle.wxs"),
84 "-loc", Path.Combine(folder, "Bundle.en-us.wxl"),
85 "-bindpath", Path.Combine(folder, "data"),
86 "-bindpath", signedFolder,
87 "-intermediateFolder", intermediateFolder,
88 "-o", exePath,
89 });
90
91 result.AssertSuccess();
92
93 result = WixRunner.Execute(new[]
94{
95 "burn",
96 "detach",
97 exePath,
98 "-engine", signedExe
99 });
100
101 result.AssertSuccess();
102
103 // Swap in a pre-signed executable since signing during the unit test
104 // is a challenge. The exe isn't an exact match but that's okay for
105 // these testing purposes.
106 File.Copy(Path.Combine(signedFolder, "signed_bundle_engine.exe"), signedExe, true);
107
108 result = WixRunner.Execute(new[]
109{
110 "burn",
111 "reattach",
112 exePath,
113 "-engine", signedExe,
114 "-o", reattachedExe
115 });
116
117 result.AssertSuccess();
118 Assert.True(File.Exists(reattachedExe));
119 }
120 }
121
122 [Fact]
123 public void CanInscribeUncompressedBundle()
124 {
125 var folder = TestData.Get(@"TestData", "BundleUncompressed");
126 var bindPath = TestData.Get(@"TestData", "SimpleBundle", "data");
127 var signedFolder = TestData.Get(@"TestData", ".Data");
128
129 using (var fs = new DisposableFileSystem())
130 {
131 var baseFolder = fs.GetFolder();
132 var intermediateFolder = Path.Combine(baseFolder, "obj");
133 var exePath = Path.Combine(baseFolder, @"bin\test.exe");
134 var signedExe = Path.Combine(intermediateFolder, @"signed.exe");
135 var reattachedExe = Path.Combine(baseFolder, @"bin\final.exe");
136
137 var result = WixRunner.Execute(new[]
138 {
139 "build",
140 Path.Combine(folder, "UncompressedBundle.wxs"),
141 "-bindpath", bindPath,
142 "-bindpath", signedFolder,
143 "-intermediateFolder", intermediateFolder,
144 "-o", exePath,
145 });
146
147 result.AssertSuccess();
148
149 result = WixRunner.Execute(new[]
150{
151 "burn",
152 "detach",
153 exePath,
154 "-engine", signedExe
155 });
156
157 result.AssertSuccess();
158
159 // Swap in a pre-signed executable since signing during the unit test
160 // is a challenge. The exe isn't an exact match but that's okay for
161 // these testing purposes.
162 File.Copy(Path.Combine(signedFolder, "signed_bundle_engine.exe"), signedExe, true);
163
164 result = WixRunner.Execute(new[]
165{
166 "burn",
167 "reattach",
168 exePath,
169 "-engine", signedExe,
170 "-o", reattachedExe
171 });
172
173 Assert.True(File.Exists(reattachedExe));
174 Assert.Equal(-1000, result.ExitCode);
175 }
176 }
65 } 177 }
66} 178}
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_bundle_engine.exe b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_bundle_engine.exe
new file mode 100644
index 00000000..f4f15e85
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/signed_bundle_engine.exe
Binary files differ