| 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; |
| 7 | using System.Collections.Generic; |
| 8 | using System.IO; |
| 9 | using System.Xml; |
| 10 | using WixToolset.Core.Native; |
| 11 | using WixToolset.Extensibility.Services; |
| 12 | |
| 13 | /// <summary> |
| 14 | /// Burn PE reader for the WiX toolset. |
| 15 | /// </summary> |
| 16 | /// <remarks>This class encapsulates reading from a stub EXE with containers attached |
| 17 | /// for dissecting bundled/chained setup packages.</remarks> |
| 18 | /// <example> |
| 19 | /// using (BurnReader reader = BurnReader.Open(fileExe, this.core, guid)) |
| 20 | /// { |
| 21 | /// reader.ExtractUXContainer(file1, tempFolder); |
| 22 | /// } |
| 23 | /// </example> |
| 24 | internal class BurnReader : BurnCommon |
| 25 | { |
| 26 | private bool disposed; |
| 27 | |
| 28 | private BinaryReader binaryReader; |
| 29 | private readonly List<DictionaryEntry> attachedContainerPayloadNames; |
| 30 | private readonly IFileSystem fileSystem; |
| 31 | |
| 32 | /// <summary> |
| 33 | /// Creates a BurnReader for reading a PE file. |
| 34 | /// </summary> |
| 35 | /// <param name="messaging">Messaging.</param> |
| 36 | /// <param name="fileSystem">File system.</param> |
| 37 | /// <param name="fileExe">File to read.</param> |
| 38 | private BurnReader(IMessaging messaging, IFileSystem fileSystem, string fileExe) |
| 39 | : base(messaging, fileExe) |
| 40 | { |
| 41 | this.attachedContainerPayloadNames = new List<DictionaryEntry>(); |
| 42 | this.fileSystem = fileSystem; |
| 43 | } |
| 44 | |
| 45 | /// <summary> |
| 46 | /// Gets the underlying stream. |
| 47 | /// </summary> |
| 48 | public Stream Stream => this.binaryReader?.BaseStream; |
| 49 | |
| 50 | /// <summary> |
| 51 | /// Opens a Burn reader. |
| 52 | /// </summary> |
| 53 | /// <param name="messaging">Messaging.</param> |
| 54 | /// <param name="fileSystem">File system.</param> |
| 55 | /// <param name="fileExe">Path to file.</param> |
| 56 | /// <returns>Burn reader.</returns> |
| 57 | public static BurnReader Open(IMessaging messaging, IFileSystem fileSystem, string fileExe) |
| 58 | { |
| 59 | var binaryReader = new BinaryReader(fileSystem.OpenFile(null, fileExe, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)); |
| 60 | var reader = new BurnReader(messaging, fileSystem, fileExe) |
| 61 | { |
| 62 | binaryReader = binaryReader, |
| 63 | }; |
| 64 | reader.Initialize(reader.binaryReader); |
| 65 | |
| 66 | return reader; |
| 67 | } |
| 68 | |
| 69 | /// <summary> |
| 70 | /// Gets the UX container from the exe and extracts its contents to the output directory. |
| 71 | /// </summary> |
| 72 | /// <param name="outputDirectory">Directory to write extracted files to.</param> |
| 73 | /// <param name="tempDirectory">Scratch directory.</param> |
| 74 | /// <returns>True if successful, false otherwise</returns> |
| 75 | public bool ExtractUXContainer(string outputDirectory, string tempDirectory) |
| 76 | { |
| 77 | // No UX container to extract |
| 78 | if (this.AttachedContainers.Count == 0) |
| 79 | { |
| 80 | return false; |
| 81 | } |
| 82 | |
| 83 | if (this.Invalid) |
| 84 | { |
| 85 | return false; |
| 86 | } |
| 87 | |
| 88 | Directory.CreateDirectory(outputDirectory); |
| 89 | var tempCabPath = Path.Combine(tempDirectory, "ux.cab"); |
| 90 | var manifestOriginalPath = Path.Combine(outputDirectory, "0"); |
| 91 | var manifestPath = Path.Combine(outputDirectory, "manifest.xml"); |
| 92 | var uxContainerSlot = this.AttachedContainers[0]; |
| 93 | |
| 94 | this.binaryReader.BaseStream.Seek(this.UXAddress, SeekOrigin.Begin); |
| 95 | using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read)) |
| 96 | { |
| 97 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)uxContainerSlot.Size); |
| 98 | } |
| 99 | |
| 100 | var cabinet = new Cabinet(tempCabPath); |
| 101 | cabinet.Extract(outputDirectory); |
| 102 | |
| 103 | this.fileSystem.MoveFile(null, manifestOriginalPath, manifestPath); |
| 104 | |
| 105 | var document = new XmlDocument(); |
| 106 | document.Load(manifestPath); |
| 107 | var namespaceManager = new XmlNamespaceManager(document.NameTable); |
| 108 | namespaceManager.AddNamespace("burn", document.DocumentElement.NamespaceURI); |
| 109 | var uxPayloads = document.SelectNodes("/burn:BurnManifest/burn:UX/burn:Payload", namespaceManager); |
| 110 | var payloads = document.SelectNodes("/burn:BurnManifest/burn:Payload", namespaceManager); |
| 111 | |
| 112 | foreach (XmlNode uxPayload in uxPayloads) |
| 113 | { |
| 114 | var sourcePathNode = uxPayload.Attributes.GetNamedItem("SourcePath"); |
| 115 | var filePathNode = uxPayload.Attributes.GetNamedItem("FilePath"); |
| 116 | |
| 117 | var sourcePath = Path.Combine(outputDirectory, sourcePathNode.Value); |
| 118 | var destinationPath = Path.Combine(outputDirectory, filePathNode.Value); |
| 119 | |
| 120 | this.fileSystem.MoveFile(null, sourcePath, destinationPath); |
| 121 | } |
| 122 | |
| 123 | foreach (XmlNode payload in payloads) |
| 124 | { |
| 125 | var packagingNode = payload.Attributes.GetNamedItem("Packaging"); |
| 126 | |
| 127 | var packaging = packagingNode.Value; |
| 128 | |
| 129 | if (packaging.Equals("embedded", StringComparison.OrdinalIgnoreCase)) |
| 130 | { |
| 131 | var sourcePathNode = payload.Attributes.GetNamedItem("SourcePath"); |
| 132 | var filePathNode = payload.Attributes.GetNamedItem("FilePath"); |
| 133 | var containerNode = payload.Attributes.GetNamedItem("Container"); |
| 134 | |
| 135 | var sourcePath = sourcePathNode.Value; |
| 136 | var destinationPath = Path.Combine(containerNode.Value, filePathNode.Value); |
| 137 | |
| 138 | this.attachedContainerPayloadNames.Add(new DictionaryEntry(sourcePath, destinationPath)); |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | return true; |
| 143 | } |
| 144 | |
| 145 | /// <summary> |
| 146 | /// Gets each non-UX attached container from the exe and extracts its contents to the output directory. |
| 147 | /// </summary> |
| 148 | /// <param name="outputDirectory">Directory to write extracted files to.</param> |
| 149 | /// <param name="tempDirectory">Scratch directory.</param> |
| 150 | /// <returns>True if successful, false otherwise</returns> |
| 151 | public bool ExtractAttachedContainers(string outputDirectory, string tempDirectory) |
| 152 | { |
| 153 | // No attached containers to extract |
| 154 | if (this.AttachedContainers.Count < 2) |
| 155 | { |
| 156 | return false; |
| 157 | } |
| 158 | |
| 159 | if (this.Invalid) |
| 160 | { |
| 161 | return false; |
| 162 | } |
| 163 | |
| 164 | Directory.CreateDirectory(outputDirectory); |
| 165 | var nextAddress = this.EngineSize; |
| 166 | for (var i = 1; i < this.AttachedContainers.Count; i++) |
| 167 | { |
| 168 | var cntnr = this.AttachedContainers[i]; |
| 169 | var tempCabPath = Path.Combine(tempDirectory, $"a{i}.cab"); |
| 170 | |
| 171 | this.binaryReader.BaseStream.Seek(nextAddress, SeekOrigin.Begin); |
| 172 | using (Stream tempCab = this.fileSystem.OpenFile(null, tempCabPath, FileMode.Create, FileAccess.Write, FileShare.Read)) |
| 173 | { |
| 174 | BurnCommon.CopyStream(this.binaryReader.BaseStream, tempCab, (int)cntnr.Size); |
| 175 | } |
| 176 | |
| 177 | var cabinet = new Cabinet(tempCabPath); |
| 178 | cabinet.Extract(outputDirectory); |
| 179 | |
| 180 | nextAddress += cntnr.Size; |
| 181 | } |
| 182 | |
| 183 | foreach (var entry in this.attachedContainerPayloadNames) |
| 184 | { |
| 185 | var sourcePath = Path.Combine(outputDirectory, (string)entry.Key); |
| 186 | var destinationPath = Path.Combine(outputDirectory, (string)entry.Value); |
| 187 | |
| 188 | this.fileSystem.MoveFile(null, sourcePath, destinationPath); |
| 189 | } |
| 190 | |
| 191 | return true; |
| 192 | } |
| 193 | |
| 194 | /// <summary> |
| 195 | /// Dispose object. |
| 196 | /// </summary> |
| 197 | /// <param name="disposing">True when releasing managed objects.</param> |
| 198 | protected override void Dispose(bool disposing) |
| 199 | { |
| 200 | if (!this.disposed) |
| 201 | { |
| 202 | if (disposing && this.binaryReader != null) |
| 203 | { |
| 204 | this.binaryReader.Close(); |
| 205 | this.binaryReader = null; |
| 206 | } |
| 207 | |
| 208 | this.disposed = true; |
| 209 | } |
| 210 | } |
| 211 | } |
| 212 | } |