@joebigelow / wix-1 / commits / f46ca6a9

Support remote Payloads

Closes 5601 Closes 6802

Rob Mensching committed Jul 14, 2022 at 06:05 UTC f46ca6a9dce91607ffc9855270dd6998216e1a8b
10 files changed +164 -22
src/api/wix/WixToolset.Data/WarningMessages.cs
+1 -1
@@ -650,7 +650,7 @@ namespace WixToolset.Data
650
651 public static Message UxPayloadsOnlySupportEmbedding(SourceLineNumber sourceLineNumbers, string sourceFile)
652 {
653 - return Message(sourceLineNumbers, Ids.UxPayloadsOnlySupportEmbedding, "A bootstrapper application payload ('{0}') was marked for something other than embedded packaging, possibly because it included a @DownloadUrl attribute. At present, bootstrapper application payloads must be embedded in the bundle, so the requested packaging is being ignored.", sourceFile);
653 + return Message(sourceLineNumbers, Ids.UxPayloadsOnlySupportEmbedding, "A bootstrapper application or bundle extension payload ('{0}') was marked for something other than embedded packaging, possibly because it included a @DownloadUrl attribute. Bootstrapper application and bundle extension payloads must be embedded in the bundle, so the requested packaging is being ignored and the file is being embedded anyway.", sourceFile);
654 }
655
656 public static Message ValidationFailedDueToSystemPolicy()
src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+5 -1
@@ -300,7 +300,11 @@ namespace WixToolset.Core.Burn
300 // downloaded. The current engine requires the UX to be fully present before any downloading starts,
301 // so that rules out downloading. Also, the burn engine does not currently copy external UX payloads
302 // into the temporary UX directory correctly, so we don't allow external either.
303 - if (PackagingType.Embedded != payload.Packaging)
303 + if (payload.SourceFile is null)
304 + {
305 + this.Messaging.Write(BurnBackendErrors.BAContainerCannotContainRemotePayload(payload.SourceLineNumbers, payload.Name));
306 + }
307 + else if (PackagingType.Embedded != payload.Packaging)
308 {
309 this.Messaging.Write(WarningMessages.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.SourceFile.Path));
310 payload.Packaging = PackagingType.Embedded;
src/wix/WixToolset.Core.Burn/BurnBackendErrors.cs
+6
@@ -95,6 +95,11 @@ namespace WixToolset.Core.Burn
95 return Message(sourceLineNumbers, Ids.InvalidBundleManifest, "Unable to read bundle executable '{0}'. Its manifest is invalid. {1}", bundleExecutable, reason);
96 }
97
98 + public static Message BAContainerCannotContainRemotePayload(SourceLineNumber sourceLineNumbers, string payloadName)
99 + {
100 + return Message(sourceLineNumbers, Ids.BAContainerCannotContainRemotePayload, "Bootstrapper application and bundle extension payloads must be embedded in the bundle. The payload '{0}' is remote thus cannot be found for embedding. Provide a full path to the payload via the Payload/@SourceFile attribute.", payloadName);
101 + }
102 +
103 private static Message Message(SourceLineNumber sourceLineNumber, Ids id, string format, params object[] args)
104 {
105 return new Message(sourceLineNumber, MessageLevel.Error, (int)id, format, args);
@@ -116,6 +121,7 @@ namespace WixToolset.Core.Burn
121 FailedToAddIconOrSplashScreenToBundle = 8011,
122 InvalidBundleManifest = 8012,
123 BundleMultipleProviders = 8013,
124 + BAContainerCannotContainRemotePayload = 8014,
125 } // last available is 8499. 8500 is BurnBackendWarnings.
126 }
127 }
src/wix/WixToolset.Core/Compile/CompilerPayload.cs
+8 -5
@@ -247,13 +247,12 @@ namespace WixToolset.Core
247 {
248 this.CalculateAndVerifyFields();
249 this.GenerateIdFromFilename();
250 - this.GenerateIdFromPrefix("ppy");
250 }
251
253 - public void FinishCompilingPayload()
252 + public void FinishCompilingPayload(string parentId)
253 {
254 this.CalculateAndVerifyFields();
256 - this.GenerateIdFromPrefix("pay");
255 + this.GenerateIdFromPrefix("pay", parentId);
256 }
257
258 private void GenerateIdFromFilename()
@@ -268,14 +267,18 @@ namespace WixToolset.Core
267 {
268 this.Id = this.Core.CreateIdentifierFromFilename(Path.GetFileName(this.SourceFile));
269 }
270 + else // if Name and SourceFile were not specified an error was already reported.
271 + {
272 + this.Id = Identifier.Invalid;
273 + }
274 }
275 }
276
274 - private void GenerateIdFromPrefix(string prefix)
277 + private void GenerateIdFromPrefix(string prefix, string parentId)
278 {
279 if (this.Id == null)
280 {
278 - this.Id = this.Core.CreateIdentifier(prefix, this.SourceFile?.ToUpperInvariant() ?? String.Empty);
281 + this.Id = this.Core.CreateIdentifier(prefix, parentId, this.SourceFile?.ToUpperInvariant() ?? this.Name?.ToUpperInvariant());
282 }
283 }
284
src/wix/WixToolset.Core/Compiler_Bundle.cs
+26 -10
@@ -698,7 +698,7 @@ namespace WixToolset.Core
698 this.ParseBootstrapperApplicationDllElement(child, id);
699 break;
700 case "Payload":
701 - this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
701 + this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, isRemoteAllowed: false);
702 break;
703 case "PayloadGroupRef":
704 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
@@ -788,7 +788,7 @@ namespace WixToolset.Core
788 }
789 }
790
791 - compilerPayload.FinishCompilingPayload();
791 + compilerPayload.FinishCompilingPayload(Compiler.BurnUXContainerId.Id);
792
793 // Now that the Id is known, we can parse the extension attributes.
794 var context = new Dictionary<string, string>
@@ -872,7 +872,7 @@ namespace WixToolset.Core
872 switch (child.Name.LocalName)
873 {
874 case "Payload":
875 - this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
875 + this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, isRemoteAllowed: false);
876 break;
877 case "PayloadGroupRef":
878 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
@@ -1237,7 +1237,7 @@ namespace WixToolset.Core
1237 }
1238 }
1239
1240 - compilerPayload.FinishCompilingPayload();
1240 + compilerPayload.FinishCompilingPayload(Compiler.BurnUXContainerId.Id);
1241
1242 // Now that the Id is known, we can parse the extension attributes.
1243 var context = new Dictionary<string, string>
@@ -1259,7 +1259,7 @@ namespace WixToolset.Core
1259 switch (child.Name.LocalName)
1260 {
1261 case "Payload":
1262 - this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
1262 + this.ParsePayloadElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId, isRemoteAllowed: false);
1263 break;
1264 case "PayloadGroupRef":
1265 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Container, Compiler.BurnUXContainerId);
@@ -1393,12 +1393,16 @@ namespace WixToolset.Core
1393 /// <param name="node">Element to parse</param>
1394 /// <param name="parentType">ComplexReferenceParentType of parent element. (BA or PayloadGroup)</param>
1395 /// <param name="parentId">Identifier of parent element.</param>
1396 - private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId)
1396 + /// <param name="isRemoteAllowed">Indicates if the Payload element can be remote or not.</param>
1397 + private Identifier ParsePayloadElement(XElement node, ComplexReferenceParentType parentType, Identifier parentId, bool isRemoteAllowed)
1398 {
1399 Debug.Assert(ComplexReferenceParentType.PayloadGroup == parentType || ComplexReferenceParentType.Package == parentType || ComplexReferenceParentType.Container == parentType);
1400
1401 var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
1401 - var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node);
1402 + var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
1403 + {
1404 + IsRemoteAllowed = isRemoteAllowed
1405 + };
1406
1407 // This list lets us evaluate extension attributes *after* all core attributes
1408 // have been parsed and dealt with, regardless of authoring order.
@@ -1414,12 +1418,24 @@ namespace WixToolset.Core
1418 case "Id":
1419 compilerPayload.ParseId(attrib);
1420 break;
1421 + case "CertificatePublicKey":
1422 + compilerPayload.ParseCertificatePublicKey(attrib);
1423 + break;
1424 + case "CertificateThumbprint":
1425 + compilerPayload.ParseCertificateThumbprint(attrib);
1426 + break;
1427 case "Compressed":
1428 compilerPayload.ParseCompressed(attrib);
1429 break;
1430 + case "Hash":
1431 + compilerPayload.ParseHash(attrib);
1432 + break;
1433 case "Name":
1434 compilerPayload.ParseName(attrib);
1435 break;
1436 + case "Size":
1437 + compilerPayload.ParseSize(attrib);
1438 + break;
1439 case "SourceFile":
1440 compilerPayload.ParseSourceFile(attrib);
1441 break;
@@ -1442,7 +1458,7 @@ namespace WixToolset.Core
1458 }
1459 }
1460
1445 - compilerPayload.FinishCompilingPayload();
1461 + compilerPayload.FinishCompilingPayload(parentId?.Id);
1462
1463 // Now that the PayloadId is known, we can parse the extension attributes.
1464 var context = new Dictionary<string, string>
@@ -1539,7 +1555,7 @@ namespace WixToolset.Core
1555 packageType = WixBundlePackageType.Msu;
1556 break;
1557 case "Payload":
1542 - this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id);
1558 + this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, isRemoteAllowed: true);
1559 break;
1560 case "PayloadGroupRef":
1561 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.PayloadGroup, id);
@@ -2263,7 +2279,7 @@ namespace WixToolset.Core
2279 }
2280 break;
2281 case "Payload":
2266 - this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id);
2282 + this.ParsePayloadElement(child, ComplexReferenceParentType.Package, id, isRemoteAllowed: true);
2283 break;
2284 case "PayloadGroupRef":
2285 this.ParsePayloadGroupRefElement(child, ComplexReferenceParentType.Package, id);
src/wix/test/WixToolsetTest.CoreIntegration/BundlePackageFixture.cs
+2 -2
@@ -103,7 +103,7 @@ namespace WixToolsetTest.CoreIntegration
103 "<Provides Key='MyProviderKey,v1.0' Version='1.0.0.0' DisplayName='BurnBundle' Imported='yes' />" +
104 "<RelatedBundle Id='{B94478B1-E1F3-4700-9CE8-6AA090854AEC}' Action='Upgrade' />" +
105 "<PayloadRef Id='chain.exe' />" +
106 - "<PayloadRef Id='payP6wZpeHEAZbDUQPEKeCpQ_9bN.4' />" +
106 + "<PayloadRef Id='paydfjdGCAZtFxTeTjs0nIscHI86SY' />" +
107 "</BundlePackage>",
108 }, bundlePackages);
109
@@ -175,7 +175,7 @@ namespace WixToolsetTest.CoreIntegration
175 var payloads = extractResult.GetManifestTestXmlLines("/burn:BurnManifest/burn:Payload", ignoreAttributesByElementName);
176 WixAssert.CompareLineByLine(new[]
177 {
178 - "<Payload Id='payP6wZpeHEAZbDUQPEKeCpQ_9bN.4' FilePath='signed_cab1.cab' FileSize='*' Hash='*' Packaging='external' SourcePath='signed_cab1.cab' />",
178 + "<Payload Id='paydfjdGCAZtFxTeTjs0nIscHI86SY' FilePath='signed_cab1.cab' FileSize='*' Hash='*' Packaging='external' SourcePath='signed_cab1.cab' />",
179 "<Payload Id='chain.exe' FilePath='chain.exe' FileSize='*' Hash='*' Packaging='external' SourcePath='chain.exe' />",
180 }, payloads);
181
src/wix/test/WixToolsetTest.CoreIntegration/BurnRemotePayloadSubcommandFixture.cs
+1 -3
@@ -398,7 +398,7 @@ namespace WixToolsetTest.CoreIntegration
398 }
399 }
400
401 - [Fact(Skip = "Blocked by https://github.com/wixtoolset/issues/issues/5601 - Support RemotePayload for Payload elements")]
401 + [Fact]
402 public void CanGetRemotePayloadWithCertificate()
403 {
404 var folder = TestData.Get(@"TestData");
@@ -441,9 +441,7 @@ namespace WixToolsetTest.CoreIntegration
441 var remotePayloadSourceText = "<Wix xmlns='http://wixtoolset.org/schemas/v4/wxs'>" +
442 " <Fragment>" +
443 " <PackageGroup Id='BundlePackages'>" +
444 - " <ExePackage CacheId='xyz'>" +
444 String.Join(Environment.NewLine, elements) +
446 - " </ExePackage>" +
445 " </PackageGroup>" +
446 " </Fragment>" +
447 "</Wix>";
src/wix/test/WixToolsetTest.CoreIntegration/PayloadFixture.cs
+81
@@ -201,5 +201,86 @@ namespace WixToolsetTest.CoreIntegration
201 }, containers);
202 }
203 }
204 +
205 + [Fact]
206 + public void CanBuildBundleWithRemotePackagePaylod()
207 + {
208 + var folder = TestData.Get(@"TestData");
209 +
210 + using (var fs = new DisposableFileSystem())
211 + {
212 + var baseFolder = fs.GetFolder();
213 + var intermediateFolder = Path.Combine(baseFolder, "obj");
214 + var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
215 + var baFolderPath = Path.Combine(baseFolder, "ba");
216 + var extractFolderPath = Path.Combine(baseFolder, "extract");
217 +
218 + var result = WixRunner.Execute(false, new[]
219 + {
220 + "build",
221 + Path.Combine(folder, "Payload", "RemotePayloadInPackage.wxs"),
222 + Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
223 + "-bindpath", Path.Combine(folder, ".Data"),
224 + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
225 + "-intermediateFolder", intermediateFolder,
226 + "-o", bundlePath
227 + });
228 +
229 + result.AssertSuccess();
230 +
231 + Assert.True(File.Exists(bundlePath));
232 +
233 + var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
234 + extractResult.AssertSuccess();
235 +
236 + var payloadElements = extractResult.GetManifestTestXmlLines("/burn:BurnManifest/burn:Payload");
237 + WixAssert.CompareLineByLine(new[]
238 + {
239 + "<Payload Id='payU3zeF5QWZvsWVEfgPVoFiuo65qQ' FilePath='reallybig.dat' FileSize='100000000' Hash='4312abcef' DownloadUrl='example.com/reallybig.dat' Packaging='external' SourcePath='reallybig.dat' />",
240 + "<Payload Id='burn.exe' FilePath='burn.exe' FileSize='463360' Hash='F6E722518AC3AB7E31C70099368D5770788C179AA23226110DCF07319B1E1964E246A1E8AE72E2CF23E0138AFC281BAFDE45969204405E114EB20C8195DA7E5E' Packaging='embedded' SourcePath='a0' Container='WixAttachedContainer' />",
241 + "<Payload Id='payIswsNTCI1qS7UYl_lydLSgHt2Aw' FilePath='fake.txt' FileSize='1' Hash='bcadef' DownloadUrl='example.com/fake.txt' Packaging='external' SourcePath='fake.txt' />",
242 + "<Payload Id='RemotePayloadExe' FilePath='fake.exe' FileSize='1' Hash='a' DownloadUrl='example.com' Packaging='external' SourcePath='fake.exe' />",
243 + }, payloadElements);
244 +
245 + var payloadRefElements = extractResult.GetManifestTestXmlLines("/burn:BurnManifest/burn:Chain/burn:ExePackage/burn:PayloadRef");
246 + WixAssert.CompareLineByLine(new[]
247 + {
248 + "<PayloadRef Id='burn.exe' />",
249 + "<PayloadRef Id='payU3zeF5QWZvsWVEfgPVoFiuo65qQ' />",
250 + "<PayloadRef Id='RemotePayloadExe' />",
251 + "<PayloadRef Id='payIswsNTCI1qS7UYl_lydLSgHt2Aw' />"
252 + }, payloadRefElements);
253 + }
254 + }
255 +
256 + [Fact]
257 + public void CannotBuildRemotePayloadInBootstrapperApplication()
258 + {
259 + var folder = TestData.Get(@"TestData");
260 +
261 + using (var fs = new DisposableFileSystem())
262 + {
263 + var baseFolder = fs.GetFolder();
264 + var intermediateFolder = Path.Combine(baseFolder, "obj");
265 + var bundlePath = Path.Combine(baseFolder, "bin", "test.exe");
266 +
267 + var result = WixRunner.Execute(false, new[]
268 + {
269 + "build",
270 + Path.Combine(folder, "Payload", "RemotePayloadInBootstrapperApplication.wxs"),
271 + Path.Combine(folder, "SimpleBundle", "MultiFileBootstrapperApplication.wxs"),
272 + "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
273 + "-bindpath", Path.Combine(folder, ".Data"),
274 + "-intermediateFolder", intermediateFolder,
275 + "-o", bundlePath,
276 + });
277 +
278 + WixAssert.CompareLineByLine(new[]
279 + {
280 + "The Payload 'RemotePayload' is being added to Container 'WixUXContainer', overriding its Compressed value of 'no'.",
281 + "Bootstrapper application and bundle extension payloads must be embedded in the bundle. The payload 'someremotefile.txt' is remote thus cannot be found for embedding. Provide a full path to the payload via the Payload/@SourceFile attribute."
282 + }, result.Messages.Select(m => m.ToString()).ToArray());
283 + }
284 + }
285 }
286 }
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/RemotePayloadInBootstrapperApplication.wxs new
+17
@@ -0,0 +1,17 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 + <Bundle Name="RemotePayloadInBootstrapperApplication" Version="1.0.0.0" Manufacturer="test" UpgradeCode="{B04C20B8-70C3-4DE1-8D91-4F11C7C68DED}">
3 + <BootstrapperApplicationRef Id="fakeba">
4 + <PayloadGroupRef Id="RemotePayload" />
5 + </BootstrapperApplicationRef>
6 +
7 + <Chain>
8 + <MsiPackage SourceFile="test.msi" DownloadUrl="http://example.com/{0}id/{1}/{2}" Compressed="no" />
9 + </Chain>
10 + </Bundle>
11 +
12 + <Fragment>
13 + <PayloadGroup Id="RemotePayload">
14 + <Payload Id="RemotePayload" Name="someremotefile.txt" DownloadUrl="http://example.com/{0}id/{1}/{2}" Hash="abc" Size="123" />
15 + </PayloadGroup>
16 + </Fragment>
17 +</Wix>
src/wix/test/WixToolsetTest.CoreIntegration/TestData/Payload/RemotePayloadInPackage.wxs new
+17
@@ -0,0 +1,17 @@
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 DetectCondition="ForTestPurposesOnly" SourceFile="burn.exe" Permanent="yes">
6 + <Payload Hash="4312abcef" Size="100000000" Name="reallybig.dat" DownloadUrl="example.com/reallybig.dat" />
7 + </ExePackage>
8 +
9 + <ExePackage Id="RemotePayloadExe" DetectCondition="ForTestPurposesOnly" Description="Override RemotePayload description"
10 + DisplayName="Override RemotePayload display name" Permanent="yes">
11 + <ExePackagePayload Description="RemotePayload description" Hash="a" ProductName="RemotePayload product name"
12 + Size="1" Version="1.0.0.0" Name="fake.exe" DownloadUrl="example.com" />
13 + <Payload Hash="bcadef" Size="1" Name="fake.txt" DownloadUrl="example.com/fake.txt" />
14 + </ExePackage>
15 + </PackageGroup>
16 + </Fragment>
17 +</Wix>