| 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 | namespace WixToolset.Core.Burn.Bundles |
| 4 | { |
| 5 | using System; |
| 6 | using System.Collections.Generic; |
| 7 | using System.Globalization; |
| 8 | using System.IO; |
| 9 | using System.Linq; |
| 10 | using System.Text; |
| 11 | using System.Xml; |
| 12 | using WixToolset.Data; |
| 13 | using WixToolset.Data.Burn; |
| 14 | using WixToolset.Data.Symbols; |
| 15 | |
| 16 | internal class CreateBootstrapperApplicationManifestCommand |
| 17 | { |
| 18 | public CreateBootstrapperApplicationManifestCommand(IntermediateSection section, WixBundleSymbol bundleSymbol, IEnumerable<WixBundleRollbackBoundarySymbol> boundaries, PackageFacades packageFacades, int lastUXPayloadIndex, Dictionary<string, WixBundlePayloadSymbol> payloadSymbols, Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> packagesPayloads, string intermediateFolder, IInternalBurnBackendHelper internalBurnBackendHelper) |
| 19 | { |
| 20 | this.Section = section; |
| 21 | this.BundleSymbol = bundleSymbol; |
| 22 | this.RollbackBoundaries = boundaries; |
| 23 | this.PackagesFacades = packageFacades; |
| 24 | this.LastUXPayloadIndex = lastUXPayloadIndex; |
| 25 | this.Payloads = payloadSymbols; |
| 26 | this.PackagesPayloads = packagesPayloads; |
| 27 | this.IntermediateFolder = intermediateFolder; |
| 28 | this.InternalBurnBackendHelper = internalBurnBackendHelper; |
| 29 | } |
| 30 | |
| 31 | private IntermediateSection Section { get; } |
| 32 | |
| 33 | private WixBundleSymbol BundleSymbol { get; } |
| 34 | |
| 35 | private IEnumerable<WixBundleRollbackBoundarySymbol> RollbackBoundaries { get; } |
| 36 | |
| 37 | private PackageFacades PackagesFacades { get; } |
| 38 | |
| 39 | private IInternalBurnBackendHelper InternalBurnBackendHelper { get; } |
| 40 | |
| 41 | private int LastUXPayloadIndex { get; } |
| 42 | |
| 43 | private Dictionary<string, WixBundlePayloadSymbol> Payloads { get; } |
| 44 | |
| 45 | private Dictionary<string, Dictionary<string, WixBundlePayloadSymbol>> PackagesPayloads { get; } |
| 46 | |
| 47 | private string IntermediateFolder { get; } |
| 48 | |
| 49 | public WixBundlePayloadSymbol BootstrapperApplicationManifestPayloadRow { get; private set; } |
| 50 | |
| 51 | public string OutputPath { get; private set; } |
| 52 | |
| 53 | public void Execute() |
| 54 | { |
| 55 | this.OutputPath = this.CreateBootstrapperApplicationManifest(); |
| 56 | |
| 57 | this.BootstrapperApplicationManifestPayloadRow = this.CreateBootstrapperApplicationManifestPayloadRow(this.OutputPath); |
| 58 | } |
| 59 | |
| 60 | private string CreateBootstrapperApplicationManifest() |
| 61 | { |
| 62 | var path = Path.Combine(this.IntermediateFolder, "wix-badata.xml"); |
| 63 | |
| 64 | Directory.CreateDirectory(Path.GetDirectoryName(path)); |
| 65 | |
| 66 | using (var writer = new XmlTextWriter(path, Encoding.Unicode)) |
| 67 | { |
| 68 | writer.Formatting = Formatting.Indented; |
| 69 | writer.WriteStartDocument(); |
| 70 | writer.WriteStartElement("BootstrapperApplicationData", BurnConstants.BootstrapperApplicationDataNamespace); |
| 71 | |
| 72 | this.WriteBundleInfo(writer); |
| 73 | |
| 74 | this.WriteRollbackBoundaryInfo(writer); |
| 75 | |
| 76 | this.WritePackageInfo(writer); |
| 77 | |
| 78 | this.WriteFeatureInfo(writer); |
| 79 | |
| 80 | this.WritePayloadInfo(writer); |
| 81 | |
| 82 | this.InternalBurnBackendHelper.WriteBootstrapperApplicationData(writer); |
| 83 | |
| 84 | writer.WriteEndElement(); |
| 85 | writer.WriteEndDocument(); |
| 86 | } |
| 87 | |
| 88 | return path; |
| 89 | } |
| 90 | |
| 91 | private void WriteBundleInfo(XmlTextWriter writer) |
| 92 | { |
| 93 | writer.WriteStartElement("WixBundleProperties"); |
| 94 | |
| 95 | writer.WriteAttributeString("DisplayName", this.BundleSymbol.Name); |
| 96 | writer.WriteAttributeString("LogPathVariable", this.BundleSymbol.LogPathVariable); |
| 97 | writer.WriteAttributeString("Compressed", this.BundleSymbol.Compressed == true ? "yes" : "no"); |
| 98 | writer.WriteAttributeString("Id", this.BundleSymbol.BundleId.ToUpperInvariant()); |
| 99 | writer.WriteAttributeString("UpgradeCode", this.BundleSymbol.UpgradeCode); |
| 100 | writer.WriteAttributeString("PerMachine", this.BundleSymbol.PerMachine ? "yes" : "no"); |
| 101 | |
| 102 | writer.WriteEndElement(); |
| 103 | } |
| 104 | |
| 105 | private void WriteRollbackBoundaryInfo(XmlTextWriter writer) |
| 106 | { |
| 107 | foreach (var rollbackBoundary in this.RollbackBoundaries) |
| 108 | { |
| 109 | writer.WriteStartElement("WixRollbackBoundary"); |
| 110 | writer.WriteAttributeString("Id", rollbackBoundary.Id.Id); |
| 111 | writer.WriteAttributeString("Vital", rollbackBoundary.Vital ? "yes" : "no"); |
| 112 | writer.WriteAttributeString("Transaction", rollbackBoundary.Transaction ? "yes" : "no"); |
| 113 | |
| 114 | if (!String.IsNullOrEmpty(rollbackBoundary.LogPathVariable)) |
| 115 | { |
| 116 | writer.WriteAttributeString("LogPathVariable", rollbackBoundary.LogPathVariable); |
| 117 | } |
| 118 | |
| 119 | writer.WriteEndElement(); |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | private void WritePackageInfo(XmlTextWriter writer) |
| 124 | { |
| 125 | foreach (var package in this.PackagesFacades.OrderedValues) |
| 126 | { |
| 127 | if (!this.PackagesPayloads.TryGetValue(package.PackageId, out var payloads)) |
| 128 | { |
| 129 | continue; |
| 130 | } |
| 131 | |
| 132 | var packagePayload = payloads[package.PackageSymbol.PayloadRef]; |
| 133 | |
| 134 | var size = package.PackageSymbol.Size.ToString(CultureInfo.InvariantCulture); |
| 135 | |
| 136 | writer.WriteStartElement("WixPackageProperties"); |
| 137 | |
| 138 | writer.WriteAttributeString("Package", package.PackageId); |
| 139 | writer.WriteAttributeString("Vital", package.PackageSymbol.Vital ? "yes" : "no"); |
| 140 | |
| 141 | if (!String.IsNullOrEmpty(package.PackageSymbol.DisplayName)) |
| 142 | { |
| 143 | writer.WriteAttributeString("DisplayName", package.PackageSymbol.DisplayName); |
| 144 | } |
| 145 | |
| 146 | if (!String.IsNullOrEmpty(package.PackageSymbol.Description)) |
| 147 | { |
| 148 | writer.WriteAttributeString("Description", package.PackageSymbol.Description); |
| 149 | } |
| 150 | |
| 151 | writer.WriteAttributeString("DownloadSize", size); |
| 152 | writer.WriteAttributeString("PackageSize", size); |
| 153 | writer.WriteAttributeString("InstalledSize", package.PackageSymbol.InstallSize?.ToString(CultureInfo.InvariantCulture) ?? size); |
| 154 | writer.WriteAttributeString("PackageType", package.PackageSymbol.Type.ToString()); |
| 155 | writer.WriteAttributeString("Permanent", package.PackageSymbol.Permanent ? "yes" : "no"); |
| 156 | writer.WriteAttributeString("LogPathVariable", package.PackageSymbol.LogPathVariable); |
| 157 | writer.WriteAttributeString("RollbackLogPathVariable", package.PackageSymbol.RollbackLogPathVariable); |
| 158 | writer.WriteAttributeString("Compressed", packagePayload.Packaging == PackagingType.Embedded ? "yes" : "no"); |
| 159 | |
| 160 | if (package.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) |
| 161 | { |
| 162 | if (!String.IsNullOrEmpty(msiPackage.ProductCode)) |
| 163 | { |
| 164 | writer.WriteAttributeString("ProductCode", msiPackage.ProductCode); |
| 165 | } |
| 166 | |
| 167 | if (!String.IsNullOrEmpty(msiPackage.UpgradeCode)) |
| 168 | { |
| 169 | writer.WriteAttributeString("UpgradeCode", msiPackage.UpgradeCode); |
| 170 | } |
| 171 | } |
| 172 | else if (package.SpecificPackageSymbol is WixBundleMspPackageSymbol mspPackage) |
| 173 | { |
| 174 | if (!String.IsNullOrEmpty(mspPackage.PatchCode)) |
| 175 | { |
| 176 | writer.WriteAttributeString("ProductCode", mspPackage.PatchCode); |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | if (!String.IsNullOrEmpty(package.PackageSymbol.Version)) |
| 181 | { |
| 182 | writer.WriteAttributeString("Version", package.PackageSymbol.Version); |
| 183 | } |
| 184 | |
| 185 | if (!String.IsNullOrEmpty(package.PackageSymbol.InstallCondition)) |
| 186 | { |
| 187 | writer.WriteAttributeString("InstallCondition", package.PackageSymbol.InstallCondition); |
| 188 | } |
| 189 | |
| 190 | if (!String.IsNullOrEmpty(package.PackageSymbol.RepairCondition)) |
| 191 | { |
| 192 | writer.WriteAttributeString("RepairCondition", package.PackageSymbol.RepairCondition); |
| 193 | } |
| 194 | |
| 195 | switch (package.PackageSymbol.Cache) |
| 196 | { |
| 197 | case BundleCacheType.Remove: |
| 198 | writer.WriteAttributeString("Cache", "remove"); |
| 199 | break; |
| 200 | case BundleCacheType.Keep: |
| 201 | writer.WriteAttributeString("Cache", "keep"); |
| 202 | break; |
| 203 | case BundleCacheType.Force: |
| 204 | writer.WriteAttributeString("Cache", "force"); |
| 205 | break; |
| 206 | } |
| 207 | |
| 208 | writer.WriteEndElement(); |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | private void WriteFeatureInfo(XmlTextWriter writer) |
| 213 | { |
| 214 | var featureSymbols = this.Section.Symbols.OfType<WixBundleMsiFeatureSymbol>(); |
| 215 | |
| 216 | foreach (var featureSymbol in featureSymbols) |
| 217 | { |
| 218 | if (!this.PackagesFacades.TryGetFacadesByPackagePayloadId(featureSymbol.PackagePayloadRef, out var facades)) |
| 219 | { |
| 220 | continue; |
| 221 | } |
| 222 | |
| 223 | foreach (var facade in facades) |
| 224 | { |
| 225 | if (!(facade.SpecificPackageSymbol is WixBundleMsiPackageSymbol msiPackage) || !msiPackage.EnableFeatureSelection) |
| 226 | { |
| 227 | continue; |
| 228 | } |
| 229 | |
| 230 | writer.WriteStartElement("WixPackageFeatureInfo"); |
| 231 | |
| 232 | writer.WriteAttributeString("Package", facade.PackageId); |
| 233 | writer.WriteAttributeString("Feature", featureSymbol.Name); |
| 234 | writer.WriteAttributeString("Size", featureSymbol.Size.ToString(CultureInfo.InvariantCulture)); |
| 235 | |
| 236 | if (!String.IsNullOrEmpty(featureSymbol.Parent)) |
| 237 | { |
| 238 | writer.WriteAttributeString("Parent", featureSymbol.Parent); |
| 239 | } |
| 240 | |
| 241 | if (!String.IsNullOrEmpty(featureSymbol.Title)) |
| 242 | { |
| 243 | writer.WriteAttributeString("Title", featureSymbol.Title); |
| 244 | } |
| 245 | |
| 246 | if (!String.IsNullOrEmpty(featureSymbol.Description)) |
| 247 | { |
| 248 | writer.WriteAttributeString("Description", featureSymbol.Description); |
| 249 | } |
| 250 | |
| 251 | writer.WriteAttributeString("Display", featureSymbol.Display.ToString(CultureInfo.InvariantCulture)); |
| 252 | writer.WriteAttributeString("Level", featureSymbol.Level.ToString(CultureInfo.InvariantCulture)); |
| 253 | writer.WriteAttributeString("Directory", featureSymbol.Directory); |
| 254 | writer.WriteAttributeString("Attributes", featureSymbol.Attributes.ToString(CultureInfo.InvariantCulture)); |
| 255 | |
| 256 | writer.WriteEndElement(); |
| 257 | } |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | private void WritePayloadInfo(XmlTextWriter writer) |
| 262 | { |
| 263 | foreach (var kvp in this.PackagesPayloads.OrderBy(kvp => kvp.Key, StringComparer.Ordinal)) |
| 264 | { |
| 265 | var packageId = kvp.Key; |
| 266 | var payloadsById = kvp.Value; |
| 267 | |
| 268 | foreach (var payloadSymbol in payloadsById.Values.OrderBy(p => p.Id.Id, StringComparer.Ordinal)) |
| 269 | { |
| 270 | this.WritePayloadInfo(writer, payloadSymbol, packageId); |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | foreach (var payloadSymbol in this.Payloads.Values.Where(p => p.LayoutOnly).OrderBy(p => p.Id.Id, StringComparer.Ordinal)) |
| 275 | { |
| 276 | this.WritePayloadInfo(writer, payloadSymbol, null); |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | private void WritePayloadInfo(XmlTextWriter writer, WixBundlePayloadSymbol payloadSymbol, string packageId) |
| 281 | { |
| 282 | writer.WriteStartElement("WixPayloadProperties"); |
| 283 | |
| 284 | if (!String.IsNullOrEmpty(packageId)) |
| 285 | { |
| 286 | writer.WriteAttributeString("Package", packageId); |
| 287 | } |
| 288 | |
| 289 | writer.WriteAttributeString("Payload", payloadSymbol.Id.Id); |
| 290 | |
| 291 | if (!String.IsNullOrEmpty(payloadSymbol.ContainerRef)) |
| 292 | { |
| 293 | writer.WriteAttributeString("Container", payloadSymbol.ContainerRef); |
| 294 | } |
| 295 | |
| 296 | writer.WriteAttributeString("Name", payloadSymbol.Name); |
| 297 | writer.WriteAttributeString("Size", payloadSymbol.FileSize.Value.ToString(CultureInfo.InvariantCulture)); |
| 298 | |
| 299 | if (!String.IsNullOrEmpty(payloadSymbol.DownloadUrl)) |
| 300 | { |
| 301 | writer.WriteAttributeString("DownloadUrl", payloadSymbol.DownloadUrl); |
| 302 | } |
| 303 | |
| 304 | writer.WriteEndElement(); |
| 305 | } |
| 306 | |
| 307 | private WixBundlePayloadSymbol CreateBootstrapperApplicationManifestPayloadRow(string baManifestPath) |
| 308 | { |
| 309 | var generatedId = this.InternalBurnBackendHelper.GenerateIdentifier("ux", BurnCommon.BADataFileName); |
| 310 | |
| 311 | var symbol = this.Section.AddSymbol(new WixBundlePayloadSymbol(this.BundleSymbol.SourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) |
| 312 | { |
| 313 | Name = BurnCommon.BADataFileName, |
| 314 | SourceFile = new IntermediateFieldPathValue { Path = baManifestPath }, |
| 315 | Compressed = true, |
| 316 | UnresolvedSourceFile = baManifestPath, |
| 317 | ContainerRef = BurnConstants.BurnUXContainerName, |
| 318 | EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, this.LastUXPayloadIndex), |
| 319 | Packaging = PackagingType.Embedded, |
| 320 | }); |
| 321 | |
| 322 | var fileInfo = new FileInfo(baManifestPath); |
| 323 | |
| 324 | symbol.FileSize = (int)fileInfo.Length; |
| 325 | |
| 326 | symbol.Hash = BundleHashAlgorithm.Hash(fileInfo); |
| 327 | |
| 328 | return symbol; |
| 329 | } |
| 330 | } |
| 331 | } |