main
cs 171 lines 7.45 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.WindowsInstaller.Unbind
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.Globalization;
8 using System.IO;
9 using System.Linq;
10 using WixToolset.Core.Native;
11 using WixToolset.Core.Native.Msi;
12 using WixToolset.Data;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Data.WindowsInstaller.Rows;
15 using WixToolset.Extensibility.Services;
16
17 internal class ExtractCabinetsCommand
18 {
19 public ExtractCabinetsCommand(IFileSystem fileSystem, WindowsInstallerData output, Database database, string inputFilePath, string exportBasePath, string intermediateFolder, bool treatOutputAsModule = false)
20 {
21 this.FileSystem = fileSystem;
22 this.Output = output;
23 this.Database = database;
24 this.InputFilePath = inputFilePath;
25 this.ExportBasePath = exportBasePath;
26 this.IntermediateFolder = intermediateFolder;
27 this.TreatOutputAsModule = treatOutputAsModule;
28 }
29
30 public Dictionary<string, MediaRow> ExtractedFileIdsWithMediaRow { get; private set; }
31
32 private IFileSystem FileSystem { get; }
33
34 private WindowsInstallerData Output { get; }
35
36 private Database Database { get; }
37
38 private string InputFilePath { get; }
39
40 private string ExportBasePath { get; }
41
42 private string IntermediateFolder { get; }
43
44 public bool TreatOutputAsModule { get; }
45
46 public void Execute()
47 {
48 var extractedFileIdsWithMediaRow = new Dictionary<string, MediaRow>();
49 var databaseBasePath = Path.GetDirectoryName(this.InputFilePath);
50
51 var cabinetPathsWithMediaRow = new Dictionary<string, MediaRow>();
52 var embeddedCabinetNamesByDiskId = new SortedDictionary<int, string>();
53 var embeddedCabinetRowsByDiskId = new SortedDictionary<int, MediaRow>();
54
55 // index all of the cabinet files
56 if (OutputType.Module == this.Output.Type || this.TreatOutputAsModule)
57 {
58 var mediaRow = new MediaRow(null, WindowsInstallerTableDefinitions.Media)
59 {
60 DiskId = 1,
61 LastSequence = 1,
62 Cabinet = "MergeModule.CABinet",
63 };
64
65 embeddedCabinetRowsByDiskId.Add(1, mediaRow);
66 embeddedCabinetNamesByDiskId.Add(1, "MergeModule.CABinet");
67 }
68
69 if (this.Output.Tables.TryGetTable("Media", out var mediaTable))
70 {
71 foreach (var mediaRow in mediaTable.Rows.Cast<MediaRow>().Where(r => !String.IsNullOrEmpty(r.Cabinet)))
72 {
73 if (OutputType.Package == this.Output.Type ||
74 OutputType.Module == this.Output.Type ||
75 (OutputType.Transform == this.Output.Type && RowOperation.Add == mediaRow.Operation))
76 {
77 if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal))
78 {
79 embeddedCabinetNamesByDiskId.Add(mediaRow.DiskId, mediaRow.Cabinet.Substring(1));
80 embeddedCabinetRowsByDiskId.Add(mediaRow.DiskId, mediaRow);
81 }
82 else
83 {
84 cabinetPathsWithMediaRow.Add(Path.Combine(databaseBasePath, mediaRow.Cabinet), mediaRow);
85 }
86 }
87 }
88 }
89
90 // Extract any embedded cabinet files from the database.
91 if (0 < embeddedCabinetRowsByDiskId.Count)
92 {
93 using (var streamsView = this.Database.OpenView("SELECT `Data` FROM `_Streams` WHERE `Name` = ?"))
94 {
95 foreach (var diskIdWithCabinetName in embeddedCabinetNamesByDiskId)
96 {
97 var diskId = diskIdWithCabinetName.Key;
98 var cabinetName = diskIdWithCabinetName.Value;
99
100 using (var record = new Record(1))
101 {
102 record.SetString(1, cabinetName);
103 streamsView.Execute(record);
104 }
105
106 using (var record = streamsView.Fetch())
107 {
108 if (null != record)
109 {
110 embeddedCabinetRowsByDiskId.TryGetValue(diskId, out var cabinetMediaRow);
111
112 // since the cabinets are stored in case-sensitive streams inside the msi, but the file system is not (typically) case-sensitive,
113 // embedded cabinets must be extracted to a canonical file name (like their diskid) to ensure extraction will always work
114 var cabinetPath = Path.Combine(this.IntermediateFolder, "Media", diskId.ToString(CultureInfo.InvariantCulture), ".cab");
115
116 // ensure the parent directory exists
117 Directory.CreateDirectory(Path.GetDirectoryName(cabinetPath));
118
119 using (var fs = this.FileSystem.OpenFile(cabinetMediaRow.SourceLineNumbers, cabinetPath, FileMode.Create, FileAccess.Write, FileShare.None))
120 {
121 int bytesRead;
122 var buffer = new byte[4096];
123
124 while (0 != (bytesRead = record.GetStream(1, buffer, buffer.Length)))
125 {
126 fs.Write(buffer, 0, bytesRead);
127 }
128 }
129
130 cabinetPathsWithMediaRow.Add(cabinetPath, cabinetMediaRow);
131 }
132 else
133 {
134 // TODO: warning about missing embedded cabinet
135 }
136 }
137 }
138 }
139 }
140
141 // Extract files from any available cabinets.
142 if (0 < cabinetPathsWithMediaRow.Count)
143 {
144 Directory.CreateDirectory(this.ExportBasePath);
145
146 foreach (var cabinetPathWithMediaRow in cabinetPathsWithMediaRow)
147 {
148 var cabinetPath = cabinetPathWithMediaRow.Key;
149 var cabinetMediaRow = cabinetPathWithMediaRow.Value;
150
151 try
152 {
153 var cabinet = new Cabinet(cabinetPath);
154 var cabinetFilesExtracted = cabinet.Extract(this.ExportBasePath);
155
156 foreach (var extractedFile in cabinetFilesExtracted)
157 {
158 extractedFileIdsWithMediaRow.Add(extractedFile, cabinetMediaRow);
159 }
160 }
161 catch (FileNotFoundException)
162 {
163 throw new WixException(ErrorMessages.FileNotFound(new SourceLineNumber(this.InputFilePath), cabinetPath));
164 }
165 }
166 }
167
168 this.ExtractedFileIdsWithMediaRow = extractedFileIdsWithMediaRow;
169 }
170 }
171 }