main
cs 212 lines 8.33 KB
Raw
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 }