/// <summary> /// Gets whether certain properties are the same. /// </summary> /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param> /// <remarks>This is not the same as object equality, but only checks a subset of properties /// to determine if the objects are similar and could be merged into a collection.</remarks> /// <returns>True if certain properties are the same.</returns> internal bool Equals(ProvidesDependency other) { if (null != other) { return(this.Key == other.Key && this.Version == other.Version && this.DisplayName == other.DisplayName); } return(false); }
/// <summary> /// Initializes package state from the MSI contents. /// </summary> private void ResolveMsiPackage(BinderFileManager fileManager, Dictionary<string, PayloadInfoRow> allPayloads, Dictionary<string, ContainerInfo> containers, YesNoType suppressLooseFilePayloadGeneration, YesNoType enableFeatureSelection, YesNoType forcePerMachine, Output bundle) { string sourcePath = this.PackagePayload.FullFileName; bool longNamesInImage = false; bool compressed = false; try { // Read data out of the msi database... using (Microsoft.Deployment.WindowsInstaller.SummaryInfo sumInfo = new Microsoft.Deployment.WindowsInstaller.SummaryInfo(sourcePath, false)) { // 1 is the Word Count summary information stream bit that means // the MSI uses short file names when set. We care about long file // names so check when the bit is not set. longNamesInImage = 0 == (sumInfo.WordCount & 1); // 2 is the Word Count summary information stream bit that means // files are compressed in the MSI by default when the bit is set. compressed = 2 == (sumInfo.WordCount & 2); // 8 is the Word Count summary information stream bit that means // "Elevated privileges are not required to install this package." // in MSI 4.5 and below, if this bit is 0, elevation is required. this.PerMachine = (0 == (sumInfo.WordCount & 8)) ? YesNoDefaultType.Yes : YesNoDefaultType.No; } using (Microsoft.Deployment.WindowsInstaller.Database db = new Microsoft.Deployment.WindowsInstaller.Database(sourcePath)) { this.ProductCode = ChainPackageInfo.GetProperty(db, "ProductCode"); this.Language = ChainPackageInfo.GetProperty(db, "ProductLanguage"); this.Version = ChainPackageInfo.GetProperty(db, "ProductVersion"); if (!Common.IsValidModuleOrBundleVersion(this.Version)) { // not a proper .NET version (i.e., five fields); can we get a valid version number up to four fields? string version = null; string[] versionParts = this.Version.Split('.'); int count = versionParts.Length; if (0 < count) { version = versionParts[0]; for (int i = 1; i < 4 && i < count; ++i) { version = String.Concat(version, ".", versionParts[i]); } } if (!String.IsNullOrEmpty(version) && Common.IsValidModuleOrBundleVersion(version)) { this.core.OnMessage(WixWarnings.VersionTruncated(this.PackagePayload.SourceLineNumbers, this.Version, sourcePath, version)); this.Version = version; } else { this.core.OnMessage(WixErrors.InvalidProductVersion(this.PackagePayload.SourceLineNumbers, this.Version, sourcePath)); } } if (String.IsNullOrEmpty(this.CacheId)) { this.CacheId = String.Format("{0}v{1}", this.ProductCode, this.Version); } if (String.IsNullOrEmpty(this.DisplayName)) { this.DisplayName = ChainPackageInfo.GetProperty(db, "ProductName"); } this.Manufacturer = ChainPackageInfo.GetProperty(db, "Manufacturer"); this.VerifyMsiProperties(); if (YesNoType.Yes == forcePerMachine) { if (YesNoDefaultType.No == this.PerMachine) { this.core.OnMessage(WixWarnings.PerUserButForcingPerMachine(this.PackagePayload.SourceLineNumbers, sourcePath)); this.PerMachine = YesNoDefaultType.Yes; // ensure that we think the MSI is per-machine. } this.MsiProperties.Add(new MsiPropertyInfo(this.Id, "ALLUSERS", "1")); // force ALLUSERS=1 via the MSI command-line. } else if (ChainPackageInfo.HasProperty(db, "ALLUSERS")) { string allusers = ChainPackageInfo.GetProperty(db, "ALLUSERS"); if (allusers.Equals("1", StringComparison.Ordinal)) { if (YesNoDefaultType.No == this.PerMachine) { this.core.OnMessage(WixErrors.PerUserButAllUsersEquals1(this.PackagePayload.SourceLineNumbers, sourcePath)); } } else if (allusers.Equals("2", StringComparison.Ordinal)) { this.core.OnMessage(WixWarnings.DiscouragedAllUsersValue(this.PackagePayload.SourceLineNumbers, sourcePath, (YesNoDefaultType.Yes == this.PerMachine) ? "machine" : "user")); } else { this.core.OnMessage(WixErrors.UnsupportedAllUsersValue(this.PackagePayload.SourceLineNumbers, sourcePath, allusers)); } } else if (YesNoDefaultType.Yes == this.PerMachine) // not forced per-machine and no ALLUSERS property, flip back to per-user { this.core.OnMessage(WixWarnings.ImplicitlyPerUser(this.PackagePayload.SourceLineNumbers, sourcePath)); this.PerMachine = YesNoDefaultType.No; } if (String.IsNullOrEmpty(this.Description) && ChainPackageInfo.HasProperty(db, "ARPCOMMENTS")) { this.Description = ChainPackageInfo.GetProperty(db, "ARPCOMMENTS"); } // Ensure the MSI package is appropriately marked visible or not. bool alreadyVisible = !ChainPackageInfo.HasProperty(db, "ARPSYSTEMCOMPONENT"); if (alreadyVisible != this.Visible) // if not already set to the correct visibility. { // If the authoring specifically added "ARPSYSTEMCOMPONENT", don't do it again. bool sysComponentSet = false; foreach (MsiPropertyInfo propertyInfo in this.MsiProperties) { if ("ARPSYSTEMCOMPONENT".Equals(propertyInfo.Name, StringComparison.Ordinal)) { sysComponentSet = true; break; } } if (!sysComponentSet) { this.MsiProperties.Add(new MsiPropertyInfo(this.Id, "ARPSYSTEMCOMPONENT", this.Visible ? "" : "1")); } } // Unless the MSI or setup code overrides the default, set MSIFASTINSTALL for best performance. if (!ChainPackageInfo.HasProperty(db, "MSIFASTINSTALL")) { bool fastInstallSet = false; foreach (MsiPropertyInfo propertyInfo in this.MsiProperties) { if ("MSIFASTINSTALL".Equals(propertyInfo.Name, StringComparison.Ordinal)) { fastInstallSet = true; break; } } if (!fastInstallSet) { this.MsiProperties.Add(new MsiPropertyInfo(this.Id, "MSIFASTINSTALL", "7")); } } this.UpgradeCode = ChainPackageInfo.GetProperty(db, "UpgradeCode"); // Represent the Upgrade table as related packages. if (db.Tables.Contains("Upgrade") && !String.IsNullOrEmpty(this.UpgradeCode)) { using (Microsoft.Deployment.WindowsInstaller.View view = db.OpenView("SELECT `UpgradeCode`, `VersionMin`, `VersionMax`, `Language`, `Attributes` FROM `Upgrade`")) { view.Execute(); while (true) { using (Microsoft.Deployment.WindowsInstaller.Record record = view.Fetch()) { if (null == record) { break; } RelatedPackage related = new RelatedPackage(); related.Id = record.GetString(1); related.MinVersion = record.GetString(2); related.MaxVersion = record.GetString(3); string languages = record.GetString(4); if (!String.IsNullOrEmpty(languages)) { string[] splitLanguages = languages.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); related.Languages.AddRange(splitLanguages); } int attributes = record.GetInteger(5); // when an Upgrade row has an upgrade code different than this package's upgrade code, don't count it as a possible downgrade to this package related.OnlyDetect = ((attributes & MsiInterop.MsidbUpgradeAttributesOnlyDetect) == MsiInterop.MsidbUpgradeAttributesOnlyDetect) && this.UpgradeCode.Equals(related.Id, StringComparison.OrdinalIgnoreCase); related.MinInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMinInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMinInclusive; related.MaxInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive) == MsiInterop.MsidbUpgradeAttributesVersionMaxInclusive; related.LangInclusive = (attributes & MsiInterop.MsidbUpgradeAttributesLanguagesExclusive) == 0; this.RelatedPackages.Add(related); } } } } // If feature selection is enabled, represent the Feature table in the manifest. if (YesNoType.Yes == enableFeatureSelection && db.Tables.Contains("Feature")) { using (Microsoft.Deployment.WindowsInstaller.View featureView = db.OpenView("SELECT `Component_` FROM `FeatureComponents` WHERE `Feature_` = ?"), componentView = db.OpenView("SELECT `FileSize` FROM `File` WHERE `Component_` = ?")) { using (Microsoft.Deployment.WindowsInstaller.Record featureRecord = new Microsoft.Deployment.WindowsInstaller.Record(1), componentRecord = new Microsoft.Deployment.WindowsInstaller.Record(1)) { using (Microsoft.Deployment.WindowsInstaller.View allFeaturesView = db.OpenView("SELECT * FROM `Feature`")) { allFeaturesView.Execute(); while (true) { using (Microsoft.Deployment.WindowsInstaller.Record allFeaturesResultRecord = allFeaturesView.Fetch()) { if (null == allFeaturesResultRecord) { break; } MsiFeature feature = new MsiFeature(); string featureName = allFeaturesResultRecord.GetString(1); feature.Name = featureName; feature.Size = 0; feature.Parent = allFeaturesResultRecord.GetString(2); feature.Title = allFeaturesResultRecord.GetString(3); feature.Description = allFeaturesResultRecord.GetString(4); feature.Display = allFeaturesResultRecord.GetInteger(5); feature.Level = allFeaturesResultRecord.GetInteger(6); feature.Directory = allFeaturesResultRecord.GetString(7); feature.Attributes = allFeaturesResultRecord.GetInteger(8); this.MsiFeatures.Add(feature); // Determine Feature Size featureRecord.SetString(1, featureName); featureView.Execute(featureRecord); // Loop over all the components while (true) { using (Microsoft.Deployment.WindowsInstaller.Record componentResultRecord = featureView.Fetch()) { if (null == componentResultRecord) { break; } string component = componentResultRecord.GetString(1); componentRecord.SetString(1, component); componentView.Execute(componentRecord); // Loop over all the files while (true) { using (Microsoft.Deployment.WindowsInstaller.Record fileResultRecord = componentView.Fetch()) { if (null == fileResultRecord) { break; } string fileSize = fileResultRecord.GetString(1); feature.Size += Convert.ToInt32(fileSize, CultureInfo.InvariantCulture.NumberFormat); } } } } } } } } } } // Add all external cabinets as package payloads. if (db.Tables.Contains("Media")) { foreach (string cabinet in db.ExecuteStringQuery("SELECT `Cabinet` FROM `Media`")) { if (!String.IsNullOrEmpty(cabinet) && !cabinet.StartsWith("#", StringComparison.Ordinal)) { // If we didn't find the Payload as an existing child of the package, we need to // add it. We expect the file to exist on-disk in the same relative location as // the MSI expects to find it... string cabinetName = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), cabinet); if (!this.IsExistingPayload(cabinetName)) { string generatedId = Common.GenerateIdentifier("cab", true, this.PackagePayload.Id, cabinet); string payloadSourceFile = fileManager.ResolveRelatedFile(this.PackagePayload.UnresolvedSourceFile, cabinet, "Cabinet", this.PackagePayload.SourceLineNumbers, BindStage.Normal); PayloadInfoRow payloadNew = PayloadInfoRow.Create(this.SourceLineNumbers, bundle, generatedId, cabinetName, payloadSourceFile, true, this.PackagePayload.SuppressSignatureValidation, null, this.PackagePayload.Container, this.PackagePayload.Packaging); payloadNew.ParentPackagePayload = this.PackagePayload.Id; if (!String.IsNullOrEmpty(payloadNew.Container)) { containers[payloadNew.Container].Payloads.Add(payloadNew); } this.Payloads.Add(payloadNew); allPayloads.Add(payloadNew.Id, payloadNew); this.Size += payloadNew.FileSize; // add the newly added payload to the package size. } } } } // Add all external files as package payloads and calculate the total install size as the rollup of // File table's sizes. this.InstallSize = 0; if (db.Tables.Contains("Component") && db.Tables.Contains("Directory") && db.Tables.Contains("File")) { Hashtable directories = new Hashtable(); // Load up the directory hash table so we will be able to resolve source paths // for files in the MSI database. using (Microsoft.Deployment.WindowsInstaller.View view = db.OpenView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { view.Execute(); while (true) { using (Microsoft.Deployment.WindowsInstaller.Record record = view.Fetch()) { if (null == record) { break; } string sourceName = Installer.GetName(record.GetString(3), true, longNamesInImage); directories.Add(record.GetString(1), new ResolvedDirectory(record.GetString(2), sourceName)); } } } // Resolve the source paths to external files and add each file size to the total // install size of the package. using (Microsoft.Deployment.WindowsInstaller.View view = db.OpenView("SELECT `Directory_`, `File`, `FileName`, `File`.`Attributes`, `FileSize` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_`")) { view.Execute(); while (true) { using (Microsoft.Deployment.WindowsInstaller.Record record = view.Fetch()) { if (null == record) { break; } // Skip adding the loose files as payloads if it was suppressed. if (suppressLooseFilePayloadGeneration != YesNoType.Yes) { // If the file is explicitly uncompressed or the MSI is uncompressed and the file is not // explicitly marked compressed then this is an external file. if (MsiInterop.MsidbFileAttributesNoncompressed == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesNoncompressed) || (!compressed && 0 == (record.GetInteger(4) & MsiInterop.MsidbFileAttributesCompressed))) { string generatedId = Common.GenerateIdentifier("f", true, this.PackagePayload.Id, record.GetString(2)); string fileSourcePath = Binder.GetFileSourcePath(directories, record.GetString(1), record.GetString(3), compressed, longNamesInImage); string payloadSourceFile = fileManager.ResolveRelatedFile(this.PackagePayload.UnresolvedSourceFile, fileSourcePath, "File", this.PackagePayload.SourceLineNumbers, BindStage.Normal); string name = Path.Combine(Path.GetDirectoryName(this.PackagePayload.Name), fileSourcePath); if (!this.IsExistingPayload(name)) { PayloadInfoRow payloadNew = PayloadInfoRow.Create(this.SourceLineNumbers, bundle, generatedId, name, payloadSourceFile, true, this.PackagePayload.SuppressSignatureValidation, null, this.PackagePayload.Container, this.PackagePayload.Packaging); payloadNew.ParentPackagePayload = this.PackagePayload.Id; if (!String.IsNullOrEmpty(payloadNew.Container)) { containers[payloadNew.Container].Payloads.Add(payloadNew); } this.Payloads.Add(payloadNew); allPayloads.Add(payloadNew.Id, payloadNew); this.Size += payloadNew.FileSize; // add the newly added payload to the package size. } } } this.InstallSize += record.GetInteger(5); } } } } // Import any dependency providers from the MSI. if (db.Tables.Contains("WixDependencyProvider")) { // Use the old schema (v1) if the Version column does not exist. bool hasVersion = db.Tables["WixDependencyProvider"].Columns.Contains("Version"); string query = "SELECT `ProviderKey`, `Version`, `DisplayName`, `Attributes` FROM `WixDependencyProvider`"; if (!hasVersion) { query = "SELECT `ProviderKey`, `Attributes` FROM `WixDependencyProvider`"; } using (Microsoft.Deployment.WindowsInstaller.View view = db.OpenView(query)) { view.Execute(); while (true) { using (Microsoft.Deployment.WindowsInstaller.Record record = view.Fetch()) { if (null == record) { break; } // Import the provider key and attributes. ProvidesDependency dependency = null; string providerKey = record.GetString(1); if (hasVersion) { string version = record.GetString(2) ?? this.Version; string displayName = record.GetString(3) ?? this.DisplayName; int attributes = record.GetInteger(4); dependency = new ProvidesDependency(providerKey, version, displayName, attributes); } else { int attributes = record.GetInteger(2); dependency = new ProvidesDependency(providerKey, this.Version, this.DisplayName, attributes); } dependency.Imported = true; this.Provides.Add(dependency); } } } } } } catch (Microsoft.Deployment.WindowsInstaller.InstallerException e) { this.core.OnMessage(WixErrors.UnableToReadPackageInformation(this.PackagePayload.SourceLineNumbers, sourcePath, e.Message)); } }
/// <summary> /// Imports authored dependency providers for each package in the manifest, /// and generates dependency providers for certain package types that do not /// have a provider defined. /// </summary> /// <param name="bundle">The <see cref="Output"/> object for the bundle.</param> /// <param name="packages">An indexed collection of chained packages.</param> private void ProcessDependencyProviders(Output bundle, Dictionary<string, ChainPackageInfo> packages) { // First import any authored dependencies. These may merge with imported provides from MSI packages. Table wixDependencyProviderTable = bundle.Tables["WixDependencyProvider"]; if (null != wixDependencyProviderTable && 0 < wixDependencyProviderTable.Rows.Count) { // Add package information for each dependency provider authored into the manifest. foreach (Row wixDependencyProviderRow in wixDependencyProviderTable.Rows) { string packageId = (string)wixDependencyProviderRow[1]; ChainPackageInfo package = null; if (packages.TryGetValue(packageId, out package)) { ProvidesDependency dependency = new ProvidesDependency(wixDependencyProviderRow); if (String.IsNullOrEmpty(dependency.Key)) { switch (package.ChainPackageType) { // The WixDependencyExtension allows an empty Key for MSIs and MSPs. case Compiler.ChainPackageType.Msi: dependency.Key = package.ProductCode; break; case Compiler.ChainPackageType.Msp: dependency.Key = package.PatchCode; break; } } if (String.IsNullOrEmpty(dependency.Version)) { dependency.Version = package.Version; } // If the version is still missing, a version could not be harvested from the package and was not authored. if (String.IsNullOrEmpty(dependency.Version)) { this.core.OnMessage(WixErrors.MissingDependencyVersion(package.Id)); } if (String.IsNullOrEmpty(dependency.DisplayName)) { dependency.DisplayName = package.DisplayName; } if (!package.Provides.Merge(dependency)) { this.core.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, package.Id)); } } } } // Generate providers for MSI packages that still do not have providers. foreach (ChainPackageInfo package in packages.Values) { if (Compiler.ChainPackageType.Msi == package.ChainPackageType && 0 == package.Provides.Count) { ProvidesDependency dependency = new ProvidesDependency(package.ProductCode, package.Version, package.DisplayName, 0); if (!package.Provides.Merge(dependency)) { this.core.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, package.Id)); } } else if (Compiler.ChainPackageType.Msp == package.ChainPackageType && 0 == package.Provides.Count) { ProvidesDependency dependency = new ProvidesDependency(package.PatchCode, package.Version, package.DisplayName, 0); if (!package.Provides.Merge(dependency)) { this.core.OnMessage(WixErrors.DuplicateProviderDependencyKey(dependency.Key, package.Id)); } } } }
/// <summary> /// Gets whether certain properties are the same. /// </summary> /// <param name="other">Another <see cref="ProvidesDependency"/> to compare.</param> /// <remarks>This is not the same as object equality, but only checks a subset of properties /// to determine if the objects are similar and could be merged into a collection.</remarks> /// <returns>True if certain properties are the same.</returns> internal bool Equals(ProvidesDependency other) { if (null != other) { return this.Key == other.Key && this.Version == other.Version && this.DisplayName == other.DisplayName; } return false; }