aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs16
-rw-r--r--src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject7
-rw-r--r--src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs11
-rw-r--r--src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs6
-rw-r--r--src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs4
-rw-r--r--src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs272
-rw-r--r--src/wix/WixToolset.Core.Native/CertificateHashes.cs83
-rw-r--r--src/wix/WixToolset.Core.Native/WixNativeExe.cs2
-rw-r--r--src/wix/WixToolset.Core/Compile/CompilerPayload.cs89
-rw-r--r--src/wix/WixToolset.Core/Compiler_Bundle.cs14
-rw-r--r--src/wix/heat/PayloadHarvester.cs129
-rw-r--r--src/wix/heat/UtilHeatExtension.cs8
-rw-r--r--src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject3
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs58
-rw-r--r--src/wix/test/WixToolsetTest.Core.Native/TestData/test.cabbin115 -> 8027 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs122
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs172
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu (renamed from src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu)bin309544 -> 309544 bytes
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs10
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat1
-rw-r--r--src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat1
-rw-r--r--src/wix/test/WixToolsetTest.Heat/PayloadTests.cs66
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exebin463360 -> 0 bytes
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs6
-rw-r--r--src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj4
-rw-r--r--src/wix/wixnative/certhashes.cpp138
-rw-r--r--src/wix/wixnative/precomp.h4
-rw-r--r--src/wix/wixnative/wixnative.cpp4
-rw-r--r--src/wix/wixnative/wixnative.vcxproj3
35 files changed, 1033 insertions, 247 deletions
diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
index 82b75285..be581fb3 100644
--- a/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
+++ b/src/api/wix/WixToolset.Data/Symbols/WixBundlePayloadSymbol.cs
@@ -26,6 +26,8 @@ namespace WixToolset.Data
26 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.LayoutOnly), IntermediateFieldType.Bool), 26 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.LayoutOnly), IntermediateFieldType.Bool),
27 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.Packaging), IntermediateFieldType.Number), 27 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.Packaging), IntermediateFieldType.Number),
28 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.ParentPackagePayloadRef), IntermediateFieldType.String), 28 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.ParentPackagePayloadRef), IntermediateFieldType.String),
29 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificatePublicKey), IntermediateFieldType.String),
30 new IntermediateFieldDefinition(nameof(WixBundlePayloadSymbolFields.CertificateThumbprint), IntermediateFieldType.String),
29 }, 31 },
30 typeof(WixBundlePayloadSymbol)); 32 typeof(WixBundlePayloadSymbol));
31 } 33 }
@@ -53,6 +55,8 @@ namespace WixToolset.Data.Symbols
53 LayoutOnly, 55 LayoutOnly,
54 Packaging, 56 Packaging,
55 ParentPackagePayloadRef, 57 ParentPackagePayloadRef,
58 CertificatePublicKey,
59 CertificateThumbprint,
56 } 60 }
57 61
58 public class WixBundlePayloadSymbol : IntermediateSymbol 62 public class WixBundlePayloadSymbol : IntermediateSymbol
@@ -162,5 +166,17 @@ namespace WixToolset.Data.Symbols
162 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef]; 166 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef];
163 set => this.Set((int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef, value); 167 set => this.Set((int)WixBundlePayloadSymbolFields.ParentPackagePayloadRef, value);
164 } 168 }
169
170 public string CertificatePublicKey
171 {
172 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.CertificatePublicKey];
173 set => this.Set((int)WixBundlePayloadSymbolFields.CertificatePublicKey, value);
174 }
175
176 public string CertificateThumbprint
177 {
178 get => (string)this.Fields[(int)WixBundlePayloadSymbolFields.CertificateThumbprint];
179 set => this.Set((int)WixBundlePayloadSymbolFields.CertificateThumbprint, value);
180 }
165 } 181 }
166} 182}
diff --git a/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject
new file mode 100644
index 00000000..cd54b69b
--- /dev/null
+++ b/src/wix/WixToolset.Converters.Symbolizer/WixToolset.Converters.Symbolizer.v3.ncrunchproject
@@ -0,0 +1,7 @@
1<ProjectConfiguration>
2 <Settings>
3 <HiddenComponentWarnings>
4 <Value>LostReference</Value>
5 </HiddenComponentWarnings>
6 </Settings>
7</ProjectConfiguration> \ No newline at end of file
diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
index c8f10a71..af45e736 100644
--- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
@@ -672,7 +672,16 @@ namespace WixToolset.Core.Burn.Bundles
672 writer.WriteAttributeString("Id", payload.Id.Id); 672 writer.WriteAttributeString("Id", payload.Id.Id);
673 writer.WriteAttributeString("FilePath", payload.Name); 673 writer.WriteAttributeString("FilePath", payload.Name);
674 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture)); 674 writer.WriteAttributeString("FileSize", payload.FileSize.Value.ToString(CultureInfo.InvariantCulture));
675 writer.WriteAttributeString("Hash", payload.Hash); 675
676 if (!String.IsNullOrEmpty(payload.CertificatePublicKey) && !String.IsNullOrEmpty(payload.CertificateThumbprint))
677 {
678 writer.WriteAttributeString("CertificateRootPublicKeyIdentifier", payload.CertificatePublicKey);
679 writer.WriteAttributeString("CertificateRootThumbprint", payload.CertificateThumbprint);
680 }
681 else
682 {
683 writer.WriteAttributeString("Hash", payload.Hash);
684 }
676 685
677 if (payload.LayoutOnly) 686 if (payload.LayoutOnly)
678 { 687 {
diff --git a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
index fd6eb093..74dc4cd5 100644
--- a/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
+++ b/src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
@@ -56,6 +56,11 @@ namespace WixToolset.Core.Burn
56 return Message(null, Ids.IncompatibleWixBurnSection, "Unable to read bundle executable '{0}', because this bundle was created with a different version of WiX burn (.wixburn section version '{1}'). You must use the same version of Windows Installer XML in order to read this bundle.", bundleExecutable, bundleVersion); 56 return Message(null, Ids.IncompatibleWixBurnSection, "Unable to read bundle executable '{0}', because this bundle was created with a different version of WiX burn (.wixburn section version '{1}'). You must use the same version of Windows Installer XML in order to read this bundle.", bundleExecutable, bundleVersion);
57 } 57 }
58 58
59 public static Message UnsupportedRemotePackagePayload(string extension, string path)
60 {
61 return Message(null, Ids.UnsupportedRemotePackagePayload, "The first remote payload must be a supported package type of .exe or .msu. Use the -packageType switch to override the inferred extension: {0} from file: {1}", extension, path);
62 }
63
59 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args) 64 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
60 { 65 {
61 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args); 66 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
@@ -73,6 +78,7 @@ namespace WixToolset.Core.Burn
73 PackageCachePayloadCollision2 = 8007, 78 PackageCachePayloadCollision2 = 8007,
74 TooManyAttachedContainers = 8008, 79 TooManyAttachedContainers = 8008,
75 IncompatibleWixBurnSection = 8009, 80 IncompatibleWixBurnSection = 8009,
81 UnsupportedRemotePackagePayload = 8010,
76 } // last available is 8499. 8500 is BurnBackendWarnings. 82 } // last available is 8499. 8500 is BurnBackendWarnings.
77 } 83 }
78} 84}
diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
index a50fb7cd..b552678f 100644
--- a/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
+++ b/src/wix/WixToolset.Core.Burn/CommandLine/BurnCommand.cs
@@ -52,6 +52,10 @@ namespace WixToolset.Core.Burn.CommandLine
52 case "reattach": 52 case "reattach":
53 this.Subcommand = new ReattachSubcommand(this.ServiceProvider); 53 this.Subcommand = new ReattachSubcommand(this.ServiceProvider);
54 return true; 54 return true;
55
56 case "remotepayload":
57 this.Subcommand = new RemotePayloadSubcommand(this.ServiceProvider);
58 return true;
55 } 59 }
56 60
57 return false; 61 return false;
diff --git a/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs b/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
new file mode 100644
index 00000000..7b362485
--- /dev/null
+++ b/src/wix/WixToolset.Core.Burn/CommandLine/RemotePayloadSubcommand.cs
@@ -0,0 +1,272 @@
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.CommandLine
4{
5 using System;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Threading;
10 using System.Threading.Tasks;
11 using System.Xml.Linq;
12 using WixToolset.Core.Burn.Interfaces;
13 using WixToolset.Core.Native;
14 using WixToolset.Data;
15 using WixToolset.Data.Symbols;
16 using WixToolset.Extensibility.Services;
17
18 internal class RemotePayloadSubcommand : BurnSubcommandBase
19 {
20 private static readonly XName ExePackagePayloadName = "ExePackagePayload";
21 private static readonly XName MsuPackagePayloadName = "MsuPackagePayload";
22 private static readonly XName PayloadName = "Payload";
23
24 public RemotePayloadSubcommand(IServiceProvider serviceProvider)
25 {
26 this.Messaging = serviceProvider.GetService<IMessaging>();
27 this.PayloadHarvester = serviceProvider.GetService<IPayloadHarvester>();
28 }
29
30 private IMessaging Messaging { get; }
31
32 private IPayloadHarvester PayloadHarvester { get; }
33
34 private List<string> BasePaths { get; } = new List<string>();
35
36 private string DownloadUrl { get; set; }
37
38 private List<string> InputPaths { get; } = new List<string>();
39
40 private string OutputPath { get; set; }
41
42 private WixBundlePackageType? PackageType { get; set; }
43
44 private bool Recurse { get; set; }
45
46 private bool UseCertificate { get; set; }
47
48 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
49 {
50 var inputPaths = this.ExpandInputPaths();
51 if (inputPaths.Count == 0)
52 {
53 Console.Error.WriteLine("Path to a remote payload is required");
54 return Task.FromResult(-1);
55 }
56
57 // Reverse sort to ensure longest paths are matched first.
58 this.BasePaths.Sort();
59 this.BasePaths.Reverse();
60
61 var elements = this.HarvestRemotePayloads(inputPaths);
62
63 if (!this.Messaging.EncounteredError)
64 {
65 if (!String.IsNullOrEmpty(this.OutputPath))
66 {
67 var outputFolder = Path.GetDirectoryName(this.OutputPath);
68 Directory.CreateDirectory(outputFolder);
69
70 File.WriteAllLines(this.OutputPath, elements.Select(e => e.ToString()));
71 }
72 else
73 {
74 foreach (var element in elements)
75 {
76 Console.WriteLine(element);
77 }
78 }
79 }
80
81 return Task.FromResult(this.Messaging.LastErrorNumber);
82 }
83
84 public override bool TryParseArgument(ICommandLineParser parser, string argument)
85 {
86 if (parser.IsSwitch(argument))
87 {
88 var parameter = argument.Substring(1);
89 switch (parameter.ToLowerInvariant())
90 {
91 case "bp":
92 case "basepath":
93 this.BasePaths.Add(parser.GetNextArgumentAsDirectoryOrError(argument));
94 return true;
95
96 case "du":
97 case "downloadurl":
98 this.DownloadUrl = parser.GetNextArgumentOrError(argument);
99 return true;
100
101 case "packagetype":
102 var packageTypeValue = parser.GetNextArgumentOrError(argument);
103 if (Enum.TryParse<WixBundlePackageType>(packageTypeValue, ignoreCase: true, out var packageType))
104 {
105 this.PackageType = packageType;
106 return true;
107 }
108 break;
109
110 case "o":
111 case "out":
112 this.OutputPath = parser.GetNextArgumentAsFilePathOrError(argument);
113 return true;
114
115 case "r":
116 case "recurse":
117 this.Recurse = true;
118 return true;
119
120 case "usecertificate":
121 this.UseCertificate = true;
122 return true;
123 }
124 }
125 else
126 {
127 this.InputPaths.Add(argument);
128 return true;
129 }
130
131 return false;
132 }
133
134 private IReadOnlyCollection<string> ExpandInputPaths()
135 {
136 var result = new List<string>();
137
138 foreach (var inputPath in this.InputPaths)
139 {
140 var filename = Path.GetFileName(inputPath);
141 var folder = Path.GetDirectoryName(inputPath);
142
143 if (String.IsNullOrEmpty(folder))
144 {
145 folder = ".";
146 }
147
148 foreach (var path in Directory.EnumerateFiles(folder, filename, this.Recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
149 {
150 result.Add(path);
151 }
152 }
153
154 return result.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
155 }
156
157 private IEnumerable<XElement> HarvestRemotePayloads(IEnumerable<string> paths)
158 {
159 var first = true;
160 var hashes = new Dictionary<string, CertificateHashes>();
161
162 if (this.UseCertificate)
163 {
164 hashes = CertificateHashes.Read(paths).Where(c => !String.IsNullOrEmpty(c.PublicKey) && !String.IsNullOrEmpty(c.Thumbprint) && c.Exception is null).ToDictionary(c => c.Path);
165 }
166
167 foreach (var path in paths)
168 {
169 var element = this.CreateRemotePayloadElement(path, first);
170 first = false;
171
172 if (element == null)
173 {
174 continue;
175 }
176
177 var payloadSymbol = new WixBundlePayloadSymbol
178 {
179 SourceFile = new IntermediateFieldPathValue { Path = path },
180 };
181
182 this.PayloadHarvester.HarvestStandardInformation(payloadSymbol);
183
184 element.Add(new XAttribute("Name", Path.GetFileName(path)));
185
186 if (!String.IsNullOrEmpty(payloadSymbol.DisplayName))
187 {
188 element.Add(new XAttribute("ProductName", payloadSymbol.DisplayName));
189 }
190
191 if (!String.IsNullOrEmpty(payloadSymbol.Description))
192 {
193 element.Add(new XAttribute("Description", payloadSymbol.Description));
194 }
195
196 if (!String.IsNullOrEmpty(this.DownloadUrl))
197 {
198 var filename = this.GetRelativeFileName(payloadSymbol.SourceFile.Path);
199 var formattedUrl = String.Format(this.DownloadUrl, filename);
200
201 if (Uri.TryCreate(formattedUrl, UriKind.Absolute, out var url))
202 {
203 element.Add(new XAttribute("DownloadUrl", url.AbsoluteUri));
204 }
205 }
206
207 if (hashes.TryGetValue(path, out var certificateHashes))
208 {
209 element.Add(new XAttribute("CertificatePublicKey", certificateHashes.PublicKey));
210 element.Add(new XAttribute("CertificateThumbprint", certificateHashes.Thumbprint));
211 }
212
213 if (!String.IsNullOrEmpty(payloadSymbol.Hash))
214 {
215 element.Add(new XAttribute("Hash", payloadSymbol.Hash));
216 }
217
218 if (payloadSymbol.FileSize.HasValue)
219 {
220 element.Add(new XAttribute("Size", payloadSymbol.FileSize.Value));
221 }
222
223 if (!String.IsNullOrEmpty(payloadSymbol.Version))
224 {
225 element.Add(new XAttribute("Version", payloadSymbol.Version));
226 }
227
228 yield return element;
229 }
230 }
231
232 private XElement CreateRemotePayloadElement(string path, bool firstHarvest)
233 {
234 if (firstHarvest)
235 {
236 var extension = this.PackageType.HasValue ? this.PackageType.ToString() : Path.GetExtension(path);
237
238 switch (extension.ToUpperInvariant())
239 {
240 case "EXE":
241 case ".EXE":
242 return new XElement(ExePackagePayloadName);
243
244 case "MSU":
245 case ".MSU":
246 return new XElement(MsuPackagePayloadName);
247
248 default:
249 this.Messaging.Write(BurnBackendErrors.UnsupportedRemotePackagePayload(extension, path));
250 return null;
251 }
252 }
253 else
254 {
255 return new XElement(PayloadName);
256 }
257 }
258
259 private string GetRelativeFileName(string path)
260 {
261 foreach (var basePath in this.BasePaths)
262 {
263 if (path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase))
264 {
265 return path.Substring(basePath.Length).TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
266 }
267 }
268
269 return Path.GetFileName(path);
270 }
271 }
272}
diff --git a/src/wix/WixToolset.Core.Native/CertificateHashes.cs b/src/wix/WixToolset.Core.Native/CertificateHashes.cs
new file mode 100644
index 00000000..a024c923
--- /dev/null
+++ b/src/wix/WixToolset.Core.Native/CertificateHashes.cs
@@ -0,0 +1,83 @@
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.Native
4{
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.Linq;
10
11 /// <summary>
12 /// Read certificates' public key and thumbprint hashes.
13 /// </summary>
14 public sealed class CertificateHashes
15 {
16 private static readonly char[] TextLineSplitter = new[] { '\t' };
17
18 private CertificateHashes(string path, string publicKey, string thumbprint, Exception exception)
19 {
20 this.Path = path;
21 this.PublicKey = publicKey;
22 this.Thumbprint = thumbprint;
23 this.Exception = exception;
24 }
25
26 /// <summary>
27 /// Path to the file read.
28 /// </summary>
29 public string Path { get; }
30
31 /// <summary>
32 /// Hash of the certificate's public key.
33 /// </summary>
34 public string PublicKey { get; }
35
36 /// <summary>
37 /// Hash of the certificate's thumbprint.
38 /// </summary>
39 public string Thumbprint { get; }
40
41 /// <summary>
42 /// Exception encountered while trying to read certificate's hash.
43 /// </summary>
44 public Exception Exception { get; }
45
46 /// <summary>
47 /// Read the certificate hashes from the provided paths.
48 /// </summary>
49 /// <param name="paths">Paths to read for certificates.</param>
50 /// <returns>Certificate hashes for the provided paths.</returns>
51 public static IReadOnlyList<CertificateHashes> Read(IEnumerable<string> paths)
52 {
53 var result = new List<CertificateHashes>();
54
55 var wixnative = new WixNativeExe("certhashes");
56
57 foreach (var path in paths)
58 {
59 wixnative.AddStdinLine(path);
60 }
61
62 try
63 {
64 var outputLines = wixnative.Run();
65 foreach (var line in outputLines.Where(l => !String.IsNullOrEmpty(l)))
66 {
67 var data = line.Split(TextLineSplitter, StringSplitOptions.None);
68
69 var error = Int32.Parse(data[3].Substring(2), NumberStyles.HexNumber);
70 var exception = error != 0 ? new Win32Exception(error) : null;
71
72 result.Add(new CertificateHashes(data[0], data[1], data[2], exception));
73 }
74 }
75 catch (Exception e)
76 {
77 result.Add(new CertificateHashes(null, null, null, e));
78 }
79
80 return result;
81 }
82 }
83}
diff --git a/src/wix/WixToolset.Core.Native/WixNativeExe.cs b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
index 13d7a4ea..f5ff8bf0 100644
--- a/src/wix/WixToolset.Core.Native/WixNativeExe.cs
+++ b/src/wix/WixToolset.Core.Native/WixNativeExe.cs
@@ -30,7 +30,7 @@ namespace WixToolset.Core.Native
30 this.stdinLines.AddRange(lines); 30 this.stdinLines.AddRange(lines);
31 } 31 }
32 32
33 public IEnumerable<string> Run() 33 public IReadOnlyCollection<string> Run()
34 { 34 {
35 EnsurePathToWixNativeExeSet(); 35 EnsurePathToWixNativeExeSet();
36 36
diff --git a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
index ea63ee8c..cee9b377 100644
--- a/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
+++ b/src/wix/WixToolset.Core/Compile/CompilerPayload.cs
@@ -11,12 +11,31 @@ namespace WixToolset.Core
11 11
12 internal class CompilerPayload 12 internal class CompilerPayload
13 { 13 {
14 public string Version { get; set; }
15
16 public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element)
17 {
18 this.Core = core;
19 this.Element = element;
20 this.SourceLineNumbers = sourceLineNumbers;
21 }
22
23 private CompilerCore Core { get; }
24
25 private XElement Element { get; }
26
27 private SourceLineNumber SourceLineNumbers { get; }
28
14 public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default; 29 public YesNoDefaultType Compressed { get; set; } = YesNoDefaultType.Default;
15 30
16 public string Description { get; set; } 31 public string Description { get; set; }
17 32
18 public string DownloadUrl { get; set; } 33 public string DownloadUrl { get; set; }
19 34
35 public string CertificatePublicKey { get; set; }
36
37 public string CertificateThumbprint { get; set; }
38
20 public string Hash { get; set; } 39 public string Hash { get; set; }
21 40
22 public Identifier Id { get; set; } 41 public Identifier Id { get; set; }
@@ -33,24 +52,9 @@ namespace WixToolset.Core
33 52
34 public string SourceFile { get; set; } 53 public string SourceFile { get; set; }
35 54
36 public string Version { get; set; }
37
38 public CompilerPayload(CompilerCore core, SourceLineNumber sourceLineNumbers, XElement element)
39 {
40 this.Core = core;
41 this.Element = element;
42 this.SourceLineNumbers = sourceLineNumbers;
43 }
44
45 private CompilerCore Core { get; }
46
47 private XElement Element { get; }
48
49 private SourceLineNumber SourceLineNumbers { get; }
50
51 private void CalculateAndVerifyFields() 55 private void CalculateAndVerifyFields()
52 { 56 {
53 var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash); 57 var isRemote = this.IsRemoteAllowed && (!String.IsNullOrEmpty(this.CertificatePublicKey) || !String.IsNullOrEmpty(this.CertificateThumbprint) || !String.IsNullOrEmpty(this.Hash));
54 58
55 if (String.IsNullOrEmpty(this.SourceFile)) 59 if (String.IsNullOrEmpty(this.SourceFile))
56 { 60 {
@@ -81,7 +85,7 @@ namespace WixToolset.Core
81 } 85 }
82 else 86 else
83 { 87 {
84 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash")); 88 this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "CertificatePublicKey", "Hash"));
85 } 89 }
86 } 90 }
87 } 91 }
@@ -93,7 +97,20 @@ namespace WixToolset.Core
93 { 97 {
94 if (isRemote) 98 if (isRemote)
95 { 99 {
96 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile")); 100 if (!String.IsNullOrEmpty(this.Hash))
101 {
102 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
103 }
104
105 if (!String.IsNullOrEmpty(this.CertificatePublicKey))
106 {
107 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificatePublicKey", "SourceFile"));
108 }
109
110 if (!String.IsNullOrEmpty(this.CertificateThumbprint))
111 {
112 this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificateThumbprint", "SourceFile"));
113 }
97 } 114 }
98 115
99 if (!String.IsNullOrEmpty(this.Description)) 116 if (!String.IsNullOrEmpty(this.Description))
@@ -120,17 +137,34 @@ namespace WixToolset.Core
120 { 137 {
121 if (String.IsNullOrEmpty(this.DownloadUrl)) 138 if (String.IsNullOrEmpty(this.DownloadUrl))
122 { 139 {
123 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash")); 140 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "SourceFile"));
124 } 141 }
125 142
126 if (String.IsNullOrEmpty(this.Name)) 143 if (String.IsNullOrEmpty(this.Name))
127 { 144 {
128 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash")); 145 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile"));
129 } 146 }
130 147
131 if (!this.Size.HasValue) 148 if (!this.Size.HasValue)
132 { 149 {
133 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "Hash")); 150 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Size", "SourceFile"));
151 }
152
153 if (String.IsNullOrEmpty(this.Hash))
154 {
155 this.Core.Write(ErrorMessages.ExpectedAttributeWithoutOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
156 }
157
158 if (!String.IsNullOrEmpty(this.CertificatePublicKey) || !String.IsNullOrEmpty(this.CertificateThumbprint))
159 {
160 if (String.IsNullOrEmpty(this.CertificateThumbprint))
161 {
162 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificateThumbprint", "CertificatePublicKey"));
163 }
164 else if (String.IsNullOrEmpty(this.CertificatePublicKey))
165 {
166 this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "CertificatePublicKey", "CertificateThumbprint"));
167 }
134 } 168 }
135 169
136 if (YesNoDefaultType.Yes == this.Compressed) 170 if (YesNoDefaultType.Yes == this.Compressed)
@@ -177,6 +211,8 @@ namespace WixToolset.Core
177 Hash = this.Hash, 211 Hash = this.Hash,
178 FileSize = this.Size, 212 FileSize = this.Size,
179 Version = this.Version, 213 Version = this.Version,
214 CertificatePublicKey = this.CertificatePublicKey,
215 CertificateThumbprint = this.CertificateThumbprint
180 }); 216 });
181 217
182 this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, ComplexReferenceChildType.Unknown, null); 218 this.Core.CreateGroupAndOrderingRows(this.SourceLineNumbers, parentType, parentId, ComplexReferenceChildType.Payload, symbol.Id.Id, ComplexReferenceChildType.Unknown, null);
@@ -248,6 +284,16 @@ namespace WixToolset.Core
248 this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 284 this.DownloadUrl = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
249 } 285 }
250 286
287 public void ParseCertificatePublicKey(XAttribute attrib)
288 {
289 this.CertificatePublicKey = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
290 }
291
292 public void ParseCertificateThumbprint(XAttribute attrib)
293 {
294 this.CertificateThumbprint = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
295 }
296
251 public void ParseHash(XAttribute attrib) 297 public void ParseHash(XAttribute attrib)
252 { 298 {
253 this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 299 this.Hash = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
@@ -286,6 +332,5 @@ namespace WixToolset.Core
286 { 332 {
287 this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib); 333 this.Version = this.Core.GetAttributeValue(this.SourceLineNumbers, attrib);
288 } 334 }
289
290 } 335 }
291} 336}
diff --git a/src/wix/WixToolset.Core/Compiler_Bundle.cs b/src/wix/WixToolset.Core/Compiler_Bundle.cs
index c8b78243..3fde990e 100644
--- a/src/wix/WixToolset.Core/Compiler_Bundle.cs
+++ b/src/wix/WixToolset.Core/Compiler_Bundle.cs
@@ -2459,6 +2459,20 @@ namespace WixToolset.Core
2459 case "SourceFile": 2459 case "SourceFile":
2460 compilerPayload.ParseSourceFile(attrib); 2460 compilerPayload.ParseSourceFile(attrib);
2461 break; 2461 break;
2462 case "CertificatePublicKey":
2463 allowed = compilerPayload.IsRemoteAllowed;
2464 if (allowed)
2465 {
2466 compilerPayload.ParseCertificatePublicKey(attrib);
2467 }
2468 break;
2469 case "CertificateThumbprint":
2470 allowed = compilerPayload.IsRemoteAllowed;
2471 if (allowed)
2472 {
2473 compilerPayload.ParseCertificateThumbprint(attrib);
2474 }
2475 break;
2462 case "DownloadUrl": 2476 case "DownloadUrl":
2463 compilerPayload.ParseDownloadUrl(attrib); 2477 compilerPayload.ParseDownloadUrl(attrib);
2464 break; 2478 break;
diff --git a/src/wix/heat/PayloadHarvester.cs b/src/wix/heat/PayloadHarvester.cs
deleted file mode 100644
index d2492512..00000000
--- a/src/wix/heat/PayloadHarvester.cs
+++ /dev/null
@@ -1,129 +0,0 @@
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.Harvesters
4{
5 using System;
6 using System.IO;
7 using WixToolset.Core.Burn.Interfaces;
8 using WixToolset.Data;
9 using WixToolset.Data.Symbols;
10 using WixToolset.Harvesters.Data;
11 using WixToolset.Harvesters.Extensibility;
12 using Wix = WixToolset.Harvesters.Serialize;
13
14 /// <summary>
15 /// Harvest WiX authoring for a payload from the file system.
16 /// </summary>
17 public sealed class PayloadHarvester : BaseHarvesterExtension
18 {
19 private bool setUniqueIdentifiers;
20 private WixBundlePackageType packageType;
21
22 private IPayloadHarvester payloadHarvester;
23
24 /// <summary>
25 /// Instantiate a new PayloadHarvester.
26 /// </summary>
27 public PayloadHarvester(IPayloadHarvester payloadHarvester, WixBundlePackageType packageType)
28 {
29 this.payloadHarvester = payloadHarvester;
30
31 this.packageType = packageType;
32 this.setUniqueIdentifiers = true;
33 }
34
35 /// <summary>
36 /// Gets of sets the option to set unique identifiers.
37 /// </summary>
38 /// <value>The option to set unique identifiers.</value>
39 public bool SetUniqueIdentifiers
40 {
41 get { return this.setUniqueIdentifiers; }
42 set { this.setUniqueIdentifiers = value; }
43 }
44
45 /// <summary>
46 /// Harvest a payload.
47 /// </summary>
48 /// <param name="argument">The path of the payload.</param>
49 /// <returns>A harvested payload.</returns>
50 public override Wix.Fragment[] Harvest(string argument)
51 {
52 if (null == argument)
53 {
54 throw new ArgumentNullException("argument");
55 }
56
57 string fullPath = Path.GetFullPath(argument);
58
59 var remotePayload = this.HarvestRemotePayload(fullPath);
60
61 var fragment = new Wix.Fragment();
62 fragment.AddChild(remotePayload);
63
64 return new Wix.Fragment[] { fragment };
65 }
66
67 /// <summary>
68 /// Harvest a payload.
69 /// </summary>
70 /// <param name="path">The path of the payload.</param>
71 /// <returns>A harvested payload.</returns>
72 public Wix.RemotePayload HarvestRemotePayload(string path)
73 {
74 if (null == path)
75 {
76 throw new ArgumentNullException("path");
77 }
78
79 if (!File.Exists(path))
80 {
81 throw new WixException(HarvesterErrors.FileNotFound(path));
82 }
83
84 Wix.RemotePayload remotePayload;
85
86 switch (this.packageType)
87 {
88 case WixBundlePackageType.Exe:
89 remotePayload = new Wix.ExePackagePayload();
90 break;
91 case WixBundlePackageType.Msu:
92 remotePayload = new Wix.MsuPackagePayload();
93 break;
94 default:
95 throw new NotImplementedException();
96 }
97
98 var payloadSymbol = new WixBundlePayloadSymbol
99 {
100 SourceFile = new IntermediateFieldPathValue { Path = path },
101 };
102
103 this.payloadHarvester.HarvestStandardInformation(payloadSymbol);
104
105 if (payloadSymbol.FileSize.HasValue)
106 {
107 remotePayload.Size = payloadSymbol.FileSize.Value;
108 }
109 remotePayload.Hash = payloadSymbol.Hash;
110
111 if (!String.IsNullOrEmpty(payloadSymbol.Version))
112 {
113 remotePayload.Version = payloadSymbol.Version;
114 }
115
116 if (!String.IsNullOrEmpty(payloadSymbol.Description))
117 {
118 remotePayload.Description = payloadSymbol.Description;
119 }
120
121 if (!String.IsNullOrEmpty(payloadSymbol.DisplayName))
122 {
123 remotePayload.ProductName = payloadSymbol.DisplayName;
124 }
125
126 return remotePayload;
127 }
128 }
129}
diff --git a/src/wix/heat/UtilHeatExtension.cs b/src/wix/heat/UtilHeatExtension.cs
index e5be9cac..5ad5ef8a 100644
--- a/src/wix/heat/UtilHeatExtension.cs
+++ b/src/wix/heat/UtilHeatExtension.cs
@@ -87,14 +87,6 @@ namespace WixToolset.Harvesters
87 harvesterExtension = new FileHarvester(); 87 harvesterExtension = new FileHarvester();
88 active = true; 88 active = true;
89 break; 89 break;
90 case "exepackagepayload":
91 harvesterExtension = new PayloadHarvester(this.PayloadHarvester, WixBundlePackageType.Exe);
92 active = true;
93 break;
94 case "msupackagepayload":
95 harvesterExtension = new PayloadHarvester(this.PayloadHarvester, WixBundlePackageType.Msu);
96 active = true;
97 break;
98 case "perf": 90 case "perf":
99 harvesterExtension = new PerformanceCategoryHarvester(); 91 harvesterExtension = new PerformanceCategoryHarvester();
100 active = true; 92 active = true;
diff --git a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
index 18ab4f79..b8c570ae 100644
--- a/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
+++ b/src/wix/test/WixToolsetTest.Converters.Symbolizer/WixToolsetTest.Converters.Symbolizer.v3.ncrunchproject
@@ -1,5 +1,8 @@
1<ProjectConfiguration> 1<ProjectConfiguration>
2 <Settings> 2 <Settings>
3 <HiddenComponentWarnings>
4 <Value>LostReference</Value>
5 </HiddenComponentWarnings>
3 <IgnoredTests> 6 <IgnoredTests>
4 <NamedTestSelector> 7 <NamedTestSelector>
5 <TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName> 8 <TestName>WixToolsetTest.Converters.Symbolizer.ConvertSymbolsFixture.CanLoadWixoutAndConvertToIntermediate</TestName>
diff --git a/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs b/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs
new file mode 100644
index 00000000..5783445b
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.Core.Native/CertificateHashesFixture.cs
@@ -0,0 +1,58 @@
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 WixToolsetTest.CoreNative
4{
5 using System.ComponentModel;
6 using System.Linq;
7 using WixToolset.Core.Native;
8 using WixToolsetTest.CoreNative.Utility;
9 using Xunit;
10
11 public class CertificateHashesFixture
12 {
13 [Fact]
14 public void CanGetHashesFromSignedFile()
15 {
16 var cabFile = TestData.Get(@"TestData\test.cab");
17
18 var hashes = CertificateHashes.Read(new[] { cabFile });
19
20 var hash = hashes.Single();
21 Assert.Equal(cabFile, hash.Path);
22 Assert.Equal("7EC90B3FC3D580EB571210011F1095E149DCC6BB", hash.PublicKey);
23 Assert.Equal("0B13494DB50BC185A34389BBBAA01EDD1CF56350", hash.Thumbprint);
24 Assert.Null(hash.Exception);
25 }
26
27 [Fact]
28 public void CannotGetHashesFromUnsignedFile()
29 {
30 var txtFile = TestData.Get(@"TestData\test.txt");
31
32 var hashes = CertificateHashes.Read(new[] { txtFile });
33
34 var hash = hashes.Single();
35 Assert.Equal(txtFile, hash.Path);
36 Assert.Null(hash.Exception);
37 }
38
39 [Fact]
40 public void CanGetMultipleHashes()
41 {
42 var cabFile = TestData.Get(@"TestData\test.cab");
43 var txtFile = TestData.Get(@"TestData\test.txt");
44
45 var hashes = CertificateHashes.Read(new[] { cabFile, txtFile });
46
47 Assert.Equal(cabFile, hashes[0].Path);
48 Assert.Equal("7EC90B3FC3D580EB571210011F1095E149DCC6BB", hashes[0].PublicKey);
49 Assert.Equal("0B13494DB50BC185A34389BBBAA01EDD1CF56350", hashes[0].Thumbprint);
50 Assert.Null(hashes[0].Exception);
51
52 Assert.Equal(txtFile, hashes[1].Path);
53 Assert.Empty(hashes[1].PublicKey);
54 Assert.Empty(hashes[1].Thumbprint);
55 Assert.Null(hashes[1].Exception);
56 }
57 }
58}
diff --git a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
index ca78f632..9700cd64 100644
--- a/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
+++ b/src/wix/test/WixToolsetTest.Core.Native/TestData/test.cab
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
index 6b2d8bfa..5d0c8561 100644
--- a/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
@@ -57,6 +57,26 @@ namespace WixToolsetTest.CoreIntegration
57 } 57 }
58 58
59 [Fact] 59 [Fact]
60 public void CanSpecifyPackagePayloadWithCertificate()
61 {
62 var folder = TestData.Get(@"TestData", "PackagePayload");
63
64 using (var fs = new DisposableFileSystem())
65 {
66 var baseFolder = fs.GetFolder();
67
68 var result = WixRunner.Execute(new[]
69 {
70 "build",
71 Path.Combine(folder, "SpecifiedCertificate.wxs"),
72 "-o", Path.Combine(baseFolder, "test.wixlib")
73 });
74
75 result.AssertSuccess();
76 }
77 }
78
79 [Fact]
60 public void ErrorWhenMissingSourceFileAndHash() 80 public void ErrorWhenMissingSourceFileAndHash()
61 { 81 {
62 var folder = TestData.Get(@"TestData", "PackagePayload"); 82 var folder = TestData.Get(@"TestData", "PackagePayload");
@@ -75,7 +95,7 @@ namespace WixToolsetTest.CoreIntegration
75 Assert.Equal(44, result.ExitCode); 95 Assert.Equal(44, result.ExitCode);
76 WixAssert.CompareLineByLine(new[] 96 WixAssert.CompareLineByLine(new[]
77 { 97 {
78 "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.", 98 "The MsuPackagePayload element's SourceFile, CertificatePublicKey, or Hash attribute was not found; one of these is required.",
79 }, result.Messages.Select(m => m.ToString()).ToArray()); 99 }, result.Messages.Select(m => m.ToString()).ToArray());
80 } 100 }
81 } 101 }
@@ -144,10 +164,10 @@ namespace WixToolsetTest.CoreIntegration
144 "-o", Path.Combine(baseFolder, "test.wixlib") 164 "-o", Path.Combine(baseFolder, "test.wixlib")
145 }); 165 });
146 166
147 Assert.Equal(10, result.ExitCode); 167 Assert.Equal(408, result.ExitCode);
148 WixAssert.CompareLineByLine(new[] 168 WixAssert.CompareLineByLine(new[]
149 { 169 {
150 "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.", 170 "The MsuPackagePayload element's DownloadUrl attribute was not found; it is required without attribute SourceFile present.",
151 }, result.Messages.Select(m => m.ToString()).ToArray()); 171 }, result.Messages.Select(m => m.ToString()).ToArray());
152 } 172 }
153 } 173 }
@@ -177,6 +197,102 @@ namespace WixToolsetTest.CoreIntegration
177 } 197 }
178 198
179 [Fact] 199 [Fact]
200 public void ErrorWhenSpecifiedSourceFileAndCertificatePublicKey()
201 {
202 var folder = TestData.Get(@"TestData", "PackagePayload");
203
204 using (var fs = new DisposableFileSystem())
205 {
206 var baseFolder = fs.GetFolder();
207
208 var result = WixRunner.Execute(new[]
209 {
210 "build",
211 Path.Combine(folder, "SpecifiedSourceFileAndCertificatePublicKey.wxs"),
212 "-o", Path.Combine(baseFolder, "test.wixlib")
213 });
214
215 WixAssert.CompareLineByLine(new[]
216 {
217 "The ExePackagePayload/@CertificatePublicKey attribute cannot be specified when attribute SourceFile is present.",
218 }, result.Messages.Select(m => m.ToString()).ToArray());
219 Assert.Equal(35, result.ExitCode);
220 }
221 }
222
223 [Fact]
224 public void ErrorWhenSpecifiedSourceFileAndCertificateThumprint()
225 {
226 var folder = TestData.Get(@"TestData", "PackagePayload");
227
228 using (var fs = new DisposableFileSystem())
229 {
230 var baseFolder = fs.GetFolder();
231
232 var result = WixRunner.Execute(new[]
233 {
234 "build",
235 Path.Combine(folder, "SpecifiedSourceFileAndCertificateThumbprint.wxs"),
236 "-o", Path.Combine(baseFolder, "test.wixlib")
237 });
238
239 WixAssert.CompareLineByLine(new[]
240 {
241 "The ExePackagePayload/@CertificateThumbprint attribute cannot be specified when attribute SourceFile is present.",
242 }, result.Messages.Select(m => m.ToString()).ToArray());
243 Assert.Equal(35, result.ExitCode);
244 }
245 }
246
247 [Fact]
248 public void ErrorWhenSpecifiedCertificateThumbprintWithoutPublicKey()
249 {
250 var folder = TestData.Get(@"TestData", "PackagePayload");
251
252 using (var fs = new DisposableFileSystem())
253 {
254 var baseFolder = fs.GetFolder();
255
256 var result = WixRunner.Execute(new[]
257 {
258 "build",
259 Path.Combine(folder, "SpecifiedHashAndCertificatePublicKey.wxs"),
260 "-o", Path.Combine(baseFolder, "test.wixlib")
261 });
262
263 WixAssert.CompareLineByLine(new[]
264 {
265 "The ExePackagePayload/@CertificatePublicKey attribute was not found; it is required when attribute CertificateThumbprint is specified.",
266 }, result.Messages.Select(m => m.ToString()).ToArray());
267 Assert.Equal(10, result.ExitCode);
268 }
269 }
270
271 [Fact]
272 public void ErrorWhenSpecifiedCertificatePublicKeyWithoutThumbprint()
273 {
274 var folder = TestData.Get(@"TestData", "PackagePayload");
275
276 using (var fs = new DisposableFileSystem())
277 {
278 var baseFolder = fs.GetFolder();
279
280 var result = WixRunner.Execute(new[]
281 {
282 "build",
283 Path.Combine(folder, "SpecifiedCertificatePublicKeyWithoutThumbprint.wxs"),
284 "-o", Path.Combine(baseFolder, "test.wixlib")
285 });
286
287 WixAssert.CompareLineByLine(new[]
288 {
289 "The ExePackagePayload/@CertificateThumbprint attribute was not found; it is required when attribute CertificatePublicKey is specified.",
290 }, result.Messages.Select(m => m.ToString()).ToArray());
291 Assert.Equal(10, result.ExitCode);
292 }
293 }
294
295 [Fact]
180 public void ErrorWhenWrongPackagePayloadInPayloadGroup() 296 public void ErrorWhenWrongPackagePayloadInPayloadGroup()
181 { 297 {
182 var folder = TestData.Get(@"TestData"); 298 var folder = TestData.Get(@"TestData");
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs b/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs
new file mode 100644
index 00000000..434b3621
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/RemotePayloadFixture.cs
@@ -0,0 +1,172 @@
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 WixToolsetTest.CoreIntegration
4{
5 using System.IO;
6 using System.Linq;
7 using WixBuildTools.TestSupport;
8 using WixToolset.Core.TestPackage;
9 using Xunit;
10
11 public class RemotePayloadFixture
12 {
13 [Fact]
14 public void CanGetRemotePayload()
15 {
16 var folder = TestData.Get(@"TestData");
17
18 using (var fs = new DisposableFileSystem())
19 {
20 var outputFolder = fs.GetFolder();
21 var outFile = Path.Combine(outputFolder, "out.xml");
22
23 var result = WixRunner.Execute(new[]
24 {
25 "burn", "remotepayload",
26 Path.Combine(folder, ".Data", "burn.exe"),
27 "-downloadurl", "https://www.example.com/files/{0}",
28 "-o", outFile
29 });
30
31 result.AssertSuccess();
32
33 var elements = File.ReadAllLines(outFile);
34 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
35
36 WixAssert.CompareLineByLine(new[]
37 {
38 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadUrl='https://www.example.com/files/burn.exe' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
39 }, elements);
40 }
41 }
42
43 [Fact]
44 public void CanGetRemoteMsuPayload()
45 {
46 var folder = TestData.Get(@"TestData");
47
48 using (var fs = new DisposableFileSystem())
49 {
50 var outputFolder = fs.GetFolder();
51 var outFile = Path.Combine(outputFolder, "out.xml");
52
53 var result = WixRunner.Execute(new[]
54 {
55 "burn", "remotepayload",
56 Path.Combine(folder, ".Data", "Windows8.1-KB2937592-x86.msu"),
57 "-o", outFile
58 });
59
60 result.AssertSuccess();
61
62 var elements = File.ReadAllLines(outFile);
63 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
64
65 WixAssert.CompareLineByLine(new[]
66 {
67 @"<MsuPackagePayload Name='Windows8.1-KB2937592-x86.msu' Hash='904ADEA6AB675ACE16483138BF3F5850FD56ACB6E3A13AFA7263ED49C68CCE6CF84D6AAD6F99AAF175A95EE1A56C787C5AD968019056490B1073E7DBB7B9B7BE' Size='309544' />",
68 }, elements);
69 }
70 }
71
72 [Fact]
73 public void CanGetRemotePayloadWithCertificate()
74 {
75 var folder = TestData.Get(@"TestData");
76
77 using (var fs = new DisposableFileSystem())
78 {
79 var outputFolder = fs.GetFolder();
80 var outFile = Path.Combine(outputFolder, "out.xml");
81
82 var result = WixRunner.Execute(new[]
83 {
84 "burn", "remotepayload",
85 "-usecertificate",
86 Path.Combine(folder, ".Data", "burn.exe"),
87 Path.Combine(folder, ".Data", "signed_cab1.cab"),
88 "-o", outFile
89 });
90
91 result.AssertSuccess();
92
93 var elements = File.ReadAllLines(outFile);
94 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
95
96 WixAssert.CompareLineByLine(new[]
97 {
98 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
99 @"<Payload Name='signed_cab1.cab' CertificatePublicKey='BBD1B48A37503767C71F455624967D406A5D66C3' CertificateThumbprint='DE13B4CE635E3F63AA2394E66F95C460267BC82F' Hash='D8D3842403710E1F6036A62543224855CADF546853933C2B17BA99D789D4347B36717687C022678A9D3DE749DFC1482DAAB92B997B62BB32A8A6828B9D04C414' Size='1585' />",
100 }, elements);
101 }
102 }
103
104 [Fact]
105 public void CanGetRemotePayloadWithoutCertificate()
106 {
107 var folder = TestData.Get(@"TestData");
108
109 using (var fs = new DisposableFileSystem())
110 {
111 var outputFolder = fs.GetFolder();
112 var outFile = Path.Combine(outputFolder, "out.xml");
113
114 var result = WixRunner.Execute(new[]
115 {
116 "burn", "remotepayload",
117 Path.Combine(folder, ".Data", "burn.exe"),
118 Path.Combine(folder, ".Data", "signed_cab1.cab"),
119 "-o", outFile
120 });
121
122 result.AssertSuccess();
123
124 var elements = File.ReadAllLines(outFile);
125 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
126
127 WixAssert.CompareLineByLine(new[]
128 {
129 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
130 @"<Payload Name='signed_cab1.cab' Hash='D8D3842403710E1F6036A62543224855CADF546853933C2B17BA99D789D4347B36717687C022678A9D3DE749DFC1482DAAB92B997B62BB32A8A6828B9D04C414' Size='1585' />",
131 }, elements);
132 }
133 }
134
135 [Fact]
136 public void CanGetRemotePayloadsRecursive()
137 {
138 var folder = TestData.Get(@"TestData");
139
140 using (var fs = new DisposableFileSystem())
141 {
142 var outputFolder = fs.GetFolder();
143 var outFile = Path.Combine(outputFolder, "out.xml");
144
145 var result = WixRunner.Execute(new[]
146 {
147 "burn", "remotepayload",
148 "-recurse",
149 "-du", "https://www.example.com/files/{0}",
150 Path.Combine(folder, ".Data", "burn.exe"),
151 Path.Combine(folder, "RemotePayload", "*"),
152 "-basepath", folder,
153 "-bp", Path.Combine(folder, ".Data"),
154 "-o", outFile
155 });
156
157 result.AssertSuccess();
158
159 var elements = File.ReadAllLines(outFile);
160 elements = elements.Select(s => s.Replace("\"", "'")).ToArray();
161
162 WixAssert.CompareLineByLine(new[]
163 {
164 @"<ExePackagePayload Name='burn.exe' ProductName='Windows Installer XML Toolset' Description='WiX Toolset Bootstrapper' DownloadUrl='https://www.example.com/files/burn.exe' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Size='463360' Version='3.14.1703.0' />",
165 @"<Payload Name='a.dat' DownloadUrl='https://www.example.com/files/RemotePayload/a.dat' Hash='D13926E5CBE5ED8B46133F9199FAF2FF25B25981C67A31AE2BC3F6C20390FACBFADCD89BD22D3445D95B989C8EACFB1E68DB634BECB5C9624865BA453BCE362A' Size='16' />",
166 @"<Payload Name='b.dat' DownloadUrl='https://www.example.com/files/RemotePayload/subfolder/b.dat' Hash='5F94707BC29ADFE3B9615E6753388707FD0B8F5FD9EEEC2B17E21E72F1635FF7D7A101E7D14F614E111F263CB9AC4D0940BE1247881A7844F226D6C400293D8E' Size='37' />",
167 @"<Payload Name='c.dat' DownloadUrl='https://www.example.com/files/RemotePayload/subfolder/c.dat' Hash='97D6209A5571E05E4F72F9C6BF0987651FA03E63F971F9B53C2B3D798A666D9864F232D4E2D6442E47D9D72B282309B6EEFF4EE017B43B706FA92A0F5EF74734' Size='42' />",
168 }, elements);
169 }
170 }
171 }
172}
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu
index c39f53b0..c39f53b0 100644
--- a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/Windows8.1-KB2937592-x86.msu
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/.Data/Windows8.1-KB2937592-x86.msu
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs
new file mode 100644
index 00000000..b5dec9a2
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificate.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload CertificatePublicKey="abcd" CertificateThumbprint="abcd" Hash="1234" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs
new file mode 100644
index 00000000..aa915a31
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedCertificatePublicKeyWithoutThumbprint.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload CertificatePublicKey="abcd" Hash="123" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs
new file mode 100644
index 00000000..5570017e
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndCertificatePublicKey.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload Hash="abcd" CertificateThumbprint="abcd" DownloadUrl="https://example.com/" Name="fake.exe" Size="100" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs
new file mode 100644
index 00000000..b79b5596
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificatePublicKey.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload SourceFile="example.exe" CertificatePublicKey="abcd" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs
new file mode 100644
index 00000000..ad2ab521
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndCertificateThumbprint.wxs
@@ -0,0 +1,10 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <PackageGroup Id="BundlePackages">
5 <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
6 <ExePackagePayload SourceFile="example.exe" CertificateThumbprint="abcd" />
7 </ExePackage>
8 </PackageGroup>
9 </Fragment>
10</Wix>
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat
new file mode 100644
index 00000000..a96b19b3
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/a.dat
@@ -0,0 +1 @@
This is a.dat.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat
new file mode 100644
index 00000000..35c4c043
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/b.dat
@@ -0,0 +1 @@
This is b.dat. A little bit longer.
diff --git a/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat
new file mode 100644
index 00000000..937aa91f
--- /dev/null
+++ b/src/wix/test/WixToolsetTest.CoreIntegration/TestData/RemotePayload/subfolder/c.dat
@@ -0,0 +1 @@
This is c.dat. A little bit longer, now!
diff --git a/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs b/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs
deleted file mode 100644
index 8072f50d..00000000
--- a/src/wix/test/WixToolsetTest.Heat/PayloadTests.cs
+++ /dev/null
@@ -1,66 +0,0 @@
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 WixToolsetTest.Harvesters
4{
5 using System;
6 using System.IO;
7 using WixBuildTools.TestSupport;
8 using Xunit;
9
10 public class PayloadTests
11 {
12 [Fact]
13 public void CanHarvestExePackagePayload()
14 {
15 var folder = TestData.Get(@"TestData");
16
17 using (var fs = new DisposableFileSystem())
18 {
19 var baseFolder = fs.GetFolder();
20 var outputFilePath = Path.Combine(baseFolder, "test.wxs");
21
22 var result = HeatRunner.Execute(new[]
23 {
24 "exepackagepayload",
25 Path.Combine(folder, ".Data", "burn.exe"),
26 "-o", outputFilePath,
27 });
28
29 result.AssertSuccess();
30
31 Assert.True(File.Exists(outputFilePath));
32
33 var expected = File.ReadAllText(Path.Combine(folder, "Payload", "HarvestedExePackagePayload.wxs")).Replace("\r\n", "\n");
34 var actual = File.ReadAllText(outputFilePath).Replace("\r\n", "\n");
35 Assert.Equal(expected, actual);
36 }
37 }
38
39 [Fact]
40 public void CanHarvestMsuPackagePayload()
41 {
42 var folder = TestData.Get(@"TestData");
43
44 using (var fs = new DisposableFileSystem())
45 {
46 var baseFolder = fs.GetFolder();
47 var outputFilePath = Path.Combine(baseFolder, "test.wxs");
48
49 var result = HeatRunner.Execute(new[]
50 {
51 "msupackagepayload",
52 Path.Combine(folder, ".Data", "Windows8.1-KB2937592-x86.msu"),
53 "-o", outputFilePath,
54 });
55
56 result.AssertSuccess();
57
58 Assert.True(File.Exists(outputFilePath));
59
60 var expected = File.ReadAllText(Path.Combine(folder, "Payload", "HarvestedMsuPackagePayload.wxs")).Replace("\r\n", "\n");
61 var actual = File.ReadAllText(outputFilePath).Replace("\r\n", "\n");
62 Assert.Equal(expected, actual);
63 }
64 }
65 }
66}
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe b/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe
deleted file mode 100644
index 2a4f423f..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/.Data/burn.exe
+++ /dev/null
Binary files differ
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs b/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs
deleted file mode 100644
index 40100f22..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedExePackagePayload.wxs
+++ /dev/null
@@ -1,6 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <ExePackagePayload Description="WiX Toolset Bootstrapper" Hash="F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E" ProductName="Windows Installer XML Toolset" Size="463360" Version="3.14.1703.0" />
5 </Fragment>
6</Wix> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs b/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs
deleted file mode 100644
index f203fe27..00000000
--- a/src/wix/test/WixToolsetTest.Heat/TestData/Payload/HarvestedMsuPackagePayload.wxs
+++ /dev/null
@@ -1,6 +0,0 @@
1<?xml version="1.0" encoding="utf-8"?>
2<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
3 <Fragment>
4 <MsuPackagePayload Hash="904ADEA6AB675ACE16483138BF3F5850FD56ACB6E3A13AFA7263ED49C68CCE6CF84D6AAD6F99AAF175A95EE1A56C787C5AD968019056490B1073E7DBB7B9B7BE" Size="309544" />
5 </Fragment>
6</Wix> \ No newline at end of file
diff --git a/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj b/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
index 2a706aa0..1ba62393 100644
--- a/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
+++ b/src/wix/test/WixToolsetTest.Heat/WixToolsetTest.Heat.csproj
@@ -9,10 +9,6 @@
9 </PropertyGroup> 9 </PropertyGroup>
10 10
11 <ItemGroup> 11 <ItemGroup>
12 <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
13 </ItemGroup>
14
15 <ItemGroup>
16 <ProjectReference Include="..\..\heat\heat.csproj" /> 12 <ProjectReference Include="..\..\heat\heat.csproj" />
17 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" /> 13 <ProjectReference Include="..\..\WixToolset.Core.TestPackage\WixToolset.Core.TestPackage.csproj" />
18 </ItemGroup> 14 </ItemGroup>
diff --git a/src/wix/wixnative/certhashes.cpp b/src/wix/wixnative/certhashes.cpp
new file mode 100644
index 00000000..cbb548ba
--- /dev/null
+++ b/src/wix/wixnative/certhashes.cpp
@@ -0,0 +1,138 @@
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
3#include "precomp.h"
4
5#define SHA1_HASH_LEN 20
6
7static HRESULT GetPublicKeyIdentifierAndThumbprint(
8 __in_z LPCWSTR wzPath,
9 __inout_z LPWSTR* psczPublicKeyIdentifier,
10 __inout_z LPWSTR* psczThumbprint);
11
12static HRESULT GetChainContext(
13 __in_z LPCWSTR wzPath,
14 __out PCCERT_CHAIN_CONTEXT* ppChainContext);
15
16
17HRESULT CertificateHashesCommand(
18 __in int argc,
19 __in_ecount(argc) LPWSTR argv[])
20{
21 Unused(argc);
22 Unused(argv);
23
24 HRESULT hr = S_OK;
25
26 LPWSTR sczFilePath = NULL;
27 LPWSTR sczPublicKeyIdentifier = NULL;
28 LPWSTR sczThumbprint = NULL;
29
30 hr = WixNativeReadStdinPreamble();
31 ExitOnFailure(hr, "Failed to read stdin preamble before reading paths to get certificate hashes");
32
33 // Get the hash for each provided file.
34 for (;;)
35 {
36 hr = ConsoleReadW(&sczFilePath);
37 ConsoleExitOnFailure(hr, CONSOLE_COLOR_RED, "Failed to read file path to signed file from stdin");
38
39 if (!*sczFilePath)
40 {
41 break;
42 }
43
44 hr = GetPublicKeyIdentifierAndThumbprint(sczFilePath, &sczPublicKeyIdentifier, &sczThumbprint);
45 if (FAILED(hr))
46 {
47 // Treat no signature as success without finding certificate hashes.
48 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t\t\t0x%x", sczFilePath, TRUST_E_NOSIGNATURE == hr ? 0 : hr);
49 }
50 else
51 {
52 ConsoleWriteLine(CONSOLE_COLOR_NORMAL, "%ls\t%ls\t%ls\t0x%x", sczFilePath, sczPublicKeyIdentifier, sczThumbprint, hr);
53 }
54 }
55
56LExit:
57 ReleaseStr(sczThumbprint);
58 ReleaseStr(sczPublicKeyIdentifier);
59 ReleaseStr(sczFilePath);
60
61 return hr;
62}
63
64static HRESULT GetPublicKeyIdentifierAndThumbprint(
65 __in_z LPCWSTR wzPath,
66 __inout_z LPWSTR* psczPublicKeyIdentifier,
67 __inout_z LPWSTR* psczThumbprint)
68{
69 HRESULT hr = S_OK;
70 PCCERT_CHAIN_CONTEXT pChainContext = NULL;
71 PCCERT_CONTEXT pCertContext = NULL;
72 BYTE rgbPublicKeyIdentifier[SHA1_HASH_LEN] = { };
73 DWORD cbPublicKeyIdentifier = sizeof(rgbPublicKeyIdentifier);
74 BYTE* pbThumbprint = NULL;
75 DWORD cbThumbprint = 0;
76
77 hr = GetChainContext(wzPath, &pChainContext);
78 ExitOnFailure(hr, "Failed to get chain context for file: %ls", wzPath);
79
80 pCertContext = pChainContext->rgpChain[0]->rgpElement[0]->pCertContext;
81
82 // Get the certificate's public key identifier and thumbprint.
83 if (!::CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING, &pCertContext->pCertInfo->SubjectPublicKeyInfo, rgbPublicKeyIdentifier, &cbPublicKeyIdentifier))
84 {
85 ExitWithLastError(hr, "Failed to get certificate public key identifier from file: %ls", wzPath);
86 }
87
88 hr = CertReadProperty(pCertContext, CERT_SHA1_HASH_PROP_ID, &pbThumbprint, &cbThumbprint);
89 ExitOnFailure(hr, "Failed to read certificate thumbprint from file: %ls", wzPath);
90
91 // Get the public key indentifier and thumbprint in hex.
92 hr = StrAllocHexEncode(rgbPublicKeyIdentifier, cbPublicKeyIdentifier, psczPublicKeyIdentifier);
93 ExitOnFailure(hr, "Failed to convert certificate public key to hex for file: %ls", wzPath);
94
95 hr = StrAllocHexEncode(pbThumbprint, cbThumbprint, psczThumbprint);
96 ExitOnFailure(hr, "Failed to convert certificate thumbprint to hex for file: %ls", wzPath);
97
98LExit:
99 ReleaseMem(pbThumbprint);
100 return hr;
101}
102
103static HRESULT GetChainContext(
104 __in_z LPCWSTR wzPath,
105 __out PCCERT_CHAIN_CONTEXT* ppChainContext)
106{
107 HRESULT hr = S_OK;
108
109 GUID guidAuthenticode = WINTRUST_ACTION_GENERIC_VERIFY_V2;
110 WINTRUST_FILE_INFO wfi = { };
111 WINTRUST_DATA wtd = { };
112 CRYPT_PROVIDER_DATA* pProviderData = NULL;
113 CRYPT_PROVIDER_SGNR* pSigner = NULL;
114
115 wfi.cbStruct = sizeof(wfi);
116 wfi.pcwszFilePath = wzPath;
117
118 wtd.cbStruct = sizeof(wtd);
119 wtd.dwUnionChoice = WTD_CHOICE_FILE;
120 wtd.pFile = &wfi;
121 wtd.dwStateAction = WTD_STATEACTION_VERIFY;
122 wtd.dwProvFlags = WTD_REVOCATION_CHECK_NONE | WTD_HASH_ONLY_FLAG | WTD_CACHE_ONLY_URL_RETRIEVAL;
123 wtd.dwUIChoice = WTD_UI_NONE;
124
125 hr = ::WinVerifyTrust(static_cast<HWND>(INVALID_HANDLE_VALUE), &guidAuthenticode, &wtd);
126 ExitOnFailure(hr, "Failed to verify certificate on file: %ls", wzPath);
127
128 pProviderData = ::WTHelperProvDataFromStateData(wtd.hWVTStateData);
129 ExitOnNullWithLastError(pProviderData, hr, "Failed to get provider state from authenticode certificate on file: %ls", wzPath);
130
131 pSigner = ::WTHelperGetProvSignerFromChain(pProviderData, 0, FALSE, 0);
132 ExitOnNullWithLastError(pSigner, hr, "Failed to get signer chain from authenticode certificate on file: %ls", wzPath);
133
134 *ppChainContext = pSigner->pChainContext;
135
136LExit:
137 return hr;
138}
diff --git a/src/wix/wixnative/precomp.h b/src/wix/wixnative/precomp.h
index 490bbdcf..0a458ca6 100644
--- a/src/wix/wixnative/precomp.h
+++ b/src/wix/wixnative/precomp.h
@@ -4,9 +4,12 @@
4#include <windows.h> 4#include <windows.h>
5#include <aclapi.h> 5#include <aclapi.h>
6#include <mergemod.h> 6#include <mergemod.h>
7#include <softpub.h>
7#include <strsafe.h> 8#include <strsafe.h>
9#include <wintrust.h>
8 10
9#include "dutil.h" 11#include "dutil.h"
12#include "certutil.h"
10#include "conutil.h" 13#include "conutil.h"
11#include "memutil.h" 14#include "memutil.h"
12#include "pathutil.h" 15#include "pathutil.h"
@@ -15,6 +18,7 @@
15#include "cabutil.h" 18#include "cabutil.h"
16 19
17HRESULT WixNativeReadStdinPreamble(); 20HRESULT WixNativeReadStdinPreamble();
21HRESULT CertificateHashesCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
18HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 22HRESULT SmartCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
19HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 23HRESULT ResetAclsCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
20HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]); 24HRESULT EnumCabCommand(__in int argc, __in_ecount(argc) LPWSTR argv[]);
diff --git a/src/wix/wixnative/wixnative.cpp b/src/wix/wixnative/wixnative.cpp
index d1236da1..8a24d5f1 100644
--- a/src/wix/wixnative/wixnative.cpp
+++ b/src/wix/wixnative/wixnative.cpp
@@ -24,6 +24,10 @@ int __cdecl wmain(int argc, LPWSTR argv[])
24 { 24 {
25 hr = EnumCabCommand(argc - 2, argv + 2); 25 hr = EnumCabCommand(argc - 2, argv + 2);
26 } 26 }
27 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"certhashes", -1))
28 {
29 hr = CertificateHashesCommand(argc - 2, argv + 2);
30 }
27 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1)) 31 else if (CSTR_EQUAL == ::CompareString(LOCALE_INVARIANT, NORM_IGNORECASE, argv[1], -1, L"resetacls", -1))
28 { 32 {
29 hr = ResetAclsCommand(argc - 2, argv + 2); 33 hr = ResetAclsCommand(argc - 2, argv + 2);
diff --git a/src/wix/wixnative/wixnative.vcxproj b/src/wix/wixnative/wixnative.vcxproj
index 1dac36e2..2e90661c 100644
--- a/src/wix/wixnative/wixnative.vcxproj
+++ b/src/wix/wixnative/wixnative.vcxproj
@@ -42,7 +42,7 @@
42 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> 42 <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
43 43
44 <PropertyGroup> 44 <PropertyGroup>
45 <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib</ProjectAdditionalLinkLibraries> 45 <ProjectAdditionalLinkLibraries>crypt32.lib;cabinet.lib;msi.lib;wintrust.lib</ProjectAdditionalLinkLibraries>
46 </PropertyGroup> 46 </PropertyGroup>
47 47
48 <ItemGroup> 48 <ItemGroup>
@@ -50,6 +50,7 @@
50 <ClCompile Include="precomp.cpp"> 50 <ClCompile Include="precomp.cpp">
51 <PrecompiledHeader>Create</PrecompiledHeader> 51 <PrecompiledHeader>Create</PrecompiledHeader>
52 </ClCompile> 52 </ClCompile>
53 <ClCompile Include="certhashes.cpp" />
53 <ClCompile Include="enumcab.cpp" /> 54 <ClCompile Include="enumcab.cpp" />
54 <ClCompile Include="extractcab.cpp" /> 55 <ClCompile Include="extractcab.cpp" />
55 <ClCompile Include="resetacls.cpp" /> 56 <ClCompile Include="resetacls.cpp" />