main
cs 391 lines 16.2 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.CommandLine
4 {
5 using System;
6 using System.IO;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using WixToolset.Core.WindowsInstaller.Bind;
10 using WixToolset.Core.WindowsInstaller.Unbind;
11 using WixToolset.Data;
12 using WixToolset.Data.Symbols;
13 using WixToolset.Data.WindowsInstaller;
14 using WixToolset.Extensibility;
15 using WixToolset.Extensibility.Data;
16 using WixToolset.Extensibility.Services;
17
18 internal class TransformSubcommand : WindowsInstallerSubcommandBase
19 {
20 public TransformSubcommand(IServiceProvider serviceProvider)
21 {
22 this.Messaging = serviceProvider.GetService<IMessaging>();
23 this.BackendHelper = serviceProvider.GetService<IBackendHelper>();
24 this.FileSystem = serviceProvider.GetService<IFileSystem>();
25 this.PathResolver = serviceProvider.GetService<IPathResolver>();
26 this.ExtensionManager = serviceProvider.GetService<IExtensionManager>();
27 }
28
29 private IMessaging Messaging { get; }
30
31 private IBackendHelper BackendHelper { get; }
32
33 private IFileSystem FileSystem { get; }
34
35 private IPathResolver PathResolver { get; }
36
37 private IExtensionManager ExtensionManager { get; }
38
39 private string OutputPath { get; set; }
40
41 private string TargetPath { get; set; }
42
43 private string UpdatedPath { get; set; }
44
45 private string ExportBasePath { get; set; }
46
47 private string IntermediateFolder { get; set; }
48
49 private bool PreserveUnchangedRows { get; set; }
50
51 private bool ShowPedanticMessages { get; set; }
52
53 private bool SuppressKeepingSpecialRows { get; set; }
54
55 private bool OutputAsWixout { get; set; }
56
57 private TransformFlags ValidationFlags { get; set; }
58
59 public override CommandLineHelp GetCommandLineHelp()
60 {
61 return new CommandLineHelp("Creates an MST transform file.", "msi transform [options] target.msi [updated.msi] -out output.mst")
62 {
63 Switches = new[]
64 {
65 new CommandLineHelpSwitch("-intermediateFolder", "Optional working folder. If not specified %TMP% will be used."),
66 new CommandLineHelpSwitch("-p", "Preserve unchanged rows."),
67 new CommandLineHelpSwitch("-pedantic", "Show pedantic messages."),
68 new CommandLineHelpSwitch("-serr <flags>", "Suppress error when applying transform; see Error flags below."),
69 new CommandLineHelpSwitch("-t <type>", "Use default validation flags for the transform type; see Transform types below."),
70 new CommandLineHelpSwitch("-val <flags>", "Validation flags for the transform; see Validation flags below."),
71 new CommandLineHelpSwitch("-x", "Folder to extract binaries."),
72 new CommandLineHelpSwitch("-xo", "Output transfrom as a WiX output instead of an MST file."),
73 new CommandLineHelpSwitch("-out", "-o", "Path to output the transform file."),
74 },
75 Notes = String.Join(Environment.NewLine,
76 "Error flags:",
77 " a Ignore errors when adding an existing row",
78 " b Ignore errors when deleting a missing row",
79 " c Ignore errors when adding an existing table",
80 " d Ignore errors when deleting a missing table",
81 " e Ignore errors when modifying a missing row",
82 " f Ignore errors when changing the code page",
83 String.Empty,
84 "Validation flags:",
85 " g UpgradeCode must match",
86 " l Language must match",
87 " r Product ID must match",
88 " s Check major version only",
89 " t Check major and minor versions",
90 " u Check major, minor, and upgrade versions",
91 " v Upgrade version < target version",
92 " w Upgrade version <= target version",
93 " x Upgrade version = target version",
94 " y Upgrade version > target version",
95 " z Upgrade version >= target version",
96 String.Empty,
97 "Transform types:",
98 " language Default flags for a language transform",
99 " instance Default flags for an instance transform",
100 " patch Default flags for a patch transform")
101 };
102 }
103
104 public override Task<int> ExecuteAsync(CancellationToken cancellationToken)
105 {
106 if (String.IsNullOrEmpty(this.TargetPath))
107 {
108 this.Messaging.Write(ErrorMessages.FilePathRequired("input file"));
109 }
110 else if (String.IsNullOrEmpty(this.OutputPath))
111 {
112 this.Messaging.Write(ErrorMessages.FilePathRequired("output file"));
113 }
114 else
115 {
116 if (String.IsNullOrEmpty(this.IntermediateFolder))
117 {
118 this.IntermediateFolder = Path.GetTempPath();
119 }
120
121 var transform = this.LoadTransform();
122
123 if (!this.Messaging.EncounteredError)
124 {
125 this.SaveTransform(transform);
126 }
127 }
128
129 return Task.FromResult(this.Messaging.LastErrorNumber);
130 }
131
132 public override bool TryParseArgument(ICommandLineParser parser, string argument)
133 {
134 if (parser.IsSwitch(argument))
135 {
136 var parameter = argument.Substring(1);
137 switch (parameter.ToLowerInvariant())
138 {
139 case "intermediatefolder":
140 this.IntermediateFolder = parser.GetNextArgumentAsDirectoryOrError(argument);
141 return true;
142
143 case "o":
144 case "out":
145 this.OutputPath = parser.GetNextArgumentAsFilePathOrError(argument, "output file");
146 return true;
147
148 case "p":
149 this.PreserveUnchangedRows = true;
150 return true;
151
152 case "pedantic":
153 this.ShowPedanticMessages = true;
154 return true;
155
156 case "serr":
157 {
158 var serr = parser.GetNextArgumentOrError(argument);
159 if (String.IsNullOrEmpty(serr))
160 {
161 return true;
162 }
163
164 switch (serr.ToLowerInvariant())
165 {
166 case "a":
167 this.ValidationFlags |= TransformFlags.ErrorAddExistingRow;
168 return true;
169
170 case "b":
171 this.ValidationFlags |= TransformFlags.ErrorDeleteMissingRow;
172 return true;
173
174 case "c":
175 this.ValidationFlags |= TransformFlags.ErrorAddExistingTable;
176 return true;
177
178 case "d":
179 this.ValidationFlags |= TransformFlags.ErrorDeleteMissingTable;
180 return true;
181
182 case "e":
183 this.ValidationFlags |= TransformFlags.ErrorUpdateMissingRow;
184 return true;
185
186 case "f":
187 this.ValidationFlags |= TransformFlags.ErrorChangeCodePage;
188 return true;
189
190 default:
191 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, serr, new[] { "a", "b", "c", "d", "e", "f" }));
192 return true;
193 }
194 }
195
196 case "t":
197 {
198 var type = parser.GetNextArgumentOrError(argument);
199 if (String.IsNullOrEmpty(type))
200 {
201 return true;
202 }
203
204 switch (type.ToLowerInvariant())
205 {
206 case "language":
207 this.ValidationFlags |= TransformFlags.LanguageTransformDefault;
208 return true;
209
210 case "instance":
211 this.ValidationFlags |= TransformFlags.InstanceTransformDefault;
212 return true;
213
214 case "patch":
215 this.ValidationFlags |= TransformFlags.PatchTransformDefault;
216 return true;
217
218 default:
219 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, type, new[] { "language", "instance", "patch" }));
220 return true;
221 }
222 }
223
224 case "val":
225 {
226 var val = parser.GetNextArgumentOrError(argument);
227 if (String.IsNullOrEmpty(val))
228 {
229 return true;
230 }
231
232 switch (val.ToLowerInvariant())
233 {
234 case "g":
235 this.ValidationFlags |= TransformFlags.ValidateUpgradeCode;
236 return true;
237
238 case "l":
239 this.ValidationFlags |= TransformFlags.ValidateLanguage;
240 return true;
241
242 case "r":
243 this.ValidationFlags |= TransformFlags.ValidateProduct;
244 return true;
245
246 case "s":
247 this.ValidationFlags |= TransformFlags.ValidateMajorVersion;
248 return true;
249
250 case "t":
251 this.ValidationFlags |= TransformFlags.ValidateMinorVersion;
252 return true;
253
254 case "u":
255 this.ValidationFlags |= TransformFlags.ValidateUpdateVersion;
256 return true;
257
258 case "v":
259 this.ValidationFlags |= TransformFlags.ValidateNewLessBaseVersion;
260 return true;
261
262 case "w":
263 this.ValidationFlags |= TransformFlags.ValidateNewLessEqualBaseVersion;
264 return true;
265
266 case "x":
267 this.ValidationFlags |= TransformFlags.ValidateNewEqualBaseVersion;
268 return true;
269
270 case "y":
271 this.ValidationFlags |= TransformFlags.ValidateNewGreaterEqualBaseVersion;
272 return true;
273
274 case "z":
275 this.ValidationFlags |= TransformFlags.ValidateNewGreaterBaseVersion;
276 return true;
277
278 default:
279 parser.ReportErrorArgument(argument, ErrorMessages.IllegalCommandLineArgumentValue(argument, val, new[] { "g", "l", "r", "s", "t", "u", "v", "w", "x", "y", "z" }));
280 return true;
281 }
282 }
283
284 case "x":
285 this.ExportBasePath = parser.GetNextArgumentAsDirectoryOrError(argument);
286 return true;
287
288 case "xo":
289 this.OutputAsWixout = true;
290 return true;
291 }
292 }
293 else if (String.IsNullOrEmpty(this.TargetPath))
294 {
295 this.TargetPath = argument;
296 return true;
297 }
298 else if (String.IsNullOrEmpty(this.UpdatedPath))
299 {
300 this.UpdatedPath = argument;
301 return true;
302 }
303
304 return false;
305 }
306
307 private WindowsInstallerData LoadTransform()
308 {
309 WindowsInstallerData transform;
310
311 if (String.IsNullOrEmpty(this.UpdatedPath))
312 {
313 Exception exception;
314
315 (transform, exception) = DataLoader.LoadWindowsInstallerDataSafely(this.TargetPath);
316
317 if (transform?.Type != OutputType.Transform)
318 {
319 this.Messaging.Write(WindowsInstallerBackendErrors.CannotLoadWixoutAsTransform(new SourceLineNumber(this.TargetPath), exception));
320 }
321 }
322 else
323 {
324 transform = this.CreateTransform();
325
326 if (null == transform.Tables || 0 >= transform.Tables.Count)
327 {
328 this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(new SourceLineNumber(this.OutputPath)));
329 }
330 }
331
332 return transform;
333 }
334
335 private void SaveTransform(WindowsInstallerData transform)
336 {
337 if (this.OutputAsWixout)
338 {
339 using (var output = WixOutput.Create(this.OutputPath))
340 {
341 transform.Save(output);
342 }
343 }
344 else
345 {
346 var fileSystemExtensions = this.ExtensionManager.GetServices<IFileSystemExtension>();
347 var fileSystemManager = new FileSystemManager(this.FileSystem, fileSystemExtensions);
348
349 var tableDefinitions = this.GetTableDefinitions();
350
351 var bindCommand = new BindTransformCommand(this.Messaging, this.BackendHelper, this.FileSystem, fileSystemManager, this.IntermediateFolder, transform, this.OutputPath, tableDefinitions);
352 bindCommand.Execute();
353 }
354 }
355
356 private WindowsInstallerData CreateTransform()
357 {
358 var targetData = this.GetWindowsInstallerData(this.TargetPath);
359
360 var updatedData = this.GetWindowsInstallerData(this.UpdatedPath);
361
362 var differ = new Differ(this.Messaging)
363 {
364 PreserveUnchangedRows = this.PreserveUnchangedRows,
365 ShowPedanticMessages = this.ShowPedanticMessages,
366 SuppressKeepingSpecialRows = this.SuppressKeepingSpecialRows
367 };
368
369 return differ.Diff(targetData, updatedData, this.ValidationFlags);
370 }
371
372 private TableDefinitionCollection GetTableDefinitions()
373 {
374 var backendExtensions = this.ExtensionManager.GetServices<IWindowsInstallerBackendBinderExtension>();
375
376 var loadTableDefinitions = new LoadTableDefinitionsCommand(this.Messaging, null, backendExtensions);
377 return loadTableDefinitions.Execute();
378 }
379
380 private WindowsInstallerData GetWindowsInstallerData(string path)
381 {
382 if (!DataLoader.TryLoadWindowsInstallerData(path, out var data))
383 {
384 var unbindCommand = new UnbindDatabaseCommand(this.Messaging, this.BackendHelper, this.FileSystem, this.PathResolver, path, null, OutputType.Package, this.ExportBasePath, null, this.IntermediateFolder, enableDemodularization: false, skipSummaryInfo: false);
385 data = unbindCommand.Execute();
386 }
387
388 return data;
389 }
390 }
391 }