aboutsummaryrefslogtreecommitdiff
path: root/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs')
-rw-r--r--src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs560
1 files changed, 0 insertions, 560 deletions
diff --git a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs b/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
deleted file mode 100644
index f73776c0..00000000
--- a/src/WixToolset.Core/Bind/Bundles/ProcessMsiPackageCommand.cs
+++ /dev/null
@@ -1,560 +0,0 @@
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
3namespace WixToolset.Bind.Bundles
4{
5 using System;
6 using System.Collections;
7 using System.Collections.Generic;
8 using System.Diagnostics;
9 using System.Globalization;
10 using System.IO;
11 using System.Linq;
12 using WixToolset.Data;
13 using WixToolset.Data.Rows;
14 using WixToolset.Extensibility;
15 using WixToolset.Msi;
16 using WixToolset.Core.Native;
17 using Dtf = WixToolset.Dtf.WindowsInstaller;
18
19 /// <summary>
20 /// Initializes package state from the MSI contents.
21 /// </summary>
22 internal class ProcessMsiPackageCommand : ICommand
23 {
24 private const string PropertySqlFormat = "SELECT `Value` FROM `Property` WHERE `Property` = '{0}'";
25
26 public RowDictionary<WixBundlePayloadRow> AuthoredPayloads { private get; set; }
27
28 public PackageFacade Facade { private get; set; }
29
30 public IBinderFileManager FileManager { private get; set; }
31
32 public Table MsiFeatureTable { private get; set; }
33
34 public Table MsiPropertyTable { private get; set; }
35
36 public Table PayloadTable { private get; set; }
37
38 public Table RelatedPackageTable { private get; set; }
39
40 /// <summary>
41 /// Processes the MSI packages to add properties and payloads from the MSI packages.
42 /// </summary>
43 public void Execute()
44 {
45 WixBundlePayloadRow packagePayload = this.AuthoredPayloads.Get(this.Facade.Package.PackagePayload);
46
47 string sourcePath = packagePayload.FullFileName;
48 bool longNamesInImage = false;
49 bool compressed = false;
50 bool x64 = false;
51 try
52 {
53 // Read data out of the msi database...
54 using (Dtf.SummaryInfo sumInfo = new Dtf.SummaryInfo(sourcePath, false))
55 {
56 // 1 is the Word Count summary information stream bit that means
57 // the MSI uses short file names when set. We care about long file
58 // names so check when the bit is not set.
59 longNamesInImage = 0 == (sumInfo.WordCount & 1);
60
61 // 2 is the Word Count summary information stream bit that means
62 // files are compressed in the MSI by default when the bit is set.
63 compressed = 2 == (sumInfo.WordCount & 2);
64
65 x64 = (sumInfo.Template.Contains("x64") || sumInfo.Template.Contains("Intel64"));
66
67 // 8 is the Word Count summary information stream bit that means
68 // "Elevated privileges are not required to install this package."
69 // in MSI 4.5 and below, if this bit is 0, elevation is required.
70 this.Facade.Package.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No;
71 this.Facade.Package.x64 = x64 ? YesNoType.Yes : YesNoType.No;
72 }
73
74 using (Dtf.Database db = new Dtf.Database(sourcePath))
75 {
76 this.Facade.MsiPackage.ProductCode = ProcessMsiPackageCommand.GetProperty(db, "ProductCode");
77 this.Facade.MsiPackage.UpgradeCode = ProcessMsiPackageCommand.GetProperty(db, "UpgradeCode");
78 this.Facade.MsiPackage.Manufacturer = ProcessMsiPackageCommand.GetProperty(db, "Manufacturer");
79 this.Facade.MsiPackage.ProductLanguage = Convert.ToInt32(ProcessMsiPackageCommand.GetProperty(db, "ProductLanguage"), CultureInfo.InvariantCulture);
80 this.Facade.MsiPackage.ProductVersion = ProcessMsiPackageCommand.GetProperty(db, "ProductVersion");
81
82 if (!Common.IsValidModuleOrBundleVersion(this.Facade.MsiPackage.ProductVersion))
83 {
84 // not a proper .NET version (e.g., five fields); can we get a valid four-part version number?
85 string version = null;
86 string[] versionParts = this.Facade.MsiPackage.ProductVersion.Split('.');
87 int count = versionParts.Length;
88 if (0 < count)
89 {
90 version = versionParts[0];
91 for (int i = 1; i < 4 && i < count; ++i)
92 {
93 version = String.Concat(version, ".", versionParts[i]);
94 }
95 }
96
97 if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version))
98 {
99 Messaging.Instance.OnMessage(WixWarnings.VersionTruncated(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath, version));
100 this.Facade.MsiPackage.ProductVersion = version;
101 }
102 else
103 {
104 Messaging.Instance.OnMessage(WixErrors.InvalidProductVersion(this.Facade.Package.SourceLineNumbers, this.Facade.MsiPackage.ProductVersion, sourcePath));
105 }
106 }
107
108 if (String.IsNullOrEmpty(this.Facade.Package.CacheId))
109 {
110 this.Facade.Package.CacheId = String.Format("{0}v{1}", this.Facade.MsiPackage.ProductCode, this.Facade.MsiPackage.ProductVersion);
111 }
112
113 if (String.IsNullOrEmpty(this.Facade.Package.DisplayName))
114 {
115 this.Facade.Package.DisplayName = ProcessMsiPackageCommand.GetProperty(db, "ProductName");
116 }
117
118 if (String.IsNullOrEmpty(this.Facade.Package.Description))
119 {
120 this.Facade.Package.Description = ProcessMsiPackageCommand.GetProperty(db, "ARPCOMMENTS");
121 }
122
123 ISet<string> payloadNames = this.GetPayloadTargetNames();
124
125 ISet<string> msiPropertyNames = this.GetMsiPropertyNames();
126
127 this.SetPerMachineAppropriately(db, sourcePath);
128
129 // Ensure the MSI package is appropriately marked visible or not.
130 this.SetPackageVisibility(db, msiPropertyNames);
131
132 // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance.
133 if (!msiPropertyNames.Contains("MSIFASTINSTALL") && !ProcessMsiPackageCommand.HasProperty(db, "MSIFASTINSTALL"))
134 {
135 this.AddMsiProperty("MSIFASTINSTALL", "7");
136 }
137
138 this.CreateRelatedPackages(db);
139
140 // If feature selection is enabled, represent the Feature table in the manifest.
141 if (this.Facade.MsiPackage.EnableFeatureSelection)
142 {
143 this.CreateMsiFeatures(db);
144 }
145
146 // Add all external cabinets as package payloads.
147 this.ImportExternalCabinetAsPayloads(db, packagePayload, payloadNames);
148
149 // Add all external files as package payloads and calculate the total install size as the rollup of
150 // File table's sizes.
151 this.Facade.Package.InstallSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, packagePayload, longNamesInImage, compressed, payloadNames);
152
153 // Add all dependency providers from the MSI.
154 this.ImportDependencyProviders(db);
155 }
156 }
157 catch (Dtf.InstallerException e)
158 {
159 Messaging.Instance.OnMessage(WixErrors.UnableToReadPackageInformation(this.Facade.Package.SourceLineNumbers, sourcePath, e.Message));
160 }
161 }
162
163 private ISet<string> GetPayloadTargetNames()
164 {
165 IEnumerable<string> payloadNames = this.PayloadTable.RowsAs<WixBundlePayloadRow>()
166 .Where(r => r.Package == this.Facade.Package.WixChainItemId)
167 .Select(r => r.Name);
168
169 return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase);
170 }
171
172 private ISet<string> GetMsiPropertyNames()
173 {
174 IEnumerable<string> properties = this.MsiPropertyTable.RowsAs<WixBundleMsiPropertyRow>()
175 .Where(r => r.ChainPackageId == this.Facade.Package.WixChainItemId)
176 .Select(r => r.Name);
177
178 return new HashSet<string>(properties, StringComparer.Ordinal);
179 }
180
181 private void SetPerMachineAppropriately(Dtf.Database db, string sourcePath)
182 {
183 if (this.Facade.MsiPackage.ForcePerMachine)
184 {
185 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
186 {
187 Messaging.Instance.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.Facade.Package.SourceLineNumbers, sourcePath));
188 this.Facade.Package.PerMachine = YesNoDefaultType.Yes; // ensure that we think the package is per-machine.
189 }
190
191 // Force ALLUSERS=1 via the MSI command-line.
192 this.AddMsiProperty("ALLUSERS", "1");
193 }
194 else
195 {
196 string allusers = ProcessMsiPackageCommand.GetProperty(db, "ALLUSERS");
197
198 if (String.IsNullOrEmpty(allusers))
199 {
200 // Not forced per-machine and no ALLUSERS property, flip back to per-user.
201 if (YesNoDefaultType.Yes == this.Facade.Package.PerMachine)
202 {
203 Messaging.Instance.OnMessage(WixWarnings.ImplicitlyPerUser(this.Facade.Package.SourceLineNumbers, sourcePath));
204 this.Facade.Package.PerMachine = YesNoDefaultType.No;
205 }
206 }
207 else if (allusers.Equals("1", StringComparison.Ordinal))
208 {
209 if (YesNoDefaultType.No == this.Facade.Package.PerMachine)
210 {
211 Messaging.Instance.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.Facade.Package.SourceLineNumbers, sourcePath));
212 }
213 }
214 else if (allusers.Equals("2", StringComparison.Ordinal))
215 {
216 Messaging.Instance.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.Facade.Package.PerMachine) ? "machine" : "user"));
217 }
218 else
219 {
220 Messaging.Instance.OnMessage(WixErrors.UnsupportedAllUsersValue(this.Facade.Package.SourceLineNumbers, sourcePath, allusers));
221 }
222 }
223 }
224
225 private void SetPackageVisibility(Dtf.Database db, ISet<string> msiPropertyNames)
226 {
227 bool alreadyVisible = !ProcessMsiPackageCommand.HasProperty(db, "ARPSYSTEMCOMPONENT");
228
229 if (alreadyVisible != this.Facade.Package.Visible) // if not already set to the correct visibility.
230 {
231 // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again.
232 if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT"))
233 {
234 this.AddMsiProperty("ARPSYSTEMCOMPONENT", this.Facade.Package.Visible ? String.Empty : "1");
235 }
236 }
237 }
238
239 private void CreateRelatedPackages(Dtf.Database db)
240 {
241 // Represent the Upgrade table as related packages.
242 if (db.Tables.Contains("Upgrade"))
243 {
244 using (Dtf.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`"))
245 {
246 view.Execute();
247 while (true)
248 {
249 using (Dtf.Record record = view.Fetch())
250 {
251 if (null == record)
252 {
253 break;
254 }
255
256 WixBundleRelatedPackageRow related = (WixBundleRelatedPackageRow)this.RelatedPackageTable.CreateRow(this.Facade.Package.SourceLineNumbers);
257 related.ChainPackageId = this.Facade.Package.WixChainItemId;
258 related.Id = record.GetString(1);
259 related.MinVersion = record.GetString(2);
260 related.MaxVersion = record.GetString(3);
261 related.Languages = record.GetString(4);
262
263 int attributes = record.GetInteger(5);
264 related.OnlyDetect = (attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect;
265 related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive;
266 related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive;
267 related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0;
268 }
269 }
270 }
271 }
272 }
273
274 private void CreateMsiFeatures(Dtf.Database db)
275 {
276 if (db.Tables.Contains("Feature"))
277 {
278 using (Dtf.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"))
279 using (Dtf.View componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?"))
280 {
281 using (Dtf.Record featureRecord = new Dtf.Record(1))
282 using (Dtf.Record componentRecord = new Dtf.Record(1))
283 {
284 using (Dtf.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`"))
285 {
286 allFeaturesView.Execute();
287
288 while (true)
289 {
290 using (Dtf.Record allFeaturesResultRecord = allFeaturesView.Fetch())
291 {
292 if (null == allFeaturesResultRecord)
293 {
294 break;
295 }
296
297 string featureName = allFeaturesResultRecord.GetString(1);
298
299 // Calculate the Feature size.
300 featureRecord.SetString(1, featureName);
301 featureView.Execute(featureRecord);
302
303 // Loop over all the components for the feature to calculate the size of the feature.
304 long size = 0;
305 while (true)
306 {
307 using (Dtf.Record componentResultRecord = featureView.Fetch())
308 {
309 if (null == componentResultRecord)
310 {
311 break;
312 }
313 string component = componentResultRecord.GetString(1);
314 componentRecord.SetString(1, component);
315 componentView.Execute(componentRecord);
316
317 while (true)
318 {
319 using (Dtf.Record fileResultRecord = componentView.Fetch())
320 {
321 if (null == fileResultRecord)
322 {
323 break;
324 }
325
326 string fileSize = fileResultRecord.GetString(1);
327 size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat);
328 }
329 }
330 }
331 }
332
333 WixBundleMsiFeatureRow feature = (WixBundleMsiFeatureRow)this.MsiFeatureTable.CreateRow(this.Facade.Package.SourceLineNumbers);
334 feature.ChainPackageId = this.Facade.Package.WixChainItemId;
335 feature.Name = featureName;
336 feature.Parent = allFeaturesResultRecord.GetString(2);
337 feature.Title = allFeaturesResultRecord.GetString(3);
338 feature.Description = allFeaturesResultRecord.GetString(4);
339 feature.Display = allFeaturesResultRecord.GetInteger(5);
340 feature.Level = allFeaturesResultRecord.GetInteger(6);
341 feature.Directory = allFeaturesResultRecord.GetString(7);
342 feature.Attributes = allFeaturesResultRecord.GetInteger(8);
343 feature.Size = size;
344 }
345 }
346 }
347 }
348 }
349 }
350 }
351
352 private void ImportExternalCabinetAsPayloads(Dtf.Database db, WixBundlePayloadRow packagePayload, ISet<string> payloadNames)
353 {
354 if (db.Tables.Contains("Media"))
355 {
356 foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`"))
357 {
358 if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal))
359 {
360 // If we didn't find the Payload as an existing child of the package, we need to
361 // add it. We expect the file to exist on-disk in the same relative location as
362 // the MSI expects to find it...
363 string cabinetName = Path.Combine(Path.GetDirectoryName(packagePayload.Name), cabinet);
364
365 if (!payloadNames.Contains(cabinetName))
366 {
367 string generatedId = Common.GenerateIdentifier("cab", packagePayload.Id, cabinet);
368 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
369
370 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
371 payload.Id = generatedId;
372 payload.Name = cabinetName;
373 payload.SourceFile = payloadSourceFile;
374 payload.Compressed = packagePayload.Compressed;
375 payload.UnresolvedSourceFile = cabinetName;
376 payload.Package = packagePayload.Package;
377 payload.Container = packagePayload.Container;
378 payload.ContentFile = true;
379 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
380 payload.Packaging = packagePayload.Packaging;
381 payload.ParentPackagePayload = packagePayload.Id;
382 }
383 }
384 }
385 }
386 }
387
388 private long ImportExternalFileAsPayloadsAndReturnInstallSize(Dtf.Database db, WixBundlePayloadRow packagePayload, bool longNamesInImage, bool compressed, ISet<string> payloadNames)
389 {
390 long size = 0;
391
392 if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File"))
393 {
394 Hashtable directories = new Hashtable();
395
396 // Load up the directory hash table so we will be able to resolve source paths
397 // for files in the MSI database.
398 using (Dtf.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`"))
399 {
400 view.Execute();
401 while (true)
402 {
403 using (Dtf.Record record = view.Fetch())
404 {
405 if (null == record)
406 {
407 break;
408 }
409
410 string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage);
411 directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName));
412 }
413 }
414 }
415
416 // Resolve the source paths to external files and add each file size to the total
417 // install size of the package.
418 using (Dtf.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`"))
419 {
420 view.Execute();
421 while (true)
422 {
423 using (Dtf.Record record = view.Fetch())
424 {
425 if (null == record)
426 {
427 break;
428 }
429
430 // Skip adding the loose files as payloads if it was suppressed.
431 if (!this.Facade.MsiPackage.SuppressLooseFilePayloadGeneration)
432 {
433 // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not
434 // explicitly marked compressed then this is an external file.
435 if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) ||
436 (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed)))
437 {
438 string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage);
439 string name = Path.Combine(Path.GetDirectoryName(packagePayload.Name), fileSourcePath);
440
441 if (!payloadNames.Contains(name))
442 {
443 string generatedId = Common.GenerateIdentifier("f", packagePayload.Id, record.GetString(2));
444 string payloadSourceFile = FileManager.ResolveRelatedFile(packagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.Facade.Package.SourceLineNumbers, BindStage.Normal);
445
446 WixBundlePayloadRow payload = (WixBundlePayloadRow)this.PayloadTable.CreateRow(this.Facade.Package.SourceLineNumbers);
447 payload.Id = generatedId;
448 payload.Name = name;
449 payload.SourceFile = payloadSourceFile;
450 payload.Compressed = packagePayload.Compressed;
451 payload.UnresolvedSourceFile = name;
452 payload.Package = packagePayload.Package;
453 payload.Container = packagePayload.Container;
454 payload.ContentFile = true;
455 payload.EnableSignatureValidation = packagePayload.EnableSignatureValidation;
456 payload.Packaging = packagePayload.Packaging;
457 payload.ParentPackagePayload = packagePayload.Id;
458 }
459 }
460 }
461
462 size += record.GetInteger(5);
463 }
464 }
465 }
466 }
467
468 return size;
469 }
470
471 private void AddMsiProperty(string name, string value)
472 {
473 WixBundleMsiPropertyRow row = (WixBundleMsiPropertyRow)this.MsiPropertyTable.CreateRow(this.Facade.MsiPackage.SourceLineNumbers);
474 row.ChainPackageId = this.Facade.Package.WixChainItemId;
475 row.Name = name;
476 row.Value = value;
477 }
478
479 private void ImportDependencyProviders(Dtf.Database db)
480 {
481 if (db.Tables.Contains("WixDependencyProvider"))
482 {
483 string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`";
484
485 using (Dtf.View view = db.OpenView(query))
486 {
487 view.Execute();
488 while (true)
489 {
490 using (Dtf.Record record = view.Fetch())
491 {
492 if (null == record)
493 {
494 break;
495 }
496
497 // Import the provider key and attributes.
498 string providerKey = record.GetString(1);
499 string version = record.GetString(2) ?? this.Facade.MsiPackage.ProductVersion;
500 string displayName = record.GetString(3) ?? this.Facade.Package.DisplayName;
501 int attributes = record.GetInteger(4);
502
503 ProvidesDependency dependency = new ProvidesDependency(providerKey, version, displayName, attributes);
504 dependency.Imported = true;
505
506 this.Facade.Provides.Add(dependency);
507 }
508 }
509 }
510 }
511 }
512
513 /// <summary>
514 /// Queries a Windows Installer database for a Property value.
515 /// </summary>
516 /// <param name="db">Database to query.</param>
517 /// <param name="property">Property to examine.</param>
518 /// <returns>String value for result or null if query doesn't match a single result.</returns>
519 private static string GetProperty(Dtf.Database db, string property)
520 {
521 try
522 {
523 return db.ExecuteScalar(PropertyQuery(property)).ToString();
524 }
525 catch (Dtf.InstallerException)
526 {
527 }
528
529 return null;
530 }
531
532 /// <summary>
533 /// Queries a Windows Installer database to determine if one or more rows exist in the Property table.
534 /// </summary>
535 /// <param name="db">Database to query.</param>
536 /// <param name="property">Property to examine.</param>
537 /// <returns>True if query matches at least one result.</returns>
538 private static bool HasProperty(Dtf.Database db, string property)
539 {
540 try
541 {
542 return 0 < db.ExecuteQuery(PropertyQuery(property)).Count;
543 }
544 catch (Dtf.InstallerException)
545 {
546 }
547
548 return false;
549 }
550
551 private static string PropertyQuery(string property)
552 {
553 // quick sanity check that we'll be creating a valid query...
554 // TODO: Are there any other special characters we should be looking for?
555 Debug.Assert(!property.Contains("'"));
556
557 return String.Format(CultureInfo.InvariantCulture, ProcessMsiPackageCommand.PropertySqlFormat, property);
558 }
559 }
560}