main
cs 232 lines 11.7 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.Bind
4 {
5 using System;
6 using System.Collections.Generic;
7 using System.ComponentModel;
8 using System.Globalization;
9 using System.IO;
10 using System.Linq;
11 using System.Runtime.InteropServices;
12 using WixToolset.Core.Native;
13 using WixToolset.Core.Native.Msi;
14 using WixToolset.Core.Native.Msm;
15 using WixToolset.Data;
16 using WixToolset.Data.Symbols;
17 using WixToolset.Extensibility.Data;
18 using WixToolset.Extensibility.Services;
19
20 /// <summary>
21 /// Retrieve files information and extract them from merge modules.
22 /// </summary>
23 internal class ExtractMergeModuleFilesCommand
24 {
25 public ExtractMergeModuleFilesCommand(IMessaging messaging, IWindowsInstallerBackendHelper backendHelper, IEnumerable<WixMergeSymbol> wixMergeSymbols, IEnumerable<IFileFacade> fileFacadesFromIntermediate, int installerVersion, string intermediateFolder, bool suppressLayout)
26 {
27 this.Messaging = messaging;
28 this.BackendHelper = backendHelper;
29 this.WixMergeSymbols = wixMergeSymbols;
30 this.FileFacadesFromIntermediate = fileFacadesFromIntermediate;
31 this.OutputInstallerVersion = installerVersion;
32 this.IntermediateFolder = intermediateFolder;
33 this.SuppressLayout = suppressLayout;
34 }
35
36 private IMessaging Messaging { get; }
37
38 private IWindowsInstallerBackendHelper BackendHelper { get; }
39
40 private IEnumerable<WixMergeSymbol> WixMergeSymbols { get; }
41
42 private IEnumerable<IFileFacade> FileFacadesFromIntermediate { get; }
43
44 private int OutputInstallerVersion { get; }
45
46 private string IntermediateFolder { get; }
47
48 private bool SuppressLayout { get; }
49
50 public IEnumerable<IFileFacade> MergeModulesFileFacades { get; private set; }
51
52 public IReadOnlyList<ITrackedFile> TrackedFiles { get; private set; }
53
54 public void Execute()
55 {
56 var mergeModulesFileFacades = new List<IFileFacade>();
57 var trackedFiles = new List<ITrackedFile>();
58
59 var merge = MsmInterop.GetMsmMerge();
60
61 // Index all of the file rows to be able to detect collisions with files in the Merge Modules.
62 // It may seem a bit expensive to build up this index solely for the purpose of checking collisions
63 // and you may be thinking, "Surely, we must need the file rows indexed elsewhere." It turns out
64 // there are other cases where we need all the file rows indexed, however they are not common cases.
65 // Now since Merge Modules are already slow and generally less desirable than .wixlibs we'll let
66 // this case be slightly more expensive because the cost of maintaining an indexed file row collection
67 // is a lot more costly for the common cases.
68 var indexedFileFacades = this.FileFacadesFromIntermediate.ToDictionary(f => f.Id, StringComparer.Ordinal);
69
70 foreach (var wixMergeRow in this.WixMergeSymbols)
71 {
72 var modulesTrackedFiles = this.CreateFacadesForMergeModuleFiles(wixMergeRow, mergeModulesFileFacades, indexedFileFacades);
73
74 // If the module has files and creating layout
75 if (modulesTrackedFiles.Count > 0 && !this.SuppressLayout)
76 {
77 this.ExtractFilesFromMergeModule(merge, wixMergeRow);
78 trackedFiles.AddRange(modulesTrackedFiles);
79 }
80 }
81
82 this.MergeModulesFileFacades = mergeModulesFileFacades;
83 this.TrackedFiles = trackedFiles;
84 }
85
86 private IReadOnlyCollection<ITrackedFile> CreateFacadesForMergeModuleFiles(WixMergeSymbol wixMergeRow, List<IFileFacade> mergeModulesFileFacades, Dictionary<string, IFileFacade> indexedFileFacades)
87 {
88 var trackedFiles = new List<ITrackedFile>();
89
90 try
91 {
92 // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module.
93 using (var db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly))
94 {
95 if (db.TableExists("File") && db.TableExists("Component"))
96 {
97 var uniqueModuleFileIdentifiers = new Dictionary<string, IFileFacade>(StringComparer.OrdinalIgnoreCase);
98
99 using (var view = db.OpenExecuteView("SELECT `File`, `Component_`, `FileName`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`"))
100 {
101 // add each file row from the merge module into the file row collection (check for errors along the way)
102 foreach (var record in view.Records)
103 {
104 var splitFilename = this.BackendHelper.SplitMsiFileName(record[3]);
105
106 // NOTE: this is very tricky - the merge module file rows are not added to the
107 // file table because they should not be created via idt import. Instead, these
108 // rows are created by merging in the actual modules.
109 var fileSymbol = new FileSymbol(wixMergeRow.SourceLineNumbers, new Identifier(AccessModifier.Section, record[1]));
110 fileSymbol.Attributes = wixMergeRow.FileAttributes;
111 fileSymbol.ComponentRef = record[2];
112 fileSymbol.Name = splitFilename[1] ?? splitFilename[0];
113 fileSymbol.ShortName = splitFilename[1] is null ? null : splitFilename[0];
114 fileSymbol.DirectoryRef = record[4];
115 fileSymbol.DiskId = wixMergeRow.DiskId;
116 fileSymbol.Source = new IntermediateFieldPathValue { Path = Path.Combine(this.IntermediateFolder, wixMergeRow.Id.Id, record[1]) };
117
118 var mergeModuleFileFacade = this.BackendHelper.CreateFileFacade(fileSymbol);
119
120 // If case-sensitive collision with another merge module or a user-authored file identifier.
121 if (indexedFileFacades.TryGetValue(mergeModuleFileFacade.Id, out var collidingFacade))
122 {
123 this.Messaging.Write(ErrorMessages.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, collidingFacade.Id));
124 }
125 else if (uniqueModuleFileIdentifiers.TryGetValue(mergeModuleFileFacade.Id, out collidingFacade)) // case-insensitive collision with another file identifier in the same merge module
126 {
127 this.Messaging.Write(ErrorMessages.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, mergeModuleFileFacade.Id, collidingFacade.Id));
128 }
129 else // no collision
130 {
131 mergeModulesFileFacades.Add(mergeModuleFileFacade);
132
133 // Keep updating the indexes as new facades are added.
134 indexedFileFacades.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
135 uniqueModuleFileIdentifiers.Add(mergeModuleFileFacade.Id, mergeModuleFileFacade);
136
137 // Track where file will be extracted.
138 trackedFiles.Add(this.BackendHelper.TrackFile(mergeModuleFileFacade.SourcePath, TrackedFileType.Intermediate, mergeModuleFileFacade.SourceLineNumber));
139 }
140 }
141 }
142 }
143
144 // Get the summary information to detect the Schema
145 using (var summaryInformation = new SummaryInformation(db))
146 {
147 var moduleInstallerVersionString = summaryInformation.GetProperty(14);
148
149 try
150 {
151 var moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture);
152 if (moduleInstallerVersion > this.OutputInstallerVersion)
153 {
154 this.Messaging.Write(WarningMessages.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, moduleInstallerVersion, this.OutputInstallerVersion));
155 }
156 }
157 catch (FormatException)
158 {
159 throw new WixException(ErrorMessages.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile, moduleInstallerVersionString));
160 }
161 }
162 }
163 }
164 catch (FileNotFoundException)
165 {
166 throw new WixException(ErrorMessages.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile));
167 }
168 catch (Win32Exception)
169 {
170 throw new WixException(ErrorMessages.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id.Id, wixMergeRow.SourceFile));
171 }
172
173 return trackedFiles;
174 }
175
176 private void ExtractFilesFromMergeModule(IMsmMerge2 merge, WixMergeSymbol wixMergeRow)
177 {
178 var moduleOpen = false;
179 short mergeLanguage;
180
181 var mergeId = wixMergeRow.Id.Id;
182
183 try
184 {
185 mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture);
186 }
187 catch (FormatException)
188 {
189 this.Messaging.Write(ErrorMessages.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, mergeId, wixMergeRow.Language.ToString()));
190 return;
191 }
192
193 try
194 {
195 merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage);
196 moduleOpen = true;
197
198 // extract the module cabinet, then explode all of the files to a temp directory
199 var moduleCabPath = Path.Combine(this.IntermediateFolder, mergeId + ".cab");
200 merge.ExtractCAB(moduleCabPath);
201
202 var mergeIdPath = Path.Combine(this.IntermediateFolder, mergeId);
203 Directory.CreateDirectory(mergeIdPath);
204
205 try
206 {
207 var cabinet = new Cabinet(moduleCabPath);
208 cabinet.Extract(mergeIdPath);
209 }
210 catch (FileNotFoundException)
211 {
212 throw new WixException(ErrorMessages.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
213 }
214 catch
215 {
216 throw new WixException(ErrorMessages.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath));
217 }
218 }
219 catch (COMException ce)
220 {
221 throw new WixException(ErrorMessages.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message));
222 }
223 finally
224 {
225 if (moduleOpen)
226 {
227 merge.CloseModule();
228 }
229 }
230 }
231 }
232 }