From 8b3488c8c77959f425d0e5f70d27c5b2b1c86125 Mon Sep 17 00:00:00 2001
From: Sean Hall <r.sean.hall@gmail.com>
Date: Sun, 28 Feb 2021 21:05:49 -0600
Subject: Use new PackagePayload symbols for the package's payload.

#4183
---
 src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs |   7 +-
 .../Bind/GenerateManifestDataFromIRCommand.cs      |   4 +
 .../Bundles/CreateBurnManifestCommand.cs           |   5 +-
 .../Bundles/GetPackageFacadesCommand.cs            | 111 ++++++++-
 src/WixToolset.Core/Compile/CompilerPayload.cs     |  93 +++++--
 src/WixToolset.Core/Compiler_Bundle.cs             | 268 ++++++++++++++-------
 src/WixToolset.Core/Linker.cs                      |   2 +-
 .../PackagePayloadFixture.cs                       | 207 ++++++++++++++++
 .../PackagePayload/MissingSourceFileAndHash.wxs    |  10 +
 .../PackagePayload/MissingSourceFileAndName.wxs    |  10 +
 .../PackagePayloadInPayloadGroup.wxs               |  15 ++
 .../TestData/PackagePayload/SpecifiedHash.wxs      |  10 +
 .../SpecifiedHashAndMissingDownloadUrl.wxs         |  10 +
 .../PackagePayload/SpecifiedSourceFileAndHash.wxs  |  10 +
 .../WrongPackagePayloadInPayloadGroup.wxs          |  15 ++
 .../SingleExeBundle/SingleExeRemotePayload.wxs     |   9 +-
 16 files changed, 670 insertions(+), 116 deletions(-)
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs
 create mode 100644 src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs

diff --git a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
index 724dd7ff..12a530ae 100644
--- a/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/BindBundleCommand.cs
@@ -180,12 +180,17 @@ namespace WixToolset.Core.Burn
 
             IDictionary<string, PackageFacade> facades;
             {
-                var command = new GetPackageFacadesCommand(chainPackageSymbols, section);
+                var command = new GetPackageFacadesCommand(this.Messaging, chainPackageSymbols, section);
                 command.Execute();
 
                 facades = command.PackageFacades;
             }
 
+            if (this.Messaging.EncounteredError)
+            {
+                return;
+            }
+
             // Process each package facade. Note this is likely to add payloads and other symbols so
             // note that any indexes created above may be out of date now.
             foreach (var facade in facades.Values)
diff --git a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
index 36ced6cf..d4a69513 100644
--- a/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
+++ b/src/WixToolset.Core.Burn/Bind/GenerateManifestDataFromIRCommand.cs
@@ -66,12 +66,16 @@ namespace WixToolset.Core.Burn.Bind
                     case SymbolDefinitionType.WixBundleContainer:
                     case SymbolDefinitionType.WixBundleCustomDataAttribute:
                     case SymbolDefinitionType.WixBundleExePackage:
+                    case SymbolDefinitionType.WixBundleExePackagePayload:
                     case SymbolDefinitionType.WixBundleExtension:
                     case SymbolDefinitionType.WixBundleMsiFeature:
                     case SymbolDefinitionType.WixBundleMsiPackage:
+                    case SymbolDefinitionType.WixBundleMsiPackagePayload:
                     case SymbolDefinitionType.WixBundleMsiProperty:
                     case SymbolDefinitionType.WixBundleMspPackage:
+                    case SymbolDefinitionType.WixBundleMspPackagePayload:
                     case SymbolDefinitionType.WixBundleMsuPackage:
+                    case SymbolDefinitionType.WixBundleMsuPackagePayload:
                     case SymbolDefinitionType.WixBundlePackage:
                     case SymbolDefinitionType.WixBundlePackageCommandLine:
                     case SymbolDefinitionType.WixBundlePackageExitCode:
diff --git a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
index 3bc6bf1b..1559a646 100644
--- a/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs
@@ -564,15 +564,16 @@ namespace WixToolset.Core.Burn.Bundles
                     }
 
                     // Write any contained Payloads with the PackagePayload being first
+                    var packagePayloadId = package.PackageSymbol.PayloadRef;
                     writer.WriteStartElement("PayloadRef");
