| 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.Generic; |
| 7 | using System.Diagnostics; |
| 8 | using System.Globalization; |
| 9 | using System.IO; |
| 10 | using System.Linq; |
| 11 | using WixToolset.Data; |
| 12 | using WixToolset.Extensibility; |
| 13 | using WixToolset.Extensibility.Services; |
| 14 | using WixToolset.Data.Symbols; |
| 15 | using WixToolset.Data.WindowsInstaller; |
| 16 | using WixToolset.Extensibility.Data; |
| 17 | using WixToolset.Core.Native.Msi; |
| 18 | |
| 19 | /// <summary> |
| 20 | /// Initializes package state from the MSI contents. |
| 21 | /// </summary> |
| 22 | internal class ProcessMsiPackageCommand |
| 23 | { |
| 24 | private const string PropertySqlQuery = "SELECT `Value` FROM `Property` WHERE `Property` = ?"; |
| 25 | |
| 26 | public ProcessMsiPackageCommand(IServiceProvider serviceProvider, IEnumerable<IBurnBackendBinderExtension> backendExtensions, IntermediateSection section, PackageFacade facade, Dictionary<string, WixBundlePayloadSymbol> packagePayloads) |
| 27 | { |
| 28 | this.Messaging = serviceProvider.GetService<IMessaging>(); |
| 29 | this.BackendHelper = serviceProvider.GetService<IBackendHelper>(); |
| 30 | this.PathResolver = serviceProvider.GetService<IPathResolver>(); |
| 31 | |
| 32 | this.BackendExtensions = backendExtensions; |
| 33 | |
| 34 | this.PackagePayloads = packagePayloads; |
| 35 | this.Section = section; |
| 36 | |
| 37 | this.ChainPackage = facade.PackageSymbol; |
| 38 | this.MsiPackage = (WixBundleMsiPackageSymbol)facade.SpecificPackageSymbol; |
| 39 | this.PackagePayload = packagePayloads[this.ChainPackage.PayloadRef]; |
| 40 | } |
| 41 | |
| 42 | private IMessaging Messaging { get; } |
| 43 | |
| 44 | private IBackendHelper BackendHelper { get; } |
| 45 | |
| 46 | private IPathResolver PathResolver { get; } |
| 47 | |
| 48 | private IEnumerable<IBurnBackendBinderExtension> BackendExtensions { get; } |
| 49 | |
| 50 | private Dictionary<string, WixBundlePayloadSymbol> PackagePayloads { get; } |
| 51 | |
| 52 | private WixBundlePackageSymbol ChainPackage { get; } |
| 53 | |
| 54 | private WixBundleMsiPackageSymbol MsiPackage { get; } |
| 55 | |
| 56 | private string PackageId => this.ChainPackage.Id.Id; |
| 57 | |
| 58 | private WixBundlePayloadSymbol PackagePayload { get; } |
| 59 | |
| 60 | private IntermediateSection Section { get; } |
| 61 | |
| 62 | /// <summary> |
| 63 | /// Processes the MSI packages to add properties and payloads from the MSI packages. |
| 64 | /// </summary> |
| 65 | public void Execute() |
| 66 | { |
| 67 | var harvestedMsiPackage = this.Section.Symbols.OfType<WixBundleHarvestedMsiPackageSymbol>() |
| 68 | .Where(h => h.Id.Id == this.PackagePayload.Id.Id) |
| 69 | .SingleOrDefault(); |
| 70 | |
| 71 | if (harvestedMsiPackage == null) |
| 72 | { |
| 73 | harvestedMsiPackage = this.HarvestPackage(); |
| 74 | |
| 75 | if (harvestedMsiPackage == null) |
| 76 | { |
| 77 | return; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | foreach (var childPayload in this.Section.Symbols.OfType<WixBundlePayloadSymbol>().Where(p => p.ParentPackagePayloadRef == this.PackagePayload.Id.Id).ToList()) |
| 82 | { |
| 83 | this.Section.AddSymbol(new WixGroupSymbol(childPayload.SourceLineNumbers) |
| 84 | { |
| 85 | ParentType = ComplexReferenceParentType.Package, |
| 86 | ParentId = this.PackageId, |
| 87 | ChildType = ComplexReferenceChildType.Payload, |
| 88 | ChildId = childPayload.Id.Id, |
| 89 | }); |
| 90 | } |
| 91 | |
| 92 | this.ChainPackage.PerMachine = harvestedMsiPackage.PerMachine; |
| 93 | this.ChainPackage.Win64 = harvestedMsiPackage.Win64; |
| 94 | |
| 95 | this.MsiPackage.ProductCode = harvestedMsiPackage.ProductCode; |
| 96 | this.MsiPackage.UpgradeCode = harvestedMsiPackage.UpgradeCode; |
| 97 | this.MsiPackage.Manufacturer = harvestedMsiPackage.Manufacturer; |
| 98 | this.MsiPackage.ProductLanguage = Convert.ToInt32(harvestedMsiPackage.ProductLanguage, CultureInfo.InvariantCulture); |
| 99 | this.MsiPackage.ProductVersion = harvestedMsiPackage.ProductVersion; |
| 100 | |
| 101 | if (String.IsNullOrEmpty(this.ChainPackage.CacheId)) |
| 102 | { |
| 103 | this.ChainPackage.CacheId = CacheIdGenerator.GenerateLocalCacheId(this.Messaging, harvestedMsiPackage, this.PackagePayload, this.MsiPackage.SourceLineNumbers, "MsiPackage"); |
| 104 | } |
| 105 | |
| 106 | if (String.IsNullOrEmpty(this.ChainPackage.DisplayName)) |
| 107 | { |
| 108 | this.ChainPackage.DisplayName = harvestedMsiPackage.ProductName; |
| 109 | } |
| 110 | |
| 111 | if (String.IsNullOrEmpty(this.ChainPackage.Description)) |
| 112 | { |
| 113 | this.ChainPackage.Description = harvestedMsiPackage.ArpComments; |
| 114 | } |
| 115 | |
| 116 | if (String.IsNullOrEmpty(this.ChainPackage.Version)) |
| 117 | { |
| 118 | this.ChainPackage.Version = this.MsiPackage.ProductVersion; |
| 119 | } |
| 120 | |
| 121 | if (!this.BackendHelper.IsValidMsiProductVersion(this.MsiPackage.ProductVersion)) |
| 122 | { |
| 123 | this.Messaging.Write(WarningMessages.InvalidMsiProductVersion(this.PackagePayload.SourceLineNumbers, this.MsiPackage.ProductVersion, this.PackageId)); |
| 124 | } |
| 125 | |
| 126 | this.SetPerMachineAppropriately(harvestedMsiPackage.AllUsers); |
| 127 | |
| 128 | var msiPropertyNames = this.GetMsiPropertyNames(); |
| 129 | |
| 130 | // Ensure the MSI package is appropriately marked visible or not. |
| 131 | this.SetPackageVisibility(harvestedMsiPackage.ArpSystemComponent, msiPropertyNames); |
| 132 | |
| 133 | // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. |
| 134 | if (String.IsNullOrEmpty(harvestedMsiPackage.MsiFastInstall) && !msiPropertyNames.Contains("MSIFASTINSTALL")) |
| 135 | { |
| 136 | this.AddMsiProperty("MSIFASTINSTALL", "7"); |
| 137 | } |
| 138 | |
| 139 | this.ChainPackage.InstallSize = harvestedMsiPackage.InstallSize; |
| 140 | } |
| 141 | |
| 142 | public WixBundleHarvestedMsiPackageSymbol HarvestPackage() |
| 143 | { |
| 144 | bool perMachine; |
| 145 | bool win64; |
| 146 | string productName; |
| 147 | string arpComments; |
| 148 | string allUsers; |
| 149 | string msiFastInstall; |
| 150 | string arpSystemComponent; |
| 151 | string productCode; |
| 152 | string upgradeCode; |
| 153 | string manufacturer; |
| 154 | string productLanguage; |
| 155 | string productVersion; |
| 156 | long installSize; |
| 157 | |
| 158 | var sourcePath = this.PackagePayload.SourceFile.Path; |
| 159 | |
| 160 | try |
| 161 | { |
| 162 | var longNamesInImage = false; |
| 163 | var compressed = false; |
| 164 | |
| 165 | this.CheckIfWindowsInstallerFileTooLarge(this.PackagePayload.SourceLineNumbers, sourcePath, "MSI"); |
| 166 | |
| 167 | using (var db = new Database(sourcePath, OpenDatabase.ReadOnly)) |
| 168 | { |
| 169 | // Read data out of the msi database... |
| 170 | using (var sumInfo = new SummaryInformation(db)) |
| 171 | { |
| 172 | var fileAndElevateFlags = sumInfo.GetNumericProperty(SummaryInformation.Package.FileAndElevatedFlags); |
| 173 | var platformsAndLanguages = sumInfo.GetProperty(SummaryInformation.Package.PlatformsAndLanguages); |
| 174 | |
| 175 | // 1 is the Word Count summary information stream bit that means |
| 176 | // the MSI uses short file names when set. We care about long file |
| 177 | // names so check when the bit is not set. |
| 178 | |
| 179 | longNamesInImage = 0 == (fileAndElevateFlags & 1); |
| 180 | |
| 181 | // 2 is the Word Count summary information stream bit that means |
| 182 | // files are compressed in the MSI by default when the bit is set. |
| 183 | compressed = 2 == (fileAndElevateFlags & 2); |
| 184 | |
| 185 | // 8 is the Word Count summary information stream bit that means |
| 186 | // "Elevated privileges are not required to install this package." |
| 187 | // in MSI 4.5 and below, if this bit is 0, elevation is required. |
| 188 | perMachine = (0 == (fileAndElevateFlags & 8)); |
| 189 | win64 = this.IsWin64(sourcePath, platformsAndLanguages); |
| 190 | } |
| 191 | |
| 192 | using (var view = db.OpenView(PropertySqlQuery)) |
| 193 | { |
| 194 | productName = ProcessMsiPackageCommand.GetProperty(view, "ProductName"); |
| 195 | arpComments = ProcessMsiPackageCommand.GetProperty(view, "ARPCOMMENTS"); |
| 196 | allUsers = ProcessMsiPackageCommand.GetProperty(view, "ALLUSERS"); |
| 197 | msiFastInstall = ProcessMsiPackageCommand.GetProperty(view, "MSIFASTINSTALL"); |
| 198 | arpSystemComponent = ProcessMsiPackageCommand.GetProperty(view, "ARPSYSTEMCOMPONENT"); |
| 199 | |
| 200 | productCode = ProcessMsiPackageCommand.GetProperty(view, "ProductCode"); |
| 201 | upgradeCode = ProcessMsiPackageCommand.GetProperty(view, "UpgradeCode"); |
| 202 | manufacturer = ProcessMsiPackageCommand.GetProperty(view, "Manufacturer"); |
| 203 | productLanguage = ProcessMsiPackageCommand.GetProperty(view, "ProductLanguage"); |
| 204 | productVersion = ProcessMsiPackageCommand.GetProperty(view, "ProductVersion"); |
| 205 | } |
| 206 | |
| 207 | var payloadNames = this.GetPayloadTargetNames(); |
| 208 | |
| 209 | this.CreateRelatedPackages(db); |
| 210 | |
| 211 | this.CreateMsiFeatures(db); |
| 212 | |
| 213 | // Add all external cabinets as package payloads. |
| 214 | this.ImportExternalCabinetAsPayloads(db, payloadNames); |
| 215 | |
| 216 | // Add all external files as package payloads and calculate the total install size as the rollup of |
| 217 | // File table's sizes. |
| 218 | installSize = this.ImportExternalFileAsPayloadsAndReturnInstallSize(db, longNamesInImage, compressed, payloadNames); |
| 219 | |
| 220 | // Add all dependency providers from the MSI. |
| 221 | this.ImportDependencyProviders(db); |
| 222 | } |
| 223 | } |
| 224 | catch (MsiException e) |
| 225 | { |
| 226 | this.Messaging.Write(ErrorMessages.UnableToReadPackageInformation(this.PackagePayload.SourceLineNumbers, sourcePath, e.Message)); |
| 227 | return null; |
| 228 | } |
| 229 | |
| 230 | return this.Section.AddSymbol(new WixBundleHarvestedMsiPackageSymbol(this.PackagePayload.SourceLineNumbers, this.PackagePayload.Id) |
| 231 | { |
| 232 | PerMachine = perMachine, |
| 233 | Win64 = win64, |
| 234 | ProductName = productName, |
| 235 | ArpComments = arpComments, |
| 236 | AllUsers = allUsers, |
| 237 | MsiFastInstall = msiFastInstall, |
| 238 | ArpSystemComponent = arpSystemComponent, |
| 239 | ProductCode = productCode, |
| 240 | UpgradeCode = upgradeCode, |
| 241 | Manufacturer = manufacturer, |
| 242 | ProductLanguage = productLanguage, |
| 243 | ProductVersion = productVersion, |
| 244 | InstallSize = installSize, |
| 245 | }); |
| 246 | } |
| 247 | |
| 248 | private ISet<string> GetPayloadTargetNames() |
| 249 | { |
| 250 | var payloadNames = this.PackagePayloads.Values.Select(p => p.Name); |
| 251 | |
| 252 | return new HashSet<string>(payloadNames, StringComparer.OrdinalIgnoreCase); |
| 253 | } |
| 254 | |
| 255 | private ISet<string> GetMsiPropertyNames() |
| 256 | { |
| 257 | var properties = this.Section.Symbols.OfType<WixBundleMsiPropertySymbol>() |
| 258 | .Where(p => p.PackageRef == this.PackageId) |
| 259 | .Select(p => p.Name); |
| 260 | |
| 261 | return new HashSet<string>(properties, StringComparer.Ordinal); |
| 262 | } |
| 263 | |
| 264 | // https://docs.microsoft.com/en-us/windows/win32/msi/template-summary |
| 265 | private bool IsWin64(string sourcePath, string platformsAndLanguages) |
| 266 | { |
| 267 | var separatorIndex = platformsAndLanguages.IndexOf(';'); |
| 268 | var platformValue = separatorIndex > 0 ? platformsAndLanguages.Substring(0, separatorIndex) : platformsAndLanguages; |
| 269 | |
| 270 | switch (platformValue) |
| 271 | { |
| 272 | case "Arm64": |
| 273 | case "Intel64": |
| 274 | case "x64": |
| 275 | return true; |
| 276 | |
| 277 | case "Arm": |
| 278 | case "Intel": |
| 279 | return false; |
| 280 | |
| 281 | default: |
| 282 | this.Messaging.Write(BurnBackendWarnings.UnknownMsiPackagePlatform(this.PackagePayload.SourceLineNumbers, sourcePath, platformValue)); |
| 283 | return true; |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | private void SetPerMachineAppropriately(string allusers) |
| 288 | { |
| 289 | Debug.Assert(this.ChainPackage.PerMachine.HasValue); |
| 290 | var perMachine = this.ChainPackage.PerMachine.Value; |
| 291 | |
| 292 | // Can ignore ALLUSERS from MsiProperties because it is not allowed there. |
| 293 | if (this.MsiPackage.ForcePerMachine) |
| 294 | { |
| 295 | if (!perMachine) |
| 296 | { |
| 297 | this.Messaging.Write(WarningMessages.PerUserButForcingPerMachine(this.PackagePayload.SourceLineNumbers, this.PackageId)); |
| 298 | this.ChainPackage.PerMachine = true; // ensure that we think the package is per-machine. |
| 299 | } |
| 300 | |
| 301 | // Force ALLUSERS=1 via the MSI command-line. |
| 302 | this.AddMsiProperty("ALLUSERS", "1"); |
| 303 | } |
| 304 | else |
| 305 | { |
| 306 | if (String.IsNullOrEmpty(allusers)) |
| 307 | { |
| 308 | // Not forced per-machine and no ALLUSERS property, flip back to per-user. |
| 309 | if (perMachine) |
| 310 | { |
| 311 | this.Messaging.Write(WarningMessages.ImplicitlyPerUser(this.ChainPackage.SourceLineNumbers, this.PackageId)); |
| 312 | this.ChainPackage.PerMachine = false; |
| 313 | } |
| 314 | } |
| 315 | else if (allusers.Equals("1", StringComparison.Ordinal)) |
| 316 | { |
| 317 | if (!perMachine) |
| 318 | { |
| 319 | this.Messaging.Write(ErrorMessages.PerUserButAllUsersEquals1(this.ChainPackage.SourceLineNumbers, this.PackageId)); |
| 320 | } |
| 321 | } |
| 322 | else if (allusers.Equals("2", StringComparison.Ordinal)) |
| 323 | { |
| 324 | this.Messaging.Write(WarningMessages.DiscouragedAllUsersValue(this.ChainPackage.SourceLineNumbers, this.PackageId, perMachine ? "machine" : "user")); |
| 325 | } |
| 326 | else |
| 327 | { |
| 328 | this.Messaging.Write(ErrorMessages.UnsupportedAllUsersValue(this.ChainPackage.SourceLineNumbers, this.PackageId, allusers)); |
| 329 | } |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | private void SetPackageVisibility(string systemComponent, ISet<string> msiPropertyNames) |
| 334 | { |
| 335 | // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. |
| 336 | if (!msiPropertyNames.Contains("ARPSYSTEMCOMPONENT")) |
| 337 | { |
| 338 | var alreadyVisible = String.IsNullOrEmpty(systemComponent); |
| 339 | var visible = this.ChainPackage.Visible; |
| 340 | |
| 341 | // If not already set to the correct visibility. |
| 342 | if (alreadyVisible != visible) |
| 343 | { |
| 344 | this.AddMsiProperty("ARPSYSTEMCOMPONENT", visible ? String.Empty : "1"); |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | |
| 349 | private void CreateRelatedPackages(Database db) |
| 350 | { |
| 351 | // Represent the Upgrade table as related packages. |
| 352 | if (db.TableExists("Upgrade")) |
| 353 | { |
| 354 | using (var view = db.OpenExecuteView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) |
| 355 | { |
| 356 | foreach (var record in view.Records) |
| 357 | { |
| 358 | var recordAttributes = record.GetInteger(5); |
| 359 | |
| 360 | var attributes = WixBundleRelatedPackageAttributes.None; |
| 361 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect) == WindowsInstallerConstants.MsidbUpgradeAttributesOnlyDetect ? WixBundleRelatedPackageAttributes.OnlyDetect : 0; |
| 362 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMinInclusive ? WixBundleRelatedPackageAttributes.MinInclusive : 0; |
| 363 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesVersionMaxInclusive ? WixBundleRelatedPackageAttributes.MaxInclusive : 0; |
| 364 | attributes |= (recordAttributes & WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive) == WindowsInstallerConstants.MsidbUpgradeAttributesLanguagesExclusive ? 0 : WixBundleRelatedPackageAttributes.LangInclusive; |
| 365 | |
| 366 | this.Section.AddSymbol(new WixBundleRelatedPackageSymbol(this.PackagePayload.SourceLineNumbers) |
| 367 | { |
| 368 | PackagePayloadRef = this.PackagePayload.Id.Id, |
| 369 | RelatedId = record.GetString(1), |
| 370 | MinVersion = record.GetString(2), |
| 371 | MaxVersion = record.GetString(3), |
| 372 | Languages = record.GetString(4), |
| 373 | Attributes = attributes, |
| 374 | }); |
| 375 | } |
| 376 | } |
| 377 | } |
| 378 | } |
| 379 | |
| 380 | private void CreateMsiFeatures(Database db) |
| 381 | { |
| 382 | if (db.TableExists("Feature") && db.TableExists("FeatureComponents")) |
| 383 | { |
| 384 | using (var allFeaturesView = db.OpenExecuteView("SELECT * FROM `Feature`")) |
| 385 | using (var featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?")) |
| 386 | using (var componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) |
| 387 | { |
| 388 | using (var featureRecord = new Record(1)) |
| 389 | using (var componentRecord = new Record(1)) |
| 390 | { |
| 391 | foreach (var allFeaturesResultRecord in allFeaturesView.Records) |
| 392 | { |
| 393 | var featureName = allFeaturesResultRecord.GetString(1); |
| 394 | |
| 395 | // Calculate the Feature size. |
| 396 | featureRecord.SetString(1, featureName); |
| 397 | featureView.Execute(featureRecord); |
| 398 | |
| 399 | // Loop over all the components for the feature to calculate the size of the feature. |
| 400 | long size = 0; |
| 401 | foreach (var componentResultRecord in featureView.Records) |
| 402 | { |
| 403 | var component = componentResultRecord.GetString(1); |
| 404 | componentRecord.SetString(1, component); |
| 405 | componentView.Execute(componentRecord); |
| 406 | |
| 407 | foreach (var fileResultRecord in componentView.Records) |
| 408 | { |
| 409 | var fileSize = fileResultRecord.GetString(1); |
| 410 | size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); |
| 411 | } |
| 412 | } |
| 413 | |
| 414 | this.Section.AddSymbol(new WixBundleMsiFeatureSymbol(this.PackagePayload.SourceLineNumbers, new Identifier(AccessModifier.Section, this.PackagePayload.Id.Id, featureName)) |
| 415 | { |
| 416 | PackagePayloadRef = this.PackagePayload.Id.Id, |
| 417 | Name = featureName, |
| 418 | Parent = allFeaturesResultRecord.GetString(2), |
| 419 | Title = allFeaturesResultRecord.GetString(3), |
| 420 | Description = allFeaturesResultRecord.GetString(4), |
| 421 | Display = allFeaturesResultRecord.GetInteger(5), |
| 422 | Level = allFeaturesResultRecord.GetInteger(6), |
| 423 | Directory = allFeaturesResultRecord.GetString(7), |
| 424 | Attributes = allFeaturesResultRecord.GetInteger(8), |
| 425 | Size = size |
| 426 | }); |
| 427 | } |
| 428 | } |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | private void ImportExternalCabinetAsPayloads(Database db, ISet<string> payloadNames) |
| 434 | { |
| 435 | if (db.TableExists("Media")) |
| 436 | { |
| 437 | using (var view = db.OpenExecuteView("SELECT `Cabinet` FROM `Media`")) |
| 438 | { |
| 439 | var sourceLineNumbers = this.PackagePayload.SourceLineNumbers; |
| 440 | |
| 441 | foreach (var cabinetRecord in view.Records) |
| 442 | { |
| 443 | var cabinet = cabinetRecord.GetString(1); |
| 444 | |
| 445 | if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) |
| 446 | { |
| 447 | // If we didn't find the Payload as an existing child of the package, we need to |
| 448 | // add it. We expect the file to exist on-disk in the same relative location as |
| 449 | // the MSI expects to find it... |
| 450 | var cabinetName = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), cabinet); |
| 451 | |
| 452 | if (!payloadNames.Contains(cabinetName)) |
| 453 | { |
| 454 | var generatedId = this.BackendHelper.GenerateIdentifier("cab", this.PackagePayload.Id.Id, cabinet); |
| 455 | var payloadSourceFile = this.ResolveRelatedFile(this.PackagePayload.SourceFile.Path, this.PackagePayload.UnresolvedSourceFile, cabinet, "cabinet", sourceLineNumbers); |
| 456 | |
| 457 | this.Section.AddSymbol(new WixBundlePayloadSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) |
| 458 | { |
| 459 | Name = cabinetName, |
| 460 | SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, |
| 461 | Compressed = this.PackagePayload.Compressed, |
| 462 | UnresolvedSourceFile = cabinetName, |
| 463 | ContainerRef = this.PackagePayload.ContainerRef, |
| 464 | DownloadUrl = this.PackagePayload.DownloadUrl, |
| 465 | Packaging = this.PackagePayload.Packaging, |
| 466 | ParentPackagePayloadRef = this.PackagePayload.Id.Id, |
| 467 | }); |
| 468 | |
| 469 | this.CheckIfWindowsInstallerFileTooLarge(sourceLineNumbers, payloadSourceFile, "cabinet"); |
| 470 | } |
| 471 | } |
| 472 | } |
| 473 | } |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | private long ImportExternalFileAsPayloadsAndReturnInstallSize(Database db, bool longNamesInImage, bool compressed, ISet<string> payloadNames) |
| 478 | { |
| 479 | long size = 0; |
| 480 | |
| 481 | if (db.TableExists("Component") && db.TableExists("Directory") && db.TableExists("File")) |
| 482 | { |
| 483 | var directories = new Dictionary<string, IResolvedDirectory>(); |
| 484 | |
| 485 | // Load up the directory hash table so we will be able to resolve source paths |
| 486 | // for files in the MSI database. |
| 487 | using (var view = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) |
| 488 | { |
| 489 | foreach (var record in view.Records) |
| 490 | { |
| 491 | var sourceName = this.BackendHelper.GetMsiFileName(record.GetString(3), true, longNamesInImage); |
| 492 | |
| 493 | var resolvedDirectory = this.BackendHelper.CreateResolvedDirectory(record.GetString(2), sourceName); |
| 494 | |
| 495 | directories.Add(record.GetString(1), resolvedDirectory); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | // Resolve the source paths to external files and add each file size to the total |
| 500 | // install size of the package. |
| 501 | using (var view = db.OpenExecuteView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) |
| 502 | { |
| 503 | var sourceLineNumbers = this.PackagePayload.SourceLineNumbers; |
| 504 | |
| 505 | foreach (var record in view.Records) |
| 506 | { |
| 507 | // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not |
| 508 | // explicitly marked compressed then this is an external file. |
| 509 | var compressionBit = record.GetInteger(4); |
| 510 | if (WindowsInstallerConstants.MsidbFileAttributesNoncompressed == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesNoncompressed) || |
| 511 | (!compressed && 0 == (compressionBit & WindowsInstallerConstants.MsidbFileAttributesCompressed))) |
| 512 | { |
| 513 | var fileSourcePath = this.PathResolver.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); |
| 514 | var name = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), fileSourcePath); |
| 515 | |
| 516 | if (!payloadNames.Contains(name)) |
| 517 | { |
| 518 | var generatedId = this.BackendHelper.GenerateIdentifier("f", this.PackagePayload.Id.Id, record.GetString(2)); |
| 519 | var payloadSourceFile = this.ResolveRelatedFile(this.PackagePayload.SourceFile.Path, this.PackagePayload.UnresolvedSourceFile, fileSourcePath, "payload", sourceLineNumbers); |
| 520 | |
| 521 | this.Section.AddSymbol(new WixBundlePayloadSymbol(sourceLineNumbers, new Identifier(AccessModifier.Section, generatedId)) |
| 522 | { |
| 523 | Name = name, |
| 524 | SourceFile = new IntermediateFieldPathValue { Path = payloadSourceFile }, |
| 525 | Compressed = this.PackagePayload.Compressed, |
| 526 | UnresolvedSourceFile = name, |
| 527 | ContainerRef = this.PackagePayload.ContainerRef, |
| 528 | DownloadUrl = this.PackagePayload.DownloadUrl, |
| 529 | Packaging = this.PackagePayload.Packaging, |
| 530 | ParentPackagePayloadRef = this.PackagePayload.Id.Id, |
| 531 | }); |
| 532 | } |
| 533 | } |
| 534 | |
| 535 | size += record.GetInteger(5); |
| 536 | } |
| 537 | } |
| 538 | } |
| 539 | |
| 540 | return size; |
| 541 | } |
| 542 | |
| 543 | private void AddMsiProperty(string name, string value) |
| 544 | { |
| 545 | this.Section.AddSymbol(new WixBundleMsiPropertySymbol(this.PackagePayload.SourceLineNumbers, new Identifier(AccessModifier.Section, this.PackageId, name)) |
| 546 | { |
| 547 | PackageRef = this.PackageId, |
| 548 | Name = name, |
| 549 | Value = value, |
| 550 | }); |
| 551 | } |
| 552 | |
| 553 | private void ImportDependencyProviders(Database db) |
| 554 | { |
| 555 | this.ImportDependencyProvidersFromTable(db, "WixDependencyProvider"); |
| 556 | this.ImportDependencyProvidersFromTable(db, "Wix4DependencyProvider"); |
| 557 | } |
| 558 | |
| 559 | private void ImportDependencyProvidersFromTable(Database db, string tableName) |
| 560 | { |
| 561 | if (db.TableExists(tableName)) |
| 562 | { |
| 563 | using (var view = db.OpenExecuteView($"SELECT `WixDependencyProvider`, `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `{tableName}`")) |
| 564 | { |
| 565 | foreach (var record in view.Records) |
| 566 | { |
| 567 | var id = new Identifier(AccessModifier.Section, this.BackendHelper.GenerateIdentifier("dep", this.PackagePayload.Id.Id, record.GetString(1))); |
| 568 | |
| 569 | // Import the provider key and attributes. |
| 570 | this.Section.AddSymbol(new WixBundleHarvestedDependencyProviderSymbol(this.PackagePayload.SourceLineNumbers, id) |
| 571 | { |
| 572 | PackagePayloadRef = this.PackagePayload.Id.Id, |
| 573 | ProviderKey = record.GetString(2), |
| 574 | Version = record.GetString(3) ?? this.MsiPackage.ProductVersion, |
| 575 | DisplayName = record.GetString(4) ?? this.ChainPackage.DisplayName, |
| 576 | ProviderAttributes = record.GetInteger(5), |
| 577 | }); |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | private string ResolveRelatedFile(string resolvedSource, string unresolvedSource, string relatedSource, string type, SourceLineNumber sourceLineNumbers) |
| 584 | { |
| 585 | var checkedPaths = new List<string>(); |
| 586 | |
| 587 | foreach (var extension in this.BackendExtensions) |
| 588 | { |
| 589 | var resolved = extension.ResolveRelatedFile(unresolvedSource, relatedSource, type, sourceLineNumbers); |
| 590 | |
| 591 | if (resolved?.CheckedPaths != null) |
| 592 | { |
| 593 | checkedPaths.AddRange(resolved.CheckedPaths); |
| 594 | } |
| 595 | |
| 596 | if (!String.IsNullOrEmpty(resolved?.Path)) |
| 597 | { |
| 598 | return resolved?.Path; |
| 599 | } |
| 600 | } |
| 601 | |
| 602 | var resolvedPath = Path.Combine(Path.GetDirectoryName(resolvedSource), relatedSource); |
| 603 | |
| 604 | if (!File.Exists(resolvedPath)) |
| 605 | { |
| 606 | checkedPaths.Add(resolvedPath); |
| 607 | this.Messaging.Write(ErrorMessages.FileNotFound(sourceLineNumbers, resolvedPath, type, checkedPaths)); |
| 608 | } |
| 609 | |
| 610 | return resolvedPath; |
| 611 | } |
| 612 | |
| 613 | private void CheckIfWindowsInstallerFileTooLarge(SourceLineNumber sourceLineNumber, string path, string description) |
| 614 | { |
| 615 | // Best effort check to see if the file is too large for the Windows Installer. |
| 616 | try |
| 617 | { |
| 618 | var fi = new FileInfo(path); |
| 619 | if (fi.Length > Int32.MaxValue) |
| 620 | { |
| 621 | this.Messaging.Write(WarningMessages.WindowsInstallerFileTooLarge(sourceLineNumber, path, description)); |
| 622 | } |
| 623 | } |
| 624 | catch |
| 625 | { |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | private static string GetProperty(View view, string property) |
| 630 | { |
| 631 | using (var queryRecord = new Record(1)) |
| 632 | { |
| 633 | queryRecord[1] = property; |
| 634 | |
| 635 | view.Execute(queryRecord); |
| 636 | |
| 637 | using (var record = view.Fetch()) |
| 638 | { |
| 639 | return record?.GetString(1); |
| 640 | } |
| 641 | } |
| 642 | } |
| 643 | } |
| 644 | } |