-                    writer.WriteAttributeString("Id", package.PackageSymbol.PayloadRef);
+                    writer.WriteAttributeString("Id", packagePayloadId);
                     writer.WriteEndElement();
 
                     var packagePayloads = payloadsByPackage[package.PackageId];
 
                     foreach (var payload in packagePayloads)
                     {
-                        if (payload.Id.Id != package.PackageSymbol.PayloadRef)
+                        if (payload.Id.Id != packagePayloadId)
                         {
                             writer.WriteStartElement("PayloadRef");
                             writer.WriteAttributeString("Id", payload.Id.Id);
diff --git a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs
index 24d1e8d8..dacff364 100644
--- a/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs
+++ b/src/WixToolset.Core.Burn/Bundles/GetPackageFacadesCommand.cs
@@ -6,17 +6,21 @@ namespace WixToolset.Core.Burn.Bundles
     using System.Linq;
     using WixToolset.Data;
     using WixToolset.Data.Symbols;
+    using WixToolset.Extensibility.Services;
 
     internal class GetPackageFacadesCommand
     {
-        public GetPackageFacadesCommand(IEnumerable<WixBundlePackageSymbol> chainPackageSymbols, IntermediateSection section)
+        public GetPackageFacadesCommand(IMessaging messaging, IEnumerable<WixBundlePackageSymbol> chainPackageSymbols, IntermediateSection section)
         {
+            this.Messaging = messaging;
             this.ChainPackageSymbols = chainPackageSymbols;
             this.Section = section;
         }
 
         private IEnumerable<WixBundlePackageSymbol> ChainPackageSymbols { get; }
 
+        private IMessaging Messaging { get; }
+
         private IntermediateSection Section { get; }
 
         public IDictionary<string, PackageFacade> PackageFacades { get; private set; }
@@ -27,12 +31,101 @@ namespace WixToolset.Core.Burn.Bundles
             var msiPackages = this.Section.Symbols.OfType<WixBundleMsiPackageSymbol>().ToDictionary(t => t.Id.Id);
             var mspPackages = this.Section.Symbols.OfType<WixBundleMspPackageSymbol>().ToDictionary(t => t.Id.Id);
             var msuPackages = this.Section.Symbols.OfType<WixBundleMsuPackageSymbol>().ToDictionary(t => t.Id.Id);
+            var exePackagePayloads = this.Section.Symbols.OfType<WixBundleExePackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
+            var msiPackagePayloads = this.Section.Symbols.OfType<WixBundleMsiPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
+            var mspPackagePayloads = this.Section.Symbols.OfType<WixBundleMspPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
+            var msuPackagePayloads = this.Section.Symbols.OfType<WixBundleMsuPackagePayloadSymbol>().ToDictionary(t => t.Id.Id);
 
             var facades = new Dictionary<string, PackageFacade>();
 
             foreach (var package in this.ChainPackageSymbols)
             {
                 var id = package.Id.Id;
+
+                IntermediateSymbol packagePayload = null;
+                foreach (var wixGroup in this.Section.Symbols.OfType<WixGroupSymbol>().Where(g => g.ParentType == ComplexReferenceParentType.Package && g.ParentId == id))
+                {
+                    if (wixGroup.ChildType == ComplexReferenceChildType.PackagePayload)
+                    {
+                        IntermediateSymbol tempPackagePayload = null;
+                        if (exePackagePayloads.TryGetValue(wixGroup.ChildId, out var exePackagePayload))
+                        {
+                            if (package.Type == WixBundlePackageType.Exe)
+                            {
+                                tempPackagePayload = exePackagePayload;
+                            }
+                            else
+                            {
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(exePackagePayload.SourceLineNumbers, "Exe"));
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
+                            }
+                        }
+                        else if (msiPackagePayloads.TryGetValue(wixGroup.ChildId, out var msiPackagePayload))
+                        {
+                            if (package.Type == WixBundlePackageType.Msi)
+                            {
+                                tempPackagePayload = msiPackagePayload;
+                            }
+                            else
+                            {
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msiPackagePayload.SourceLineNumbers, "Msi"));
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
+                            }
+                        }
+                        else if (mspPackagePayloads.TryGetValue(wixGroup.ChildId, out var mspPackagePayload))
+                        {
+                            if (package.Type == WixBundlePackageType.Msp)
+                            {
+                                tempPackagePayload = mspPackagePayload;
+                            }
+                            else
+                            {
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(mspPackagePayload.SourceLineNumbers, "Msp"));
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
+                            }
+                        }
+                        else if (msuPackagePayloads.TryGetValue(wixGroup.ChildId, out var msuPackagePayload))
+                        {
+                            if (package.Type == WixBundlePackageType.Msu)
+                            {
+                                tempPackagePayload = msuPackagePayload;
+                            }
+                            else
+                            {
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported(msuPackagePayload.SourceLineNumbers, "Msu"));
+                                this.Messaging.Write(ErrorMessages.PackagePayloadUnsupported2(package.SourceLineNumbers));
+                            }
+                        }
+                        else
+                        {
+                            this.Messaging.Write(ErrorMessages.IdentifierNotFound(package.Type + "PackagePayload", wixGroup.ChildId));
+                        }
+
+                        if (tempPackagePayload != null)
+                        {
+                            if (packagePayload == null)
+                            {
+                                packagePayload = tempPackagePayload;
+                            }
+                            else
+                            {
+                                this.Messaging.Write(ErrorMessages.MultiplePackagePayloads(tempPackagePayload.SourceLineNumbers, id, packagePayload.Id.Id, tempPackagePayload.Id.Id));
+                                this.Messaging.Write(ErrorMessages.MultiplePackagePayloads2(packagePayload.SourceLineNumbers));
+                                this.Messaging.Write(ErrorMessages.MultiplePackagePayloads3(package.SourceLineNumbers));
+                            }
+                        }
+                    }
+                }
+
+                if (packagePayload == null)
+                {
+                    this.Messaging.Write(ErrorMessages.MissingPackagePayload(package.SourceLineNumbers, id, package.Type.ToString()));
+                }
+                else
+                {
+                    package.PayloadRef = packagePayload.Id.Id;
+                }
+
                 switch (package.Type)
                 {
                 case WixBundlePackageType.Exe:
@@ -40,6 +133,10 @@ namespace WixToolset.Core.Burn.Bundles
                     {
                         facades.Add(id, new PackageFacade(package, exePackage));
                     }
+                    else
+                    {
+                        this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleExePackage", id));
+                    }
                     break;
 
                 case WixBundlePackageType.Msi:
@@ -47,6 +144,10 @@ namespace WixToolset.Core.Burn.Bundles
                     {
                         facades.Add(id, new PackageFacade(package, msiPackage));
                     }
+                    else
+                    {
+                        this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsiPackage", id));
+                    }
                     break;
 
                 case WixBundlePackageType.Msp:
@@ -54,6 +155,10 @@ namespace WixToolset.Core.Burn.Bundles
                     {
                         facades.Add(id, new PackageFacade(package, mspPackage));
                     }
+                    else
+                    {
+                        this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMspPackage", id));
+                    }
                     break;
 
                 case WixBundlePackageType.Msu:
@@ -61,6 +166,10 @@ namespace WixToolset.Core.Burn.Bundles
                     {
                         facades.Add(id, new PackageFacade(package, msuPackage));
                     }
+                    else
+                    {
+                        this.Messaging.Write(ErrorMessages.IdentifierNotFound("WixBundleMsuPackage", id));
+                    }
                     break;
                 }
             }
diff --git a/src/WixToolset.Core/Compile/CompilerPayload.cs b/src/WixToolset.Core/Compile/CompilerPayload.cs
index 4eda56f8..7a5fd1b2 100644
--- a/src/WixToolset.Core/Compile/CompilerPayload.cs
+++ b/src/WixToolset.Core/Compile/CompilerPayload.cs
@@ -23,6 +23,8 @@ namespace WixToolset.Core
 
         public Identifier Id { get; set; }
 
+        public bool IsRemoteAllowed { get; set; }
+
         public bool IsRequired { get; set; } = true;
 
         public string Name { get; set; }
@@ -48,26 +50,17 @@ namespace WixToolset.Core
 
         private SourceLineNumber SourceLineNumbers { get; }
 
-        private void CalculateAndVerifyFields(CompilerPayload remotePayload = null)
+        private void CalculateAndVerifyFields()
         {
+            var isRemote = this.IsRemoteAllowed && !String.IsNullOrEmpty(this.Hash);
+
             if (String.IsNullOrEmpty(this.SourceFile))
             {
-                if (String.IsNullOrEmpty(this.Name))
-                {
-                    if (this.IsRequired)
-                    {
-                        this.Core.Write(ErrorMessages.ExpectedAttributesWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile"));
-                    }
-                }
-                else if (remotePayload == null)
+                if (!String.IsNullOrEmpty(this.Name) && !isRemote)
                 {
                     this.SourceFile = Path.Combine("SourceDir", this.Name);
                 }
             }
-            else if (remotePayload != null)
-            {
-                this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "RemotePayload", "SourceFile"));
-            }
             else if (this.SourceFile.EndsWith(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal))
             {
                 if (String.IsNullOrEmpty(this.Name))
@@ -80,24 +73,67 @@ namespace WixToolset.Core
                 }
             }
 
-            if (remotePayload != null)
+            if (String.IsNullOrEmpty(this.SourceFile) && !isRemote)
             {
-                if (this.DownloadUrl == null)
+                if (this.IsRequired)
                 {
-                    this.Core.Write(ErrorMessages.ExpectedAttributeWithElement(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "RemotePayload"));
+                    if (!this.IsRemoteAllowed)
+                    {
+                        this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "SourceFile"));
+                    }
+                    else
+                    {
+                        this.Core.Write(ErrorMessages.ExpectedAttributes(this.SourceLineNumbers, this.Element.Name.LocalName, "SourceFile", "Hash"));
+                    }
                 }
+            }
+            else if (this.IsRemoteAllowed)
+            {
+                var isLocal = !String.IsNullOrEmpty(this.SourceFile);
 
-                if (YesNoDefaultType.No != this.Compressed)
+                if (isLocal)
+                {
+                    if (isRemote)
+                    {
+                        this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Hash", "SourceFile"));
+                    }
+                }
+                else
                 {
+                    if (String.IsNullOrEmpty(this.DownloadUrl))
+                    {
+                        this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "DownloadUrl", "Hash"));
+                    }
+
+                    if (String.IsNullOrEmpty(this.Name))
+                    {
+                        this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, "Name", "Hash"));
+                    }
+
+                    if (YesNoDefaultType.Yes == this.Compressed)
+                    {
+                        this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName));
+                    }
+
                     this.Compressed = YesNoDefaultType.No;
-                    this.Core.Write(WarningMessages.RemotePayloadsMustNotAlsoBeCompressed(this.SourceLineNumbers, this.Element.Name.LocalName));
                 }
 
-                this.Description = remotePayload.Description;
-                this.DisplayName = remotePayload.DisplayName;
-                this.Hash = remotePayload.Hash;
-                this.Size = remotePayload.Size;
-                this.Version = remotePayload.Version;
+                VerifyValidValue("Description", !String.IsNullOrEmpty(this.Description));
+                VerifyValidValue("ProductName", !String.IsNullOrEmpty(this.ProductName));
+                VerifyValidValue("Size", this.Size.HasValue);
+                VerifyValidValue("Version", !String.IsNullOrEmpty(this.Version));
+
+                void VerifyValidValue(string attributeName, bool isSpecified)
+                {
+                    if (isLocal && isSpecified)
+                    {
+                        this.Core.Write(ErrorMessages.IllegalAttributeWithOtherAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, attributeName, "SourceFile"));
+                    }
+                    else if (!isLocal && !isSpecified)
+                    {
+                        this.Core.Write(ErrorMessages.ExpectedAttribute(this.SourceLineNumbers, this.Element.Name.LocalName, attributeName, "Hash"));
+                    }
+                }
             }
         }
 
@@ -143,9 +179,9 @@ namespace WixToolset.Core
             return symbol;
         }
 
-        public void FinishCompilingPackage(CompilerPayload remotePayload)
+        public void FinishCompilingPackage()
         {
-            this.CalculateAndVerifyFields(remotePayload);
+            this.CalculateAndVerifyFields();
             this.GenerateIdFromFilename();
 
             if (this.Id == null)
@@ -155,6 +191,13 @@ namespace WixToolset.Core
             }
         }
 
+        public void FinishCompilingPackagePayload()
+        {
+            this.CalculateAndVerifyFields();
+            this.GenerateIdFromFilename();
+            this.GenerateIdFromPrefix("ppy");
+        }
+
         public void FinishCompilingPayload()
         {
             this.CalculateAndVerifyFields();
diff --git a/src/WixToolset.Core/Compiler_Bundle.cs b/src/WixToolset.Core/Compiler_Bundle.cs
index 64fe2acc..60db75d6 100644
--- a/src/WixToolset.Core/Compiler_Bundle.cs
+++ b/src/WixToolset.Core/Compiler_Bundle.cs
@@ -1451,71 +1451,6 @@ namespace WixToolset.Core
             return compilerPayload.Id;
         }
 
-        private CompilerPayload ParseRemotePayloadElement(XElement node)
-        {
-            var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
-            var remotePayload = new CompilerPayload(this.Core, sourceLineNumbers, node);
-
-            foreach (var attrib in node.Attributes())
-            {
-                if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
-                {
-                    switch (attrib.Name.LocalName)
-                    {
-                    case "Description":
-                        remotePayload.ParseDescription(attrib);
-                        break;
-                    case "Hash":
-                        remotePayload.ParseHash(attrib);
-                        break;
-                    case "ProductName":
-                        remotePayload.ParseProductName(attrib);
-                        break;
-                    case "Size":
-                        remotePayload.ParseSize(attrib);
-                        break;
-                    case "Version":
-                        remotePayload.ParseVersion(attrib);
-                        break;
-                    default:
-                        this.Core.UnexpectedAttribute(node, attrib);
-                        break;
-                    }
-                }
-                else
-                {
-                    this.Core.ParseExtensionAttribute(node, attrib);
-                }
-            }
-
-            if (String.IsNullOrEmpty(remotePayload.ProductName))
-            {
-                this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "ProductName"));
-            }
-
-            if (String.IsNullOrEmpty(remotePayload.Description))
-            {
-                this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Description"));
-            }
-
-            if (String.IsNullOrEmpty(remotePayload.Hash))
-            {
-                this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Hash"));
-            }
-
-            if (0 == remotePayload.Size)
-            {
-                this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Size"));
-            }
-
-            if (String.IsNullOrEmpty(remotePayload.Version))
-            {
-                this.Core.Write(ErrorMessages.ExpectedAttribute(sourceLineNumbers, node.Name.LocalName, "Version"));
-            }
-
-            return remotePayload;
-        }
-
         /// <summary>
         /// Parse PayloadGroup element.
         /// </summary>
@@ -1561,8 +1496,21 @@ namespace WixToolset.Core
             {
                 if (CompilerCore.WixNamespace == child.Name.Namespace)
                 {
+                    WixBundlePackageType? packageType = null;
                     switch (child.Name.LocalName)
                     {
+                    case "ExePackagePayload":
+                        packageType = WixBundlePackageType.Exe;
+                        break;
+                    case "MsiPackagePayload":
+                        packageType = WixBundlePackageType.Msi;
+                        break;
+                    case "MspPackagePayload":
+                        packageType = WixBundlePackageType.Msp;
+                        break;
+                    case "MsuPackagePayload":
+                        packageType = WixBundlePackageType.Msu;
+                        break;
                     case "Payload":
                         previousId = this.ParsePayloadElement(child, ComplexReferenceParentType.PayloadGroup, id, previousType, previousId);
                         previousType = ComplexReferenceChildType.Payload;
@@ -1575,6 +1523,19 @@ namespace WixToolset.Core
                         this.Core.UnexpectedElement(node, child);
                         break;
                     }
+
+                    if (packageType.HasValue)
+                    {
+                        var compilerPayload = this.ParsePackagePayloadElement(null, child, packageType.Value, null);
+                        var payloadSymbol = compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.PayloadGroup, id?.Id, previousType, previousId?.Id);
+                        if (payloadSymbol != null)
+                        {
+                            previousId = payloadSymbol.Id;
+                            previousType = ComplexReferenceChildType.Payload;
+
+                            this.CreatePackagePayloadSymbol(payloadSymbol.SourceLineNumbers, packageType.Value, payloadSymbol.Id, ComplexReferenceParentType.PayloadGroup, id);
+                        }
+                    }
                 }
                 else
                 {
@@ -1984,7 +1945,7 @@ namespace WixToolset.Core
             Debug.Assert(ComplexReferenceParentType.PackageGroup == parentType);
             Debug.Assert(ComplexReferenceChildType.Unknown == previousType || ComplexReferenceChildType.PackageGroup == previousType || ComplexReferenceChildType.Package == previousType);
 
-            var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);;
+            var sourceLineNumbers = Preprocessor.GetSourceLineNumbers(node);
             var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
             {
                 IsRequired = false,
@@ -2008,8 +1969,9 @@ namespace WixToolset.Core
             string msuKB = null;
             var enableFeatureSelection = YesNoType.NotSet;
             var forcePerMachine = YesNoType.NotSet;
-            CompilerPayload remotePayload = null;
+            CompilerPayload childPackageCompilerPayload = null;
             var slipstream = YesNoType.NotSet;
+            var hasPayloadInfo = false;
 
             var expectedNetFx4Args = new string[] { "/q", "/norestart", "/chainingpackage" };
 
@@ -2029,12 +1991,15 @@ namespace WixToolset.Core
                         break;
                     case "Name":
                         compilerPayload.ParseName(attrib);
+                        hasPayloadInfo = true;
                         break;
                     case "SourceFile":
                         compilerPayload.ParseSourceFile(attrib);
+                        hasPayloadInfo = true;
                         break;
                     case "DownloadUrl":
                         compilerPayload.ParseDownloadUrl(attrib);
+                        hasPayloadInfo = true;
                         break;
                     case "After":
                         after = this.Core.GetAttributeValue(sourceLineNumbers, attrib);
@@ -2128,6 +2093,7 @@ namespace WixToolset.Core
                         break;
                     case "Compressed":
                         compilerPayload.ParseCompressed(attrib);
+                        hasPayloadInfo = true;
                         break;
                     case "Slipstream":
                         slipstream = this.Core.GetAttributeYesNoValue(sourceLineNumbers, attrib);
@@ -2150,26 +2116,30 @@ namespace WixToolset.Core
                 }
             }
 
-            // We need to handle RemotePayload up front because it effects value of sourceFile which is used in Id generation.  Id is needed by other child elements.
-            foreach (var child in node.Elements(CompilerCore.WixNamespace + "RemotePayload"))
+            // We need to handle the package payload up front because it affects Id generation.  Id is needed by other child elements.
+            var packagePayloadElementName = packageType + "PackagePayload";
+            foreach (var child in node.Elements(CompilerCore.WixNamespace + packagePayloadElementName))
             {
                 var childSourceLineNumbers = Preprocessor.GetSourceLineNumbers(child);
 
-                if (CompilerCore.WixNamespace == node.Name.Namespace && node.Name.LocalName != "ExePackage" && node.Name.LocalName != "MsuPackage")
+                if (childPackageCompilerPayload != null)
                 {
-                    this.Core.Write(ErrorMessages.RemotePayloadUnsupported(childSourceLineNumbers));
-                    continue;
+                    this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
                 }
-
-                if (null != remotePayload)
+                else if (hasPayloadInfo)
                 {
-                    this.Core.Write(ErrorMessages.TooManyChildren(childSourceLineNumbers, node.Name.LocalName, child.Name.LocalName));
+                    this.Core.Write(ErrorMessages.UnexpectedElementWithAttribute(sourceLineNumbers, node.Name.LocalName, child.Name.LocalName, "SourceFile", "Name", "DownloadUrl", "Compressed"));
                 }
 
-                remotePayload = this.ParseRemotePayloadElement(child);
+                childPackageCompilerPayload = this.ParsePackagePayloadElement(childSourceLineNumbers, child, packageType, compilerPayload.Id);
+            }
+
+            if (compilerPayload.Id == null && childPackageCompilerPayload != null)
+            {
+                compilerPayload.Id = childPackageCompilerPayload.Id;
             }
 
-            compilerPayload.FinishCompilingPackage(remotePayload);
+            compilerPayload.FinishCompilingPackage();
             var id = compilerPayload.Id;
 
             if (null == logPathVariable)
@@ -2279,7 +2249,11 @@ namespace WixToolset.Core
                             this.ParseCommandLineElement(child, id.Id);
                         }
                         break;
-                    case "RemotePayload":
+                    case "ExePackagePayload":
+                    case "MsiPackagePayload":
+                    case "MspPackagePayload":
+                    case "MsuPackagePayload":
+                        allowed = packagePayloadElementName == child.Name.LocalName;
                         // Handled previously
                         break;
                     default:
@@ -2301,8 +2275,13 @@ namespace WixToolset.Core
 
             if (!this.Core.EncounteredError)
             {
-                // We create the package contents as a payload with this package as the parent
-                compilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id);
+                var packageCompilerPayload = childPackageCompilerPayload ?? (hasPayloadInfo ? compilerPayload : null);
+                if (packageCompilerPayload != null)
+                {
+                    var payload = packageCompilerPayload.CreatePayloadSymbol(ComplexReferenceParentType.Package, id.Id);
+
+                    this.CreatePackagePayloadSymbol(sourceLineNumbers, packageType, payload.Id, ComplexReferenceParentType.Package, id);
+                }
 
                 this.Core.AddSymbol(new WixChainItemSymbol(sourceLineNumbers, id));
 
@@ -2313,7 +2292,6 @@ namespace WixToolset.Core
                 var chainPackageSymbol = this.Core.AddSymbol(new WixBundlePackageSymbol(sourceLineNumbers, id)
                 {
                     Type = packageType,
-                    PayloadRef = id.Id,
                     Attributes = attributes,
                     InstallCondition = installCondition,
                     CacheId = cacheId,
@@ -2391,6 +2369,134 @@ namespace WixToolset.Core
             return id.Id;
         }
 
+        private void CreatePackagePayloadSymbol(SourceLineNumber sourceLineNumbers, WixBundlePackageType packageType, Identifier payloadId, ComplexReferenceParentType parentType, Identifier parentId)
+        {
+            switch (packageType)
+            {
+                case WixBundlePackageType.Exe:
+                    this.Core.AddSymbol(new WixBundleExePackagePayloadSymbol(sourceLineNumbers, payloadId));
+                    break;
+
+                case WixBundlePackageType.Msi:
+                    this.Core.AddSymbol(new WixBundleMsiPackagePayloadSymbol(sourceLineNumbers, payloadId));
+                    break;
+
+                case WixBundlePackageType.Msp:
+                    this.Core.AddSymbol(new WixBundleMspPackagePayloadSymbol(sourceLineNumbers, payloadId));
+                    break;
+
+                case WixBundlePackageType.Msu:
+                    this.Core.AddSymbol(new WixBundleMsuPackagePayloadSymbol(sourceLineNumbers, payloadId));
+                    break;
+            }
+
+            this.Core.CreateGroupAndOrderingRows(sourceLineNumbers, parentType, parentId?.Id, ComplexReferenceChildType.PackagePayload, payloadId?.Id, ComplexReferenceChildType.Unknown, null);
+        }
+
+        private CompilerPayload ParsePackagePayloadElement(SourceLineNumber sourceLineNumbers, XElement node, WixBundlePackageType packageType, Identifier defaultId)
+        {
+            sourceLineNumbers = sourceLineNumbers ?? Preprocessor.GetSourceLineNumbers(node);
+            var compilerPayload = new CompilerPayload(this.Core, sourceLineNumbers, node)
+            {
+                Id = defaultId,
+                IsRemoteAllowed = packageType == WixBundlePackageType.Exe || packageType == WixBundlePackageType.Msu,
+            };
+
+            // This list lets us evaluate extension attributes *after* all core attributes
+            // have been parsed and dealt with, regardless of authoring order.
+            var extensionAttributes = new List<XAttribute>();
+
+            foreach (var attrib in node.Attributes())
+            {
+                if (String.IsNullOrEmpty(attrib.Name.NamespaceName) || CompilerCore.WixNamespace == attrib.Name.Namespace)
+                {
+                    var allowed = true;
+                    switch (attrib.Name.LocalName)
+                    {
+                        case "Id":
+                            compilerPayload.ParseId(attrib);
+                            break;
+                        case "Compressed":
+                            compilerPayload.ParseCompressed(attrib);
+                            break;
+                        case "Name":
+                            compilerPayload.ParseName(attrib);
+                            break;
+                        case "SourceFile":
+                            compilerPayload.ParseSourceFile(attrib);
+                            break;
+                        case "DownloadUrl":
+                            compilerPayload.ParseDownloadUrl(attrib);
+                            break;
+                        case "Description":
+                            allowed = compilerPayload.IsRemoteAllowed;
+                            if (allowed)
+                            {
+                                compilerPayload.ParseDescription(attrib);
+                            }
+                            break;
+                        case "Hash":
+                            allowed = compilerPayload.IsRemoteAllowed;
+                            if (allowed)
+                            {
+                                compilerPayload.ParseHash(attrib);
+                            }
+                            break;
+                        case "ProductName":
+                            allowed = compilerPayload.IsRemoteAllowed;
+                            if (allowed)
+                            {
+                                compilerPayload.ParseProductName(attrib);
+                            }
+                            break;
+                        case "Size":
+                            allowed = compilerPayload.IsRemoteAllowed;
+                            if (allowed)
+                            {
+                                compilerPayload.ParseSize(attrib);
+                            }
+                            break;
+                        case "Version":
+                            allowed = compilerPayload.IsRemoteAllowed;
+                            if (allowed)
+                            {
+                                compilerPayload.ParseVersion(attrib);
+                            }
+                            break;
+                        default:
+                            allowed = false;
+                            break;
+                    }
+
+                    if (!allowed)
+                    {
+                        this.Core.UnexpectedAttribute(node, attrib);
+                    }
+                }
+                else
+                {
+                    this.Core.ParseExtensionAttribute(node, attrib);
+                }
+            }
+
+            compilerPayload.FinishCompilingPackagePayload();
+
+            // Now that the PayloadId is known, we can parse the extension attributes.
+            var context = new Dictionary<string, string>
+            {
+                ["Id"] = compilerPayload.Id.Id,
+            };
+
+            foreach (var extensionAttribute in extensionAttributes)
+            {
+                this.Core.ParseExtensionAttribute(node, extensionAttribute, context);
+            }
+
+            this.Core.ParseForExtensionElements(node);
+
+            return compilerPayload;
+        }
+
         /// <summary>
         /// Parse CommandLine element.
         /// </summary>
diff --git a/src/WixToolset.Core/Linker.cs b/src/WixToolset.Core/Linker.cs
index e0af89ba..41e0db7d 100644
--- a/src/WixToolset.Core/Linker.cs
+++ b/src/WixToolset.Core/Linker.cs
@@ -1242,7 +1242,7 @@ namespace WixToolset.Core
             var groups = new WixGroupingOrdering(entrySection, this.Messaging);
 
             // Create UX payloads and Package payloads
-            groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload });
+            groups.UseTypes(new[] { ComplexReferenceParentType.Container, ComplexReferenceParentType.Layout, ComplexReferenceParentType.PackageGroup, ComplexReferenceParentType.PayloadGroup, ComplexReferenceParentType.Package }, new[] { ComplexReferenceChildType.PackageGroup, ComplexReferenceChildType.Package, ComplexReferenceChildType.PackagePayload, ComplexReferenceChildType.PayloadGroup, ComplexReferenceChildType.Payload });
             groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Package, false);
             groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Container, false);
             groups.FlattenAndRewriteGroups(ComplexReferenceParentType.Layout, false);
diff --git a/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
new file mode 100644
index 00000000..77a21f61
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/PackagePayloadFixture.cs
@@ -0,0 +1,207 @@
+// 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.
+
+namespace WixToolsetTest.CoreIntegration
+{
+    using System.Collections.Generic;
+    using System.IO;
+    using System.Linq;
+    using WixBuildTools.TestSupport;
+    using WixToolset.Core.TestPackage;
+    using Xunit;
+
+    public class PackagePayloadFixture
+    {
+        [Fact]
+        public void CanSpecifyPackagePayloadInPayloadGroup()
+        {
+            var folder = TestData.Get(@"TestData");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+                var intermediateFolder = Path.Combine(baseFolder, "obj");
+                var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
+                var baFolderPath = Path.Combine(baseFolder, "ba");
+                var extractFolderPath = Path.Combine(baseFolder, "extract");
+
+                var result = WixRunner.Execute(new[]
+                {
+                    "build",
+                    Path.Combine(folder, "PackagePayload", "PackagePayloadInPayloadGroup.wxs"),
+                    Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
+                    "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
+                    "-bindpath", Path.Combine(folder, ".Data"),
+                    "-intermediateFolder", intermediateFolder,
+                    "-o", bundlePath,
+                });
+
+                result.AssertSuccess();
+
+                Assert.True(File.Exists(bundlePath));
+
+                var extractResult = BundleExtractor.ExtractBAContainer(null, bundlePath, baFolderPath, extractFolderPath);
+                extractResult.AssertSuccess();
+
+                var exePackageElements = extractResult.SelectManifestNodes("/burn:BurnManifest/burn:Chain/burn:ExePackage");
+                var ignoreAttributesByElementName = new Dictionary<string, List<string>>
+                {
+                    { "ExePackage", new List<string> { "CacheId", "InstallSize", "Size" } },
+                };
+                Assert.Equal(1, exePackageElements.Count);
+                Assert.Equal("<ExePackage Id='PackagePayloadInPayloadGroup' Cache='yes' CacheId='*' InstallSize='*' Size='*' PerMachine='yes' Permanent='yes' Vital='yes' RollbackBoundaryForward='WixDefaultBoundary' RollbackBoundaryBackward='WixDefaultBoundary' LogPathVariable='WixBundleLog_PackagePayloadInPayloadGroup' RollbackLogPathVariable='WixBundleRollbackLog_PackagePayloadInPayloadGroup' DetectCondition='none' InstallArguments='' UninstallArguments='' RepairArguments='' Repairable='no'><PayloadRef Id='burn.exe' /></ExePackage>", exePackageElements[0].GetTestXml(ignoreAttributesByElementName));
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenMissingSourceFileAndHash()
+        {
+            var folder = TestData.Get(@"TestData", "PackagePayload");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+
+                var result = WixRunner.Execute(false, new[]
+                {
+                    "build",
+                    Path.Combine(folder, "MissingSourceFileAndHash.wxs"),
+                    "-o", Path.Combine(baseFolder, "test.wixlib")
+                });
+
+                Assert.Equal(44, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The MsuPackagePayload element's SourceFile or Hash attribute was not found; one of these is required.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenMissingSourceFileAndName()
+        {
+            var folder = TestData.Get(@"TestData", "PackagePayload");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+
+                var result = WixRunner.Execute(false, new[]
+                {
+                    "build",
+                    Path.Combine(folder, "MissingSourceFileAndName.wxs"),
+                    "-o", Path.Combine(baseFolder, "test.wixlib")
+                });
+
+                Assert.Equal(44, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The MsiPackagePayload element's Name or SourceFile attribute was not found; one of these is required.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenSpecifiedHash()
+        {
+            var folder = TestData.Get(@"TestData", "PackagePayload");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+
+                var result = WixRunner.Execute(new[]
+                {
+                    "build",
+                    Path.Combine(folder, "SpecifiedHash.wxs"),
+                    "-o", Path.Combine(baseFolder, "test.wixlib")
+                });
+
+                Assert.Equal(4, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The MspPackagePayload element contains an unexpected attribute 'Hash'.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenSpecifiedHashAndMissingDownloadUrl()
+        {
+            var folder = TestData.Get(@"TestData", "PackagePayload");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+
+                var result = WixRunner.Execute(new[]
+                {
+                    "build",
+                    Path.Combine(folder, "SpecifiedHashAndMissingDownloadUrl.wxs"),
+                    "-o", Path.Combine(baseFolder, "test.wixlib")
+                });
+
+                Assert.Equal(10, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The MsuPackagePayload/@DownloadUrl attribute was not found; it is required when attribute Hash is specified.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenSpecifiedSourceFileAndHash()
+        {
+            var folder = TestData.Get(@"TestData", "PackagePayload");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+
+                var result = WixRunner.Execute(new[]
+                {
+                    "build",
+                    Path.Combine(folder, "SpecifiedSourceFileAndHash.wxs"),
+                    "-o", Path.Combine(baseFolder, "test.wixlib")
+                });
+
+                Assert.Equal(35, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The ExePackagePayload/@Hash attribute cannot be specified when attribute SourceFile is present.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+
+        [Fact]
+        public void ErrorWhenWrongPackagePayloadInPayloadGroup()
+        {
+            var folder = TestData.Get(@"TestData");
+
+            using (var fs = new DisposableFileSystem())
+            {
+                var baseFolder = fs.GetFolder();
+                var intermediateFolder = Path.Combine(baseFolder, "obj");
+                var bundlePath = Path.Combine(baseFolder, @"bin\test.exe");
+
+                var result = WixRunner.Execute(new[]
+                {
+                    "build",
+                    Path.Combine(folder, "PackagePayload", "WrongPackagePayloadInPayloadGroup.wxs"),
+                    Path.Combine(folder, "BundleWithPackageGroupRef", "Bundle.wxs"),
+                    "-bindpath", Path.Combine(folder, "SimpleBundle", "data"),
+                    "-bindpath", Path.Combine(folder, ".Data"),
+                    "-intermediateFolder", intermediateFolder,
+                    "-o", bundlePath,
+                });
+
+                Assert.Equal(407, result.ExitCode);
+                WixAssert.CompareLineByLine(new[]
+                {
+                    "The ExePackagePayload element can only be used for ExePackages.",
+                    "The location of the package related to previous error.",
+                    "There is no payload defined for package 'WrongPackagePayloadInPayloadGroup'. This is specified on the MsiPackage element or a child MsiPackagePayload element.",
+                }, result.Messages.Select(m => m.ToString()).ToArray());
+            }
+        }
+    }
+}
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs
new file mode 100644
index 00000000..5e1b99ff
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndHash.wxs
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <MsuPackage Id="MissingSourceFileAndHash" Permanent="yes" DetectCondition="none">
+                <MsuPackagePayload DownloadUrl="example.com" />
+            </MsuPackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs
new file mode 100644
index 00000000..f220d81a
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/MissingSourceFileAndName.wxs
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <MsiPackage Id="MissingSourceFileAndName">
+                <MsiPackagePayload DownloadUrl="example.com" />
+            </MsiPackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs
new file mode 100644
index 00000000..149870a4
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/PackagePayloadInPayloadGroup.wxs
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <ExePackage Id="PackagePayloadInPayloadGroup" Permanent="yes" DetectCondition="none">
+                <PayloadGroupRef Id="PackagePayloadGroup" />
+            </ExePackage>
+        </PackageGroup>
+    </Fragment>
+    <Fragment>
+        <PayloadGroup Id="PackagePayloadGroup">
+            <ExePackagePayload SourceFile="burn.exe" />
+        </PayloadGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs
new file mode 100644
index 00000000..3c361c49
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHash.wxs
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <MspPackage Id="SpecifiedHash">
+                <MspPackagePayload SourceFile="example.msp" DownloadUrl="example.com" Hash="abcd" />
+            </MspPackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs
new file mode 100644
index 00000000..8e62f660
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedHashAndMissingDownloadUrl.wxs
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <MsuPackage Id="SpecifiedHashAndMissingDownloadUrl" Permanent="yes" DetectCondition="none">
+                <MsuPackagePayload Name="example.msu" Hash="abcd" Size="1" Version="1.0.0.0" ProductName="KB1234567" Description="fake msu" />
+            </MsuPackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs
new file mode 100644
index 00000000..f79da874
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/SpecifiedSourceFileAndHash.wxs
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <ExePackage Id="SpecifiedSourceFileAndHash" Permanent="yes" DetectCondition="none">
+                <ExePackagePayload SourceFile="example.exe" Hash="abcd" />
+            </ExePackage>
+        </PackageGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs
new file mode 100644
index 00000000..dda306cf
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/PackagePayload/WrongPackagePayloadInPayloadGroup.wxs
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
+    <Fragment>
+        <PackageGroup Id="BundlePackages">
+            <MsiPackage Id="WrongPackagePayloadInPayloadGroup">
+                <PayloadGroupRef Id="WrongPackagePayloadGroup" />
+            </MsiPackage>
+        </PackageGroup>
+    </Fragment>
+    <Fragment>
+        <PayloadGroup Id="WrongPackagePayloadGroup">
+            <ExePackagePayload SourceFile="burn.exe" />
+        </PayloadGroup>
+    </Fragment>
+</Wix>
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs
index 79ba52d2..56f08ba9 100644
--- a/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/SingleExeBundle/SingleExeRemotePayload.wxs
@@ -13,11 +13,10 @@
                 Vital="yes"
                 Permanent="yes"
                 Protocol="netfx4"
-                DownloadUrl="C"
-                LogPathVariable="NetFx462FullLog"
-                Compressed="no"
-                Name="NDP462-KB3151802-Web.exe">
-                <RemotePayload
+                LogPathVariable="NetFx462FullLog">
+                <ExePackagePayload
+                  DownloadUrl="C"
+                  Name="NDP462-KB3151802-Web.exe"
                   Description="Microsoft .NET Framework 4.6.2 Setup"
                   Hash="C42E6ED280290648BBD59F664008852F4CFE4548"
                   ProductName="Microsoft .NET Framework 4.6.2"
-- 
cgit v1.2.3-55-g6feb