private void GenerateBAManifestPayloadTables(Output bundle, List<ChainPackageInfo> chainPackages, Dictionary<string, PayloadInfoRow> payloads) { Table wixPayloadPropertiesTable = bundle.EnsureTable(this.core.TableDefinitions["WixPayloadProperties"]); foreach (ChainPackageInfo package in chainPackages) { PayloadInfoRow packagePayload = payloads[package.Payload]; Row payloadRow = wixPayloadPropertiesTable.CreateRow(packagePayload.SourceLineNumbers); payloadRow[0] = packagePayload.Id; payloadRow[1] = package.Id; payloadRow[2] = packagePayload.Container; payloadRow[3] = packagePayload.Name; payloadRow[4] = packagePayload.FileSize.ToString(); payloadRow[5] = packagePayload.DownloadUrl; payloadRow[6] = packagePayload.LayoutOnly ? "yes" : "no"; foreach (PayloadInfoRow childPayload in package.Payloads) { payloadRow = wixPayloadPropertiesTable.CreateRow(childPayload.SourceLineNumbers); payloadRow[0] = childPayload.Id; payloadRow[1] = package.Id; payloadRow[2] = childPayload.Container; payloadRow[3] = childPayload.Name; payloadRow[4] = childPayload.FileSize.ToString(); payloadRow[5] = childPayload.DownloadUrl; payloadRow[6] = childPayload.LayoutOnly ? "yes" : "no"; } } foreach (PayloadInfoRow payload in payloads.Values) { if (payload.LayoutOnly) { Row row = wixPayloadPropertiesTable.CreateRow(payload.SourceLineNumbers); row[0] = payload.Id; row[1] = null; row[2] = payload.Container; row[3] = payload.Name; row[4] = payload.FileSize.ToString(); row[5] = payload.DownloadUrl; row[6] = payload.LayoutOnly ? "yes" : "no"; } } }
/// <summary> /// Create the #transform for the given main transform. /// </summary> /// <param name="patchId">patch GUID from patch authoring.</param> /// <param name="mainTransform">transform generated by torch.</param> /// <param name="mediaRow">media authored into patch.</param> /// <param name="productCode">output string to receive ProductCode.</param> public Output BuildPairedTransform(string patchId, Output mainTransform, MediaRow mediaRow, ref string productCode) { Output pairedTransform = new Output(null); pairedTransform.Type = OutputType.Transform; pairedTransform.Codepage = mainTransform.Codepage; // lookup productVersion property to correct summaryInformation string newProductVersion = null; Table mainPropertyTable = mainTransform.Tables["Property"]; if (mainPropertyTable != null) { foreach (Row row in mainPropertyTable.Rows) { if ("ProductVersion" == (string)row[0]) { newProductVersion = (string)row[1]; } } } // TODO: build class for manipulating SummaryInformation table Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; // add required properties Hashtable mainSummaryRows = new Hashtable(); foreach (Row mainSummaryRow in mainSummaryTable.Rows) { mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; } if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) { Row mainSummaryRow = mainSummaryTable.CreateRow(null); mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; mainSummaryRow[1] = "0"; } // copy summary information from core transform Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); foreach (Row mainSummaryRow in mainSummaryTable.Rows) { string value = (string)mainSummaryRow[1]; switch ((SummaryInformation.Transform)mainSummaryRow[0]) { case SummaryInformation.Transform.ProductCodes: string[] propertyData = value.Split(';'); string oldProductVersion = propertyData[0].Substring(38); string upgradeCode = propertyData[2]; productCode = propertyData[0].Substring(0, 38); if (newProductVersion == null) { newProductVersion = oldProductVersion; } // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); break; case SummaryInformation.Transform.ValidationFlags: // TODO: ensure this row exists in mainSummaryTable!!!! // TODO: author these settings in patch XML or set in torch.exe int i = Convert.ToInt32(value); i |= (int)SummaryInformation.TransformFlags.ErrorAddExistingRow; i |= (int)SummaryInformation.TransformFlags.ErrorDeleteMissingRow; i |= (int)SummaryInformation.TransformFlags.ErrorAddExistingTable; i |= (int)SummaryInformation.TransformFlags.ErrorDeleteMissingTable; i |= (int)SummaryInformation.TransformFlags.ErrorUpdateMissingRow; i |= (int)SummaryInformation.TransformFlags.ValidateProduct; mainSummaryRow[1] = value = i.ToString(); break; } Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); pairedSummaryRow[0] = mainSummaryRow[0]; pairedSummaryRow[1] = value; } if (productCode == null) { throw new InvalidOperationException("Could not determine ProductCode from transform summary information"); } // copy File table Table mainFileTable = mainTransform.Tables["File"]; Table mainWixFileTable = mainTransform.Tables["WixFile"]; if (mainFileTable != null) { FileRowCollection mainFileRows = new FileRowCollection(); mainFileRows.AddRange(mainFileTable.Rows); Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); foreach (Row mainWixFileRow in mainWixFileTable.Rows) { FileRow mainFileRow = mainFileRows[(string)mainWixFileRow[0]]; // set File.Sequence to non null to satisfy transform bind mainFileRow.Sequence = 1; // delete's don't need rows in the paired transform if (mainFileRow.Operation == RowOperation.Delete) { continue; } FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); pairedFileRow.Operation = RowOperation.Modify; for (int i = 0; i < mainFileRow.Fields.Length; i++) { object value = mainFileRow[i]; pairedFileRow[i] = value; } // override authored media for patch bind // TODO: consider using File/@DiskId for patch media mainFileRow.DiskId = mediaRow.DiskId; mainWixFileRow[5] = mediaRow.DiskId; // suppress any change to File.Sequence to avoid bloat mainFileRow.Fields[7].Modified = false; // force File row to appear in the transform if (RowOperation.Modify == mainFileRow.Operation) { mainFileRow.Operation = RowOperation.Modify; pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Modify; } else if (RowOperation.Add == mainFileRow.Operation) { // set msidbFileAttributesPatchAdded pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Add; } else { pairedFileRow.Attributes = mainFileRow.Attributes; pairedFileRow.Fields[6].Modified = false; } } } // add Media row to pairedTransform Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); Row pairedMediaRow = pairedMediaTable.CreateRow(null); pairedMediaRow.Operation = RowOperation.Add; for (int i = 0; i < mediaRow.Fields.Length; i++) { pairedMediaRow[i] = mediaRow[i]; } // add PatchPackage for this Media Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); pairedPackageTable.Operation = TableOperation.Add; Row pairedPackageRow = pairedPackageTable.CreateRow(null); pairedPackageRow.Operation = RowOperation.Add; pairedPackageRow[0] = patchId; pairedPackageRow[1] = mediaRow.DiskId; // add property to both identify client patches and whether those patches are removable or not string patchPropertyId = new Guid(patchId).ToString("N", CultureInfo.InvariantCulture).ToUpper(); int allowRemoval = 0; Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; if (null != msiPatchMetadataTable) { foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) { if (string.Empty == (string)msiPatchMetadataRow[0] && "AllowRemoval" == (string)msiPatchMetadataRow[1]) { allowRemoval = Convert.ToInt32((string)msiPatchMetadataRow[2]); } } } Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); pairedPropertyTable.Operation = TableOperation.Add; Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = string.Format(CultureInfo.InvariantCulture, "_{0}.AllowRemoval", patchPropertyId); pairedPropertyRow[1] = allowRemoval.ToString(); return(pairedTransform); }
private void GenerateBAManifestPackageTables(Output bundle, List<ChainPackageInfo> chainPackages) { Table wixPackagePropertiesTable = bundle.EnsureTable(this.core.TableDefinitions["WixPackageProperties"]); foreach (ChainPackageInfo package in chainPackages) { Row row = wixPackagePropertiesTable.CreateRow(package.SourceLineNumbers); row[0] = package.Id; row[1] = package.Vital ? "yes" : "no"; row[2] = package.DisplayName; row[3] = package.Description; row[4] = package.Size.ToString(CultureInfo.InvariantCulture); // TODO: DownloadSize (compressed) (what does this mean when it's embedded?) row[5] = package.Size.ToString(CultureInfo.InvariantCulture); // Package.Size (uncompressed) row[6] = package.InstallSize.ToString(CultureInfo.InvariantCulture); // InstallSize (required disk space) row[7] = package.ChainPackageType.ToString(CultureInfo.InvariantCulture); row[8] = package.Permanent ? "yes" : "no"; row[9] = package.LogPathVariable; row[10] = package.RollbackLogPathVariable; row[11] = (PackagingType.Embedded == package.PackagePayload.Packaging) ? "yes" : "no"; row[12] = package.DisplayInternalUI ? "yes" : "no"; if (!String.IsNullOrEmpty(package.ProductCode)) { row[13] = package.ProductCode; } if (!String.IsNullOrEmpty(package.UpgradeCode)) { row[14] = package.UpgradeCode; } if (!String.IsNullOrEmpty(package.Version)) { row[15] = package.Version; } if (!String.IsNullOrEmpty(package.InstallCondition)) { row[16] = package.InstallCondition; } switch (package.Cache) { case YesNoAlwaysType.No: row[17] = "no"; break; case YesNoAlwaysType.Yes: row[17] = "yes"; break; case YesNoAlwaysType.Always: row[17] = "always"; break; } Table wixPackageFeatureInfoTable = bundle.EnsureTable(this.core.TableDefinitions["WixPackageFeatureInfo"]); foreach (MsiFeature feature in package.MsiFeatures) { Row packageFeatureInfoRow = wixPackageFeatureInfoTable.CreateRow(package.SourceLineNumbers); packageFeatureInfoRow[0] = package.Id; packageFeatureInfoRow[1] = feature.Name; packageFeatureInfoRow[2] = Convert.ToString(feature.Size, CultureInfo.InvariantCulture); packageFeatureInfoRow[3] = feature.Parent; packageFeatureInfoRow[4] = feature.Title; packageFeatureInfoRow[5] = feature.Description; packageFeatureInfoRow[6] = Convert.ToString(feature.Display, CultureInfo.InvariantCulture); packageFeatureInfoRow[7] = Convert.ToString(feature.Level, CultureInfo.InvariantCulture); packageFeatureInfoRow[8] = feature.Directory; packageFeatureInfoRow[9] = Convert.ToString(feature.Attributes, CultureInfo.InvariantCulture); } } }
public Output Link(SectionCollection sections, ArrayList transforms, OutputType expectedOutputType) { Output output = null; try { SymbolCollection allSymbols; Section entrySection; bool containsModuleSubstitution = false; bool containsModuleConfiguration = false; StringCollection referencedSymbols = new StringCollection(); ArrayList unresolvedReferences = new ArrayList(); ConnectToFeatureCollection componentsToFeatures = new ConnectToFeatureCollection(); ConnectToFeatureCollection featuresToFeatures = new ConnectToFeatureCollection(); ConnectToFeatureCollection modulesToFeatures = new ConnectToFeatureCollection(); this.activeOutput = null; this.encounteredError = false; SortedList adminProperties = new SortedList(); SortedList secureProperties = new SortedList(); SortedList hiddenProperties = new SortedList(); RowCollection actionRows = new RowCollection(); RowCollection suppressActionRows = new RowCollection(); TableDefinitionCollection customTableDefinitions = new TableDefinitionCollection(); RowCollection customRows = new RowCollection(); StringCollection generatedShortFileNameIdentifiers = new StringCollection(); Hashtable generatedShortFileNames = new Hashtable(); Hashtable multipleFeatureComponents = new Hashtable(); Hashtable wixVariables = new Hashtable(); // verify that modularization types match for foreign key relationships foreach (TableDefinition tableDefinition in this.tableDefinitions) { foreach (ColumnDefinition columnDefinition in tableDefinition.Columns) { if (null != columnDefinition.KeyTable && 0 > columnDefinition.KeyTable.IndexOf(';') && columnDefinition.IsKeyColumnSet) { try { TableDefinition keyTableDefinition = this.tableDefinitions[columnDefinition.KeyTable]; if (0 >= columnDefinition.KeyColumn || keyTableDefinition.Columns.Count < columnDefinition.KeyColumn) { this.OnMessage(WixErrors.InvalidKeyColumn(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn)); } else if (keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType != columnDefinition.ModularizeType && ColumnModularizeType.CompanionFile != columnDefinition.ModularizeType) { this.OnMessage(WixErrors.CollidingModularizationTypes(tableDefinition.Name, columnDefinition.Name, columnDefinition.KeyTable, columnDefinition.KeyColumn, columnDefinition.ModularizeType.ToString(), keyTableDefinition.Columns[columnDefinition.KeyColumn - 1].ModularizeType.ToString())); } } catch (WixMissingTableDefinitionException) { // ignore missing table definitions - this error is caught in other places } } } } // add in the extension sections foreach (WixExtension extension in this.extensions) { Library library = extension.GetLibrary(this.tableDefinitions); if (null != library) { sections.AddRange(library.Sections); } } // first find the entry section and create the symbols hash for all the sections sections.FindEntrySectionAndLoadSymbols(this.allowIdenticalRows, this, expectedOutputType, out entrySection, out allSymbols); // should have found an entry section by now if (null == entrySection) { throw new WixException(WixErrors.MissingEntrySection(expectedOutputType.ToString())); } // add the missing standard action symbols this.LoadStandardActionSymbols(allSymbols); // now that we know where we're starting from, create the output object output = new Output(null); output.EntrySection = entrySection; // Note: this entry section will get added to the Output.Sections collection later if (null != this.localizer && -1 != this.localizer.Codepage) { output.Codepage = this.localizer.Codepage; } this.activeOutput = output; // Resolve the symbol references to find the set of sections we // care about for linking. Of course, we start with the entry // section (that's how it got its name after all). output.Sections.AddRange(output.EntrySection.ResolveReferences(output.Type, allSymbols, referencedSymbols, unresolvedReferences, this)); // Flattening the complex references that participate in groups. this.FlattenSectionsComplexReferences(output.Sections); if (this.encounteredError) { return null; } // The hard part in linking is processing the complex references. this.ProcessComplexReferences(output, output.Sections, referencedSymbols, componentsToFeatures, featuresToFeatures, modulesToFeatures); for (int i = 0; i < unresolvedReferences.Count; ++i) { Section.SimpleReferenceSection referenceSection = (Section.SimpleReferenceSection)unresolvedReferences[i]; if (this.allowUnresolvedReferences) { this.OnMessage(WixWarnings.UnresolvedReferenceWarning(referenceSection.WixSimpleReferenceRow.SourceLineNumbers, referenceSection.Section.Type.ToString(), referenceSection.Section.Id, referenceSection.WixSimpleReferenceRow.SymbolicName)); } else { this.OnMessage(WixErrors.UnresolvedReference(referenceSection.WixSimpleReferenceRow.SourceLineNumbers, referenceSection.Section.Type.ToString(), referenceSection.Section.Id, referenceSection.WixSimpleReferenceRow.SymbolicName)); } } if (this.encounteredError) { return null; } SymbolCollection unreferencedSymbols = output.Sections.GetOrphanedSymbols(referencedSymbols, this); // Display a warning message for Components that were never referenced by a Feature. foreach (Symbol symbol in unreferencedSymbols) { if ("Component" == symbol.Row.Table.Name) { this.OnMessage(WixErrors.OrphanedComponent(symbol.Row.SourceLineNumbers, (string)symbol.Row[0])); } } Dictionary<string, List<Symbol>> duplicatedSymbols = output.Sections.GetDuplicateSymbols(this); // Display a warning message for Components that were never referenced by a Feature. foreach (List<Symbol> duplicatedSymbolList in duplicatedSymbols.Values) { Symbol symbol = duplicatedSymbolList[0]; // Certain tables allow duplicates because they allow overrides. if (symbol.Row.Table.Name != "WixAction" && symbol.Row.Table.Name != "WixVariable") { this.OnMessage(WixErrors.DuplicateSymbol(symbol.Row.SourceLineNumbers, symbol.Name)); for (int i = 1; i < duplicatedSymbolList.Count; i++) { Symbol duplicateSymbol = duplicatedSymbolList[i]; this.OnMessage(WixErrors.DuplicateSymbol2(duplicateSymbol.Row.SourceLineNumbers)); } } } if (this.encounteredError) { return null; } if (null != this.unreferencedSymbolsFile) { sections.GetOrphanedSymbols(referencedSymbols, this).OutputSymbols(this.unreferencedSymbolsFile); } // resolve the feature to feature connects this.ResolveFeatureToFeatureConnects(featuresToFeatures, allSymbols); // start generating OutputTables and OutputRows for all the sections in the output RowCollection ensureTableRows = new RowCollection(); int sectionCount = 0; foreach (Section section in output.Sections) { sectionCount++; string sectionId = section.Id; if (null == sectionId && this.sectionIdOnRows) { sectionId = "wix.section." + sectionCount.ToString(CultureInfo.InvariantCulture); } foreach (Table table in section.Tables) { // By default, copy rows unless we've been asked to drop unreal tables from // the output and it's an unreal table and *not* a UX Manifest table. bool copyRows = true; if (this.dropUnrealTables && table.Definition.IsUnreal && !table.Definition.IsBootstrapperApplicationData) { copyRows = false; } // handle special tables switch (table.Name) { case "AppSearch": this.activeOutput.EnsureTable(this.tableDefinitions["Signature"]); break; case "Class": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 11, componentsToFeatures, multipleFeatureComponents); } break; case "ChainPackage": case "ChainPackageGroup": case "MsiProperty": copyRows = true; break; case "CustomAction": if (OutputType.Module == this.activeOutput.Type) { this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); } foreach (Row row in table.Rows) { // For script CAs that specify HideTarget we should also hide the CA data property for the action. int bits = Convert.ToInt32(row[1]); if (MsiInterop.MsidbCustomActionTypeHideTarget == (bits & MsiInterop.MsidbCustomActionTypeHideTarget) && MsiInterop.MsidbCustomActionTypeInScript == (bits & MsiInterop.MsidbCustomActionTypeInScript)) { hiddenProperties[Convert.ToString(row[0])] = null; } } break; case "Dialog": this.activeOutput.EnsureTable(this.tableDefinitions["ListBox"]); break; case "Directory": foreach (Row row in table.Rows) { if (OutputType.Module == this.activeOutput.Type) { string directory = row[0].ToString(); if (Util.IsStandardDirectory(directory)) { // if the directory table contains references to standard windows folders // mergemod.dll will add customactions to set the MSM directory to // the same directory as the standard windows folder and will add references to // custom action to all the standard sequence tables. A problem will occur // if the MSI does not have these tables as mergemod.dll does not add these // tables to the MSI if absent. This code adds the tables in case mergemod.dll // needs them. this.activeOutput.EnsureTable(this.tableDefinitions["CustomAction"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdminUISequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["AdvtExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallExecuteSequence"]); this.activeOutput.EnsureTable(this.tableDefinitions["InstallUISequence"]); } else { foreach (string standardDirectory in Util.StandardDirectories.Keys) { if (directory.StartsWith(standardDirectory, StringComparison.Ordinal)) { this.OnMessage(WixWarnings.StandardDirectoryConflictInMergeModule(row.SourceLineNumbers, directory, standardDirectory)); } } } } } break; case "Extension": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 1, 4, componentsToFeatures, multipleFeatureComponents); } break; case "ModuleSubstitution": containsModuleSubstitution = true; break; case "ModuleConfiguration": containsModuleConfiguration = true; break; case "MsiAssembly": if (this.suppressMsiAssemblyTable) { copyRows = false; } else if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 0, 1, componentsToFeatures, multipleFeatureComponents); } break; case "ProgId": // the Extension table is required with a ProgId table this.activeOutput.EnsureTable(this.tableDefinitions["Extension"]); break; case "Property": for (int i = 0; i < table.Rows.Count; i++) { if (null == table.Rows[i][1]) { table.Rows.RemoveAt(i); i--; } } break; case "PublishComponent": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 4, componentsToFeatures, multipleFeatureComponents); } break; case "Shortcut": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 3, 4, componentsToFeatures, multipleFeatureComponents); } break; case "TypeLib": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 2, 6, componentsToFeatures, multipleFeatureComponents); } break; case "Upgrade": foreach (UpgradeRow row in table.Rows) { secureProperties[row.ActionProperty] = null; } break; case "Variable": copyRows = true; break; case "WixAction": if (this.sectionIdOnRows) { foreach (Row row in table.Rows) { row.SectionId = sectionId; } } actionRows.AddRange(table.Rows); break; case "WixBBControl": case "WixControl": copyRows = true; break; case "WixCustomTable": this.LinkCustomTable(table, customTableDefinitions); copyRows = false; // we've created table definitions from these rows, no need to process them any longer break; case "WixCustomRow": foreach (Row row in table.Rows) { row.SectionId = (this.sectionIdOnRows ? sectionId : null); customRows.Add(row); } copyRows = false; break; case "WixEnsureTable": ensureTableRows.AddRange(table.Rows); break; case "WixFile": foreach (Row row in table.Rows) { // DiskId is not valid when creating a module, so set it to // 0 for all files to ensure proper sorting in the binder if (OutputType.Module == this.activeOutput.Type) { row[5] = 0; } // if the short file name was generated, check for collisions if (0x1 == (int)row[9]) { generatedShortFileNameIdentifiers.Add((string)row[0]); } } copyRows = true; break; case "WixFragment": copyRows = true; break; case "WixGroup": copyRows = true; break; case "WixInstanceTransforms": copyRows = true; break; case "WixMedia": copyRows = true; break; case "WixMerge": if (OutputType.Product == output.Type) { this.ResolveFeatures(table.Rows, 0, 7, modulesToFeatures, null); } copyRows = true; break; case "WixOrdering": copyRows = true; break; case "WixProperty": foreach (WixPropertyRow wixPropertyRow in table.Rows) { if (wixPropertyRow.Admin) { adminProperties[wixPropertyRow.Id] = null; } if (wixPropertyRow.Hidden) { hiddenProperties[wixPropertyRow.Id] = null; } if (wixPropertyRow.Secure) { secureProperties[wixPropertyRow.Id] = null; } } break; case "WixSuppressAction": suppressActionRows.AddRange(table.Rows); break; case "WixSuppressModularization": // just copy the rows to the output copyRows = true; break; case "WixVariable": // check for colliding values and collect the wix variable rows foreach (WixVariableRow row in table.Rows) { WixVariableRow collidingRow = (WixVariableRow)wixVariables[row.Id]; if (null == collidingRow || (collidingRow.Overridable && !row.Overridable)) { wixVariables[row.Id] = row; } else if (!row.Overridable || (collidingRow.Overridable && row.Overridable)) { this.OnMessage(WixErrors.WixVariableCollision(row.SourceLineNumbers, row.Id)); } } copyRows = false; break; case "WixPatchRef": case "WixPatchBaseline": case "WixPatchId": copyRows = true; break; } if (copyRows) { Table outputTable = this.activeOutput.EnsureTable(this.tableDefinitions[table.Name]); this.CopyTableRowsToOutputTable(table, outputTable, sectionId); } } } // Verify that there were no duplicate fragment Id's. Table wixFragmentTable = this.activeOutput.Tables["WixFragment"]; Hashtable fragmentIdIndex = new Hashtable(); if (null != wixFragmentTable) { foreach (Row row in wixFragmentTable.Rows) { string fragmentId = row.Fields[0].Data.ToString(); if (!fragmentIdIndex.ContainsKey(fragmentId)) { fragmentIdIndex.Add(fragmentId, row.SourceLineNumbers); } else { this.OnMessage(WixErrors.DuplicateSymbol(row.SourceLineNumbers, fragmentId)); if (null != fragmentIdIndex[fragmentId]) { this.OnMessage(WixErrors.DuplicateSymbol2((SourceLineNumberCollection)fragmentIdIndex[fragmentId])); } } } } // copy the module to feature connections into the output if (0 < modulesToFeatures.Count) { Table wixFeatureModulesTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixFeatureModules"]); foreach (ConnectToFeature connectToFeature in modulesToFeatures) { foreach (string feature in connectToFeature.ConnectFeatures) { Row row = wixFeatureModulesTable.CreateRow(null); row[0] = feature; row[1] = connectToFeature.ChildId; } } } // ensure the creation of tables that need to exist if (0 < ensureTableRows.Count) { foreach (Row row in ensureTableRows) { string tableId = (string)row[0]; TableDefinition tableDef = null; try { tableDef = this.tableDefinitions[tableId]; } catch (WixMissingTableDefinitionException) { tableDef = customTableDefinitions[tableId]; } this.activeOutput.EnsureTable(tableDef); } } // copy all the suppress action rows to the output to suppress actions from merge modules if (0 < suppressActionRows.Count) { Table suppressActionTable = this.activeOutput.EnsureTable(this.tableDefinitions["WixSuppressAction"]); suppressActionTable.Rows.AddRange(suppressActionRows); } // sequence all the actions this.SequenceActions(actionRows, suppressActionRows); // check for missing table and add them or display an error as appropriate switch (this.activeOutput.Type) { case OutputType.Module: this.activeOutput.EnsureTable(this.tableDefinitions["Component"]); this.activeOutput.EnsureTable(this.tableDefinitions["Directory"]); this.activeOutput.EnsureTable(this.tableDefinitions["FeatureComponents"]); this.activeOutput.EnsureTable(this.tableDefinitions["File"]); this.activeOutput.EnsureTable(this.tableDefinitions["ModuleComponents"]); this.activeOutput.EnsureTable(this.tableDefinitions["ModuleSignature"]); break; case OutputType.PatchCreation: Table imageFamiliesTable = this.activeOutput.Tables["ImageFamilies"]; Table targetImagesTable = this.activeOutput.Tables["TargetImages"]; Table upgradedImagesTable = this.activeOutput.Tables["UpgradedImages"]; if (null == imageFamiliesTable || 1 > imageFamiliesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("ImageFamilies")); } if (null == targetImagesTable || 1 > targetImagesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("TargetImages")); } if (null == upgradedImagesTable || 1 > upgradedImagesTable.Rows.Count) { this.OnMessage(WixErrors.ExpectedRowInPatchCreationPackage("UpgradedImages")); } this.activeOutput.EnsureTable(this.tableDefinitions["Properties"]); break; case OutputType.Product: this.activeOutput.EnsureTable(this.tableDefinitions["File"]); this.activeOutput.EnsureTable(this.tableDefinitions["Media"]); break; } this.CheckForIllegalTables(this.activeOutput); // add the custom row data foreach (Row row in customRows) { TableDefinition customTableDefinition = (TableDefinition)customTableDefinitions[row[0].ToString()]; Table customTable = this.activeOutput.EnsureTable(customTableDefinition); Row customRow = customTable.CreateRow(row.SourceLineNumbers); customRow.SectionId = row.SectionId; string[] data = row[1].ToString().Split(Common.CustomRowFieldSeparator); for (int i = 0; i < data.Length; ++i) { bool foundColumn = false; string[] item = data[i].Split(colonCharacter, 2); for (int j = 0; j < customRow.Fields.Length; ++j) { if (customRow.Fields[j].Column.Name == item[0]) { if (0 < item[1].Length) { if (ColumnType.Number == customRow.Fields[j].Column.Type) { try { customRow.Fields[j].Data = Convert.ToInt32(item[1], CultureInfo.InvariantCulture); } catch (FormatException) { this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); } catch (OverflowException) { this.OnMessage(WixErrors.IllegalIntegerValue(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name, item[1])); } } else if (ColumnCategory.Identifier == customRow.Fields[j].Column.Category) { if (CompilerCore.IsIdentifier(item[1]) || Common.IsValidBinderVariable(item[1]) || ColumnCategory.Formatted == customRow.Fields[j].Column.Category) { customRow.Fields[j].Data = item[1]; } else { this.OnMessage(WixErrors.IllegalIdentifier(row.SourceLineNumbers, "Data", item[1])); } } else { customRow.Fields[j].Data = item[1]; } } foundColumn = true; break; } } if (!foundColumn) { this.OnMessage(WixErrors.UnexpectedCustomTableColumn(row.SourceLineNumbers, item[0])); } } for (int i = 0; i < customTableDefinition.Columns.Count; ++i) { if (!customTableDefinition.Columns[i].IsNullable && (null == customRow.Fields[i].Data || 0 == customRow.Fields[i].Data.ToString().Length)) { this.OnMessage(WixErrors.NoDataForColumn(row.SourceLineNumbers, customTableDefinition.Columns[i].Name, customTableDefinition.Name)); } } } //correct the section Id in FeatureComponents table if (this.sectionIdOnRows) { Hashtable componentSectionIds = new Hashtable(); Table componentTable = output.Tables["Component"]; if (null != componentTable) { foreach (Row componentRow in componentTable.Rows) { componentSectionIds.Add(componentRow.Fields[0].Data.ToString(), componentRow.SectionId); } } Table featureComponentsTable = output.Tables["FeatureComponents"]; if (null != featureComponentsTable) { foreach (Row featureComponentsRow in featureComponentsTable.Rows) { if (componentSectionIds.Contains(featureComponentsRow.Fields[1].Data.ToString())) { featureComponentsRow.SectionId = (string)componentSectionIds[featureComponentsRow.Fields[1].Data.ToString()]; } } } } // update the special properties if (0 < adminProperties.Count) { Table propertyTable = this.activeOutput.EnsureTable(this.tableDefinitions["Property"]); Row row = propertyTable.CreateRow(null); row[0] = "AdminProperties"; row[1] = GetPropertyListString(adminProperties); } if (0 < secureProperties.Count) { Table propertyTable = this.activeOutput.EnsureTable(this.tableDefinitions["Property"]); Row row = propertyTable.CreateRow(null); row[0] = "SecureCustomProperties"; row[1] = GetPropertyListString(secureProperties); } if (0 < hiddenProperties.Count) { Table propertyTable = this.activeOutput.EnsureTable(this.tableDefinitions["Property"]); Row row = propertyTable.CreateRow(null); row[0] = "MsiHiddenProperties"; row[1] = GetPropertyListString(hiddenProperties); } // add the ModuleSubstitution table to the ModuleIgnoreTable if (containsModuleSubstitution) { Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); moduleIgnoreTableRow[0] = "ModuleSubstitution"; } // add the ModuleConfiguration table to the ModuleIgnoreTable if (containsModuleConfiguration) { Table moduleIgnoreTableTable = this.activeOutput.EnsureTable(this.tableDefinitions["ModuleIgnoreTable"]); Row moduleIgnoreTableRow = moduleIgnoreTableTable.CreateRow(null); moduleIgnoreTableRow[0] = "ModuleConfiguration"; } // index all the file rows FileRowCollection indexedFileRows = new FileRowCollection(); Table fileTable = this.activeOutput.Tables["File"]; if (null != fileTable) { indexedFileRows.AddRange(fileTable.Rows); } // flag all the generated short file name collisions foreach (string fileId in generatedShortFileNameIdentifiers) { FileRow fileRow = indexedFileRows[fileId]; string[] names = fileRow.FileName.Split('|'); string shortFileName = names[0]; // create lists of conflicting generated short file names if (!generatedShortFileNames.Contains(shortFileName)) { generatedShortFileNames.Add(shortFileName, new ArrayList()); } ((ArrayList)generatedShortFileNames[shortFileName]).Add(fileRow); } // check for generated short file name collisions foreach (DictionaryEntry entry in generatedShortFileNames) { string shortFileName = (string)entry.Key; ArrayList fileRows = (ArrayList)entry.Value; if (1 < fileRows.Count) { // sort the rows by DiskId fileRows.Sort(); this.OnMessage(WixWarnings.GeneratedShortFileNameConflict(((FileRow)fileRows[0]).SourceLineNumbers, shortFileName)); for (int i = 1; i < fileRows.Count; i++) { FileRow fileRow = (FileRow)fileRows[i]; if (null != fileRow.SourceLineNumbers) { this.OnMessage(WixWarnings.GeneratedShortFileNameConflict2(fileRow.SourceLineNumbers)); } } } } // copy the wix variable rows to the output after all overriding has been accounted for. if (0 < wixVariables.Count) { Table wixVariableTable = output.EnsureTable(this.tableDefinitions["WixVariable"]); foreach (WixVariableRow row in wixVariables.Values) { wixVariableTable.Rows.Add(row); } } // Bundles have groups of data that must be flattened in a way different from other types. this.FlattenBundleTables(output); if (this.encounteredError) { return null; } this.CheckOutputConsistency(output); // inspect the output InspectorCore inspectorCore = new InspectorCore(this.Message); foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) { inspectorExtension.Core = inspectorCore; inspectorExtension.InspectOutput(output); // reset inspectorExtension.Core = null; } if (inspectorCore.EncounteredError) { this.encounteredError = true; } } finally { this.activeOutput = null; } return (this.encounteredError ? null : output); }
private void ProcessPatchXml(string sourcePath, Output bundle) { string patchXml = Microsoft.Deployment.WindowsInstaller.Installer.ExtractPatchXmlData(sourcePath); XmlDocument doc = new XmlDocument(); doc.LoadXml(patchXml); XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); // Determine target ProductCodes and/or UpgradeCodes. foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) { // If this patch targets a product code, this is the best case. XmlNode targetCode = node.SelectSingleNode("p:TargetProductCode", nsmgr); WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; if (ChainPackageInfo.TargetsCode(targetCode)) { attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; } else // maybe targets an upgrade code? { targetCode = node.SelectSingleNode("p:UpgradeCode", nsmgr); if (ChainPackageInfo.TargetsCode(targetCode)) { attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; } else // this patch targets an unknown number of products { this.TargetUnspecified = true; } } Table table = bundle.EnsureTable(this.core.TableDefinitions["WixBundlePatchTargetCode"]); WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)table.CreateRow(this.PackagePayload.SourceLineNumbers, false); row.MspPackageId = this.PackagePayload.Id; row.TargetCode = targetCode.InnerText; row.Attributes = attributes; if (this.TargetCodes.TryAdd(row)) { table.Rows.Add(row); } } // Suppress patch sequence data for improved performance. if (this.core.GetProperty<bool>(Binder.PARAM_SPSD_NAME)) { XmlNode root = doc.DocumentElement; foreach (XmlNode node in root.SelectNodes("p:SequenceData", nsmgr)) { root.RemoveChild(node); } } // Save the XML as compact as possible. using (StringWriter writer = new StringWriter()) { XmlWriterSettings settings = new XmlWriterSettings() { Encoding = ChainPackageInfo.XmlOutputEncoding, Indent = false, NewLineChars = string.Empty, NewLineHandling = NewLineHandling.Replace, }; using (XmlWriter xmlWriter = XmlWriter.Create(writer, settings)) { doc.WriteTo(xmlWriter); } this.PatchXml = writer.ToString(); } }
/// <summary> /// Populates the WixBuildInfo table in an output. /// </summary> /// <param name="output">The output.</param> /// <param name="databaseFile">The output file if OutputFile not set.</param> private void WriteBuildInfoTable(Output output, string outputFile) { Table buildInfoTable = output.EnsureTable(this.core.TableDefinitions["WixBuildInfo"]); Row buildInfoRow = buildInfoTable.CreateRow(null); Assembly executingAssembly = Assembly.GetExecutingAssembly(); FileVersionInfo fileVersion = FileVersionInfo.GetVersionInfo(executingAssembly.Location); buildInfoRow[0] = fileVersion.FileVersion; buildInfoRow[1] = this.OutputFile ?? outputFile; if (!String.IsNullOrEmpty(this.wixprojectFile)) { buildInfoRow[2] = this.wixprojectFile; } if (!String.IsNullOrEmpty(this.pdbFile)) { buildInfoRow[3] = this.pdbFile; } }
/// <summary> /// Performs various consistency checks on the output. /// </summary> /// <param name="output">Output containing instance transform definitions.</param> private void CheckOutputConsistency(Output output) { // Get the output's minimum installer version int outputInstallerVersion = int.MinValue; Table summaryInformationTable = output.Tables["_SummaryInformation"]; if (null != summaryInformationTable) { foreach (Row row in summaryInformationTable.Rows) { if (14 == (int)row[0]) { outputInstallerVersion = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); break; } } } // ensure the Error table exists if output is marked for MSI 1.0 or below (see ICE40) if (100 >= outputInstallerVersion && OutputType.Product == output.Type) { output.EnsureTable(this.tableDefinitions["Error"]); } // check for the presence of tables/rows/columns that require MSI 1.1 or later if (110 > outputInstallerVersion) { Table isolatedComponentTable = output.Tables["IsolatedComponent"]; if (null != isolatedComponentTable) { foreach (Row row in isolatedComponentTable.Rows) { this.OnMessage(WixWarnings.TableIncompatibleWithInstallerVersion(row.SourceLineNumbers, "IsolatedComponent", outputInstallerVersion)); } } } // check for the presence of tables/rows/columns that require MSI 4.0 or later if (400 > outputInstallerVersion) { Table shortcutTable = output.Tables["Shortcut"]; if (null != shortcutTable) { foreach (Row row in shortcutTable.Rows) { if (null != row[12] || null != row[13] || null != row[14] || null != row[15]) { this.OnMessage(WixWarnings.ColumnsIncompatibleWithInstallerVersion(row.SourceLineNumbers, "Shortcut", outputInstallerVersion)); } } } } }
/// <summary> /// Binds a bundle. /// </summary> /// <param name="bundle">The bundle to bind.</param> /// <param name="bundleFile">The bundle to create.</param> /// <returns>true if binding completed successfully; false otherwise</returns> private bool BindBundle(Output bundle, string bundleFile) { // First look for data we expect to find... Chain, WixGroups, etc. Table chainPackageTable = bundle.Tables["ChainPackage"]; if (null == chainPackageTable || 0 == chainPackageTable.Rows.Count) { // We shouldn't really get past the linker phase if there are // no group items... that means that there's no UX, no Chain, // *and* no Containers! throw new WixException(WixErrors.MissingBundleInformation("ChainPackage")); } Table wixGroupTable = bundle.Tables["WixGroup"]; if (null == wixGroupTable || 0 == wixGroupTable.Rows.Count) { // We shouldn't really get past the linker phase if there are // no group items... that means that there's no UX, no Chain, // *and* no Containers! throw new WixException(WixErrors.MissingBundleInformation("WixGroup")); } // Ensure there is one and only one row in the WixBundle table. // The compiler and linker behavior should have colluded to get // this behavior. Table bundleTable = bundle.Tables["WixBundle"]; if (null == bundleTable || 1 != bundleTable.Rows.Count) { throw new WixException(WixErrors.MissingBundleInformation("WixBundle")); } // Ensure there is one and only one row in the WixBootstrapperApplication table. // The compiler and linker behavior should have colluded to get // this behavior. Table baTable = bundle.Tables["WixBootstrapperApplication"]; if (null == baTable || 1 != baTable.Rows.Count) { throw new WixException(WixErrors.MissingBundleInformation("WixBootstrapperApplication")); } // Ensure there is one and only one row in the WixChain table. // The compiler and linker behavior should have colluded to get // this behavior. Table chainTable = bundle.Tables["WixChain"]; if (null == chainTable || 1 != chainTable.Rows.Count) { throw new WixException(WixErrors.MissingBundleInformation("WixChain")); } foreach (BinderExtension extension in this.extensions) { extension.BundleInitialize(bundle); } if (this.core.EncounteredError) { return false; } this.WriteBuildInfoTable(bundle, bundleFile); // gather all the wix variables Table wixVariableTable = bundle.Tables["WixVariable"]; if (null != wixVariableTable) { foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) { this.WixVariableResolver.AddVariable(wixVariableRow); } } Hashtable cabinets = new Hashtable(); ArrayList delayedFields = new ArrayList(); // localize fields, resolve wix variables, and resolve file paths this.ResolveFields(bundle.Tables, cabinets, delayedFields); // if there are any fields to resolve later, create the cache to populate during bind IDictionary<string, string> variableCache = null; if (0 < delayedFields.Count) { variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); } if (this.core.EncounteredError) { return false; } Table relatedBundleTable = bundle.Tables["RelatedBundle"]; List<RelatedBundleInfo> allRelatedBundles = new List<RelatedBundleInfo>(); if (null != relatedBundleTable && 0 < relatedBundleTable.Rows.Count) { Dictionary<string, bool> deduplicatedRelatedBundles = new Dictionary<string, bool>(); foreach (Row row in relatedBundleTable.Rows) { string id = (string)row[0]; if (!deduplicatedRelatedBundles.ContainsKey(id)) { deduplicatedRelatedBundles[id] = true; allRelatedBundles.Add(new RelatedBundleInfo(row)); } } } // Ensure that the bundle has our well-known persisted values. Table variableTable = bundle.EnsureTable(this.core.TableDefinitions["Variable"]); VariableRow bundleNameWellKnownVariable = (VariableRow)variableTable.CreateRow(null); bundleNameWellKnownVariable.Id = Binder.BURN_BUNDLE_NAME; bundleNameWellKnownVariable.Hidden = false; bundleNameWellKnownVariable.Persisted = true; VariableRow bundleOriginalSourceWellKnownVariable = (VariableRow)variableTable.CreateRow(null); bundleOriginalSourceWellKnownVariable.Id = Binder.BURN_BUNDLE_ORIGINAL_SOURCE; bundleOriginalSourceWellKnownVariable.Hidden = false; bundleOriginalSourceWellKnownVariable.Persisted = true; VariableRow bundleOriginalSourceFolderWellKnownVariable = (VariableRow)variableTable.CreateRow(null); bundleOriginalSourceFolderWellKnownVariable.Id = Binder.BURN_BUNDLE_ORIGINAL_SOURCE_FOLDER; bundleOriginalSourceFolderWellKnownVariable.Hidden = false; bundleOriginalSourceFolderWellKnownVariable.Persisted = true; VariableRow bundleLastUsedSourceWellKnownVariable = (VariableRow)variableTable.CreateRow(null); bundleLastUsedSourceWellKnownVariable.Id = Binder.BURN_BUNDLE_LAST_USED_SOURCE; bundleLastUsedSourceWellKnownVariable.Hidden = false; bundleLastUsedSourceWellKnownVariable.Persisted = true; // To make lookups easier, we load the variable table bottom-up, so // that we can index by ID. List<VariableInfo> allVariables = new List<VariableInfo>(variableTable.Rows.Count); foreach (VariableRow variableRow in variableTable.Rows) { allVariables.Add(new VariableInfo(variableRow)); } // TODO: Although the WixSearch tables are defined in the Util extension, // the Bundle Binder has to know all about them. We hope to revisit all // of this in the 4.0 timeframe. Dictionary<string, WixSearchInfo> allSearches = new Dictionary<string, WixSearchInfo>(); Table wixFileSearchTable = bundle.Tables["WixFileSearch"]; if (null != wixFileSearchTable && 0 < wixFileSearchTable.Rows.Count) { foreach (Row row in wixFileSearchTable.Rows) { WixFileSearchInfo fileSearchInfo = new WixFileSearchInfo(row); allSearches.Add(fileSearchInfo.Id, fileSearchInfo); } } Table wixRegistrySearchTable = bundle.Tables["WixRegistrySearch"]; if (null != wixRegistrySearchTable && 0 < wixRegistrySearchTable.Rows.Count) { foreach (Row row in wixRegistrySearchTable.Rows) { WixRegistrySearchInfo registrySearchInfo = new WixRegistrySearchInfo(row); allSearches.Add(registrySearchInfo.Id, registrySearchInfo); } } Table wixComponentSearchTable = bundle.Tables["WixComponentSearch"]; if (null != wixComponentSearchTable && 0 < wixComponentSearchTable.Rows.Count) { foreach (Row row in wixComponentSearchTable.Rows) { WixComponentSearchInfo componentSearchInfo = new WixComponentSearchInfo(row); allSearches.Add(componentSearchInfo.Id, componentSearchInfo); } } Table wixProductSearchTable = bundle.Tables["WixProductSearch"]; if (null != wixProductSearchTable && 0 < wixProductSearchTable.Rows.Count) { foreach (Row row in wixProductSearchTable.Rows) { WixProductSearchInfo productSearchInfo = new WixProductSearchInfo(row); allSearches.Add(productSearchInfo.Id, productSearchInfo); } } // Merge in the variable/condition info and get the canonical ordering for // the searches. List<WixSearchInfo> orderedSearches = new List<WixSearchInfo>(); Table wixSearchTable = bundle.Tables["WixSearch"]; if (null != wixSearchTable && 0 < wixSearchTable.Rows.Count) { orderedSearches.Capacity = wixSearchTable.Rows.Count; foreach (Row row in wixSearchTable.Rows) { WixSearchInfo searchInfo = allSearches[(string)row[0]]; searchInfo.AddWixSearchRowInfo(row); orderedSearches.Add(searchInfo); } } // extract files that come from cabinet files (this does not extract files from merge modules) this.ExtractCabinets(cabinets); WixBundleRow bundleInfo = (WixBundleRow)bundleTable.Rows[0]; bundleInfo.PerMachine = true; // default to per-machine but the first-per user package would flip it. // Get update if specified. Table bundleUpdateTable = bundle.Tables["WixBundleUpdate"]; WixBundleUpdateRow bundleUpdateRow = null; if (null != bundleUpdateTable) { bundleUpdateRow = (WixBundleUpdateRow)bundleUpdateTable.Rows[0]; } // Get update registration if specified. Table updateRegistrationTable = bundle.Tables["WixUpdateRegistration"]; WixUpdateRegistrationRow updateRegistrationInfo = null; if (null != updateRegistrationTable) { updateRegistrationInfo = (WixUpdateRegistrationRow)updateRegistrationTable.Rows[0]; } // Get the explicit payloads. Table payloadTable = bundle.Tables["Payload"]; Dictionary<string, PayloadInfoRow> allPayloads = new Dictionary<string, PayloadInfoRow>(payloadTable.Rows.Count); Table payloadInfoTable = bundle.EnsureTable(core.TableDefinitions["PayloadInfo"]); foreach (PayloadInfoRow row in payloadInfoTable.Rows) { allPayloads.Add(row.Id, row); } RowDictionary<Row> payloadDisplayInformationRows = new RowDictionary<Row>(bundle.Tables["PayloadDisplayInformation"]); foreach (Row row in payloadTable.Rows) { string id = (string)row[0]; PayloadInfoRow payloadInfo = null; if (allPayloads.ContainsKey(id)) { payloadInfo = allPayloads[id]; } else { allPayloads.Add(id, payloadInfo = (PayloadInfoRow)payloadInfoTable.CreateRow(row.SourceLineNumbers)); } payloadInfo.FillFromPayloadRow(bundle, row); // Check if there is an override row for the display name or description. Row payloadDisplayInformationRow; if (payloadDisplayInformationRows.TryGet(id, out payloadDisplayInformationRow)) { if (!String.IsNullOrEmpty(payloadDisplayInformationRow[1] as string)) { payloadInfo.ProductName = (string)payloadDisplayInformationRow[1]; } if (!String.IsNullOrEmpty(payloadDisplayInformationRow[2] as string)) { payloadInfo.Description = (string)payloadDisplayInformationRow[2]; } } if (payloadInfo.Packaging == PackagingType.Unknown) { payloadInfo.Packaging = bundleInfo.DefaultPackagingType; } } Dictionary<string, ContainerInfo> containers = new Dictionary<string, ContainerInfo>(); Dictionary<string, bool> payloadsAddedToContainers = new Dictionary<string, bool>(); // Create the list of containers. Table containerTable = bundle.Tables["Container"]; if (null != containerTable) { foreach (Row row in containerTable.Rows) { ContainerInfo container = new ContainerInfo(row, this.FileManager); containers.Add(container.Id, container); } } // Create the default attached container for payloads that need to be attached but don't have an explicit container. ContainerInfo defaultAttachedContainer = new ContainerInfo("WixAttachedContainer", "bundle-attached.cab", "attached", null, this.FileManager); containers.Add(defaultAttachedContainer.Id, defaultAttachedContainer); Row baRow = baTable.Rows[0]; string baPayloadId = (string)baRow[0]; // Create lists of which payloads go in each container or are layout only. foreach (Row row in wixGroupTable.Rows) { string rowParentName = (string)row[0]; string rowParentType = (string)row[1]; string rowChildName = (string)row[2]; string rowChildType = (string)row[3]; if (Enum.GetName(typeof(ComplexReferenceChildType), ComplexReferenceChildType.Payload) == rowChildType) { PayloadInfoRow payload = allPayloads[rowChildName]; if (Enum.GetName(typeof(ComplexReferenceParentType), ComplexReferenceParentType.Container) == rowParentType) { ContainerInfo container = containers[rowParentName]; // Make sure the BA DLL is the first payload. if (payload.Id.Equals(baPayloadId)) { container.Payloads.Insert(0, payload); } else { container.Payloads.Add(payload); } payload.Container = container.Id; payloadsAddedToContainers.Add(rowChildName, false); } else if (Enum.GetName(typeof(ComplexReferenceParentType), ComplexReferenceParentType.Layout) == rowParentType) { payload.LayoutOnly = true; } } } ContainerInfo burnUXContainer; containers.TryGetValue(Compiler.BurnUXContainerId, out burnUXContainer); List<PayloadInfoRow> uxPayloads = null == burnUXContainer ? null : burnUXContainer.Payloads; // If we didn't get any UX payloads, it's an error! if (null == uxPayloads || 0 == uxPayloads.Count) { throw new WixException(WixErrors.MissingBundleInformation("BootstrapperApplication")); } // Get the catalog information Dictionary<string, CatalogInfo> catalogs = new Dictionary<string, CatalogInfo>(); Table catalogTable = bundle.Tables["WixCatalog"]; if (null != catalogTable) { foreach (WixCatalogRow catalogRow in catalogTable.Rows) { // Each catalog is also a payload string payloadId = Common.GenerateIdentifier("pay", true, catalogRow.SourceFile); string catalogFile = this.FileManager.ResolveFile(catalogRow.SourceFile, "Catalog", catalogRow.SourceLineNumbers, BindStage.Normal); PayloadInfoRow payloadInfo = PayloadInfoRow.Create(catalogRow.SourceLineNumbers, bundle, payloadId, Path.GetFileName(catalogFile), catalogFile, true, false, null, burnUXContainer.Id, PackagingType.Embedded); // Add the payload to the UX container allPayloads.Add(payloadInfo.Id, payloadInfo); burnUXContainer.Payloads.Add(payloadInfo); payloadsAddedToContainers.Add(payloadInfo.Id, true); // Create the catalog info CatalogInfo catalog = new CatalogInfo(catalogRow, payloadId); catalogs.Add(catalog.Id, catalog); } } // Get the chain packages, this may add more payloads. Dictionary<string, ChainPackageInfo> allPackages = new Dictionary<string, ChainPackageInfo>(); Dictionary<string, RollbackBoundaryInfo> allBoundaries = new Dictionary<string, RollbackBoundaryInfo>(); foreach (Row row in chainPackageTable.Rows) { Compiler.ChainPackageType type = (Compiler.ChainPackageType)Enum.Parse(typeof(Compiler.ChainPackageType), row[1].ToString(), true); if (Compiler.ChainPackageType.RollbackBoundary == type) { RollbackBoundaryInfo rollbackBoundary = new RollbackBoundaryInfo(row); allBoundaries.Add(rollbackBoundary.Id, rollbackBoundary); } else // package { Table chainPackageInfoTable = bundle.EnsureTable(this.core.TableDefinitions["ChainPackageInfo"]); ChainPackageInfo packageInfo = new ChainPackageInfo(row, wixGroupTable, allPayloads, containers, this.FileManager, this.core, bundle); allPackages.Add(packageInfo.Id, packageInfo); chainPackageInfoTable.Rows.Add(packageInfo); // Add package properties to resolve fields later. if (null != variableCache) { Binder.PopulatePackageVariableCache(packageInfo, variableCache); } } } // Determine patches to automatically slipstream. this.AutomaticallySlipstreamPatches(bundle, allPackages.Values); // NOTE: All payloads should be generated before here with the exception of specific engine and ux data files. ArrayList fileTransfers = new ArrayList(); string layoutDirectory = Path.GetDirectoryName(bundleFile); // Handle any payloads not explicitly in a container. foreach (string payloadName in allPayloads.Keys) { if (!payloadsAddedToContainers.ContainsKey(payloadName)) { PayloadInfoRow payload = allPayloads[payloadName]; if (PackagingType.Embedded == payload.Packaging) { payload.Container = defaultAttachedContainer.Id; defaultAttachedContainer.Payloads.Add(payload); } else if (!String.IsNullOrEmpty(payload.FullFileName)) { FileTransfer transfer; if (FileTransfer.TryCreate(payload.FullFileName, Path.Combine(layoutDirectory, payload.Name), false, "Payload", payload.SourceLineNumbers, out transfer)) { fileTransfers.Add(transfer); } } } } // Give the UX payloads their embedded IDs... for (int uxPayloadIndex = 0; uxPayloadIndex < uxPayloads.Count; ++uxPayloadIndex) { PayloadInfoRow payload = uxPayloads[uxPayloadIndex]; // In theory, UX payloads could be embedded in the UX CAB, external to the // bundle EXE, or even downloaded. The current engine requires the UX to be // fully present before any downloading starts, so that rules out downloading. // Also, the burn engine does not currently copy external UX payloads into // the temporary UX directory correctly, so we don't allow external either. if (PackagingType.Embedded != payload.Packaging) { core.OnMessage(WixWarnings.UxPayloadsOnlySupportEmbedding(payload.SourceLineNumbers, payload.FullFileName)); payload.Packaging = PackagingType.Embedded; } payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloadIndex); } if (this.core.EncounteredError) { return false; } // If catalog files exist, non-UX payloads should validate with the catalog if (catalogs.Count > 0) { foreach (PayloadInfoRow payloadInfo in allPayloads.Values) { if (String.IsNullOrEmpty(payloadInfo.EmbeddedId)) { VerifyPayloadWithCatalog(payloadInfo, catalogs); } } } if (this.core.EncounteredError) { return false; } // Process the chain of packages to add them in the correct order // and assign the forward rollback boundaries as appropriate. Remember // rollback boundaries are authored as elements in the chain which // we re-interpret here to add them as attributes on the next available // package in the chain. Essentially we mark some packages as being // the start of a rollback boundary when installing and repairing. // We handle uninstall (aka: backwards) rollback boundaries after // we get these install/repair (aka: forward) rollback boundaries // defined. ChainInfo chain = new ChainInfo(chainTable.Rows[0]); // WixChain table always has one and only row in it. RollbackBoundaryInfo previousRollbackBoundary = new RollbackBoundaryInfo("WixDefaultBoundary"); // ensure there is always a rollback boundary at the beginning of the chain. foreach (Row row in wixGroupTable.Rows) { string rowParentName = (string)row[0]; string rowParentType = (string)row[1]; string rowChildName = (string)row[2]; string rowChildType = (string)row[3]; if ("PackageGroup" == rowParentType && "WixChain" == rowParentName && "Package" == rowChildType) { ChainPackageInfo packageInfo = null; if (allPackages.TryGetValue(rowChildName, out packageInfo)) { if (null != previousRollbackBoundary) { chain.RollbackBoundaries.Add(previousRollbackBoundary); packageInfo.RollbackBoundary = previousRollbackBoundary; previousRollbackBoundary = null; } chain.Packages.Add(packageInfo); } else // must be a rollback boundary. { // Discard the next rollback boundary if we have a previously defined boundary. Of course, // a boundary specifically defined will override the default boundary. RollbackBoundaryInfo nextRollbackBoundary = allBoundaries[rowChildName]; if (null != previousRollbackBoundary && !previousRollbackBoundary.Default) { this.core.OnMessage(WixWarnings.DiscardedRollbackBoundary(nextRollbackBoundary.SourceLineNumbers, nextRollbackBoundary.Id)); } else { previousRollbackBoundary = nextRollbackBoundary; } } } } if (null != previousRollbackBoundary) { this.core.OnMessage(WixWarnings.DiscardedRollbackBoundary(previousRollbackBoundary.SourceLineNumbers, previousRollbackBoundary.Id)); } // With the forward rollback boundaries assigned, we can now go // through the packages with rollback boundaries and assign backward // rollback boundaries. Backward rollback boundaries are used when // the chain is going "backwards" which (AFAIK) only happens during // uninstall. // // Consider the scenario with three packages: A, B and C. Packages A // and C are marked as rollback boundary packages and package B is // not. The naive implementation would execute the chain like this // (numbers indicate where rollback boundaries would end up): // install: 1 A B 2 C // uninstall: 2 C B 1 A // // The uninstall chain is wrong, A and B should be grouped together // not C and B. The fix is to label packages with a "backwards" // rollback boundary used during uninstall. The backwards rollback // boundaries are assigned to the package *before* the next rollback // boundary. Using our example from above again, I'll mark the // backwards rollback boundaries prime (aka: with '). // install: 1 A B 1' 2 C 2' // uninstall: 2' C 2 1' B A 1 // // If the marked boundaries are ignored during install you get the // same thing as above (good) and if the non-marked boundaries are // ignored during uninstall then A and B are correctly grouped. // Here's what it looks like without all the markers: // install: 1 A B 2 C // uninstall: 2 C 1 B A // Woot! string previousRollbackBoundaryId = null; ChainPackageInfo previousPackage = null; foreach (ChainPackageInfo package in chain.Packages) { if (null != package.RollbackBoundary) { if (null != previousPackage) { previousPackage.RollbackBoundaryBackwardId = previousRollbackBoundaryId; } previousRollbackBoundaryId = package.RollbackBoundary.Id; } previousPackage = package; } if (!String.IsNullOrEmpty(previousRollbackBoundaryId) && null != previousPackage) { previousPackage.RollbackBoundaryBackwardId = previousRollbackBoundaryId; } // Give all embedded payloads that don't have an embedded ID yet an embedded ID. int payloadIndex = 0; foreach (PayloadInfoRow payload in allPayloads.Values) { Debug.Assert(PackagingType.Unknown != payload.Packaging); if (PackagingType.Embedded == payload.Packaging && String.IsNullOrEmpty(payload.EmbeddedId)) { payload.EmbeddedId = String.Format(CultureInfo.InvariantCulture, BurnCommon.BurnAttachedContainerEmbeddedIdFormat, payloadIndex); ++payloadIndex; } } // Load the MsiProperty information... Table msiPropertyTable = bundle.Tables["MsiProperty"]; if (null != msiPropertyTable && 0 < msiPropertyTable.Rows.Count) { foreach (Row row in msiPropertyTable.Rows) { MsiPropertyInfo msiProperty = new MsiPropertyInfo(row); ChainPackageInfo package; if (allPackages.TryGetValue(msiProperty.PackageId, out package)) { package.MsiProperties.Add(msiProperty); } else { core.OnMessage(WixErrors.IdentifierNotFound("Package", msiProperty.PackageId)); } } } // Load the SlipstreamMsp information... Table slipstreamMspTable = bundle.Tables["SlipstreamMsp"]; if (null != slipstreamMspTable && 0 < slipstreamMspTable.Rows.Count) { foreach (Row row in slipstreamMspTable.Rows) { string msiPackageId = (string)row[0]; string mspPackageId = (string)row[1]; if (!allPackages.ContainsKey(mspPackageId)) { core.OnMessage(WixErrors.IdentifierNotFound("Package", mspPackageId)); continue; } ChainPackageInfo package; if (!allPackages.TryGetValue(msiPackageId, out package)) { core.OnMessage(WixErrors.IdentifierNotFound("Package", msiPackageId)); continue; } package.SlipstreamMsps.Add(mspPackageId); } } // Load the ExitCode information... Table exitCodeTable = bundle.Tables["ExitCode"]; if (null != exitCodeTable && 0 < exitCodeTable.Rows.Count) { foreach (Row row in exitCodeTable.Rows) { ExitCodeInfo exitCode = new ExitCodeInfo(row); ChainPackageInfo package; if (allPackages.TryGetValue(exitCode.PackageId, out package)) { package.ExitCodes.Add(exitCode); } else { core.OnMessage(WixErrors.IdentifierNotFound("Package", exitCode.PackageId)); } } } // Load the CommandLine information... Dictionary<string, List<WixCommandLineRow>> commandLinesByPackage = new Dictionary<string, List<WixCommandLineRow>>(); Table commandLineTable = bundle.Tables["WixCommandLine"]; if (null != commandLineTable && 0 < commandLineTable.Rows.Count) { foreach (WixCommandLineRow row in commandLineTable.Rows) { if (!commandLinesByPackage.ContainsKey(row.PackageId)) { commandLinesByPackage.Add(row.PackageId, new List<WixCommandLineRow>()); } List<WixCommandLineRow> commandLines = commandLinesByPackage[row.PackageId]; commandLines.Add(row); } } // Resolve any delayed fields before generating the manifest. if (0 < delayedFields.Count) { this.ResolveDelayedFields(bundle, delayedFields, variableCache, null); } // Process WixApprovedExeForElevation rows. Table wixApprovedExeForElevationTable = bundle.Tables["WixApprovedExeForElevation"]; List<ApprovedExeForElevation> approvedExesForElevation = new List<ApprovedExeForElevation>(); if (null != wixApprovedExeForElevationTable && 0 < wixApprovedExeForElevationTable.Rows.Count) { foreach (WixApprovedExeForElevationRow wixApprovedExeForElevationRow in wixApprovedExeForElevationTable.Rows) { ApprovedExeForElevation approvedExeForElevation = new ApprovedExeForElevation(wixApprovedExeForElevationRow); approvedExesForElevation.Add(approvedExeForElevation); } } // Set the overridable bundle provider key. this.SetBundleProviderKey(bundle, bundleInfo); // Import or generate dependency providers for packages in the manifest. this.ProcessDependencyProviders(bundle, allPackages); // Generate the core-defined BA manifest tables... this.GenerateBAManifestPackageTables(bundle, chain.Packages); this.GenerateBAManifestPayloadTables(bundle, chain.Packages, allPayloads); foreach (BinderExtension extension in this.extensions) { extension.BundleFinalize(bundle); } // Start creating the bundle. this.PopulateBundleInfoFromChain(bundleInfo, chain.Packages); this.PopulateChainInfoTables(bundle, bundleInfo, chain.Packages); this.GenerateBAManifestBundleTables(bundle, bundleInfo); // Copy the burn.exe to a writable location then mark it to be moved to its // final build location. string stubPlatform; if (Platform.X64 == bundleInfo.Platform) // today, the x64 Burn uses the x86 stub. { stubPlatform = "x86"; } else { stubPlatform = bundleInfo.Platform.ToString(); } string wixExeDirectory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), stubPlatform); string stubFile = Path.Combine(wixExeDirectory, "burn.exe"); string bundleTempPath = Path.Combine(this.TempFilesLocation, Path.GetFileName(bundleFile)); this.core.OnMessage(WixVerboses.GeneratingBundle(bundleTempPath, stubFile)); File.Copy(stubFile, bundleTempPath, true); File.SetAttributes(bundleTempPath, FileAttributes.Normal); FileTransfer bundleTransfer; if (FileTransfer.TryCreate(bundleTempPath, bundleFile, true, "Bundle", bundleInfo.SourceLineNumbers, out bundleTransfer)) { bundleTransfer.Built = true; fileTransfers.Add(bundleTransfer); } // Create our manifests, CABs and final EXE... string baManifestPath = Path.Combine(this.TempFilesLocation, "bundle-BootstrapperApplicationData.xml"); this.CreateBootstrapperApplicationManifest(bundle, baManifestPath, uxPayloads); // Add the bootstrapper application manifest to the set of UX payloads. PayloadInfoRow baManifestPayload = PayloadInfoRow.Create(null /*TODO*/, bundle, Common.GenerateIdentifier("ux", true, "BootstrapperApplicationData.xml"), "BootstrapperApplicationData.xml", baManifestPath, false, true, null, burnUXContainer.Id, PackagingType.Embedded); baManifestPayload.EmbeddedId = string.Format(CultureInfo.InvariantCulture, BurnCommon.BurnUXContainerEmbeddedIdFormat, uxPayloads.Count); uxPayloads.Add(baManifestPayload); // Create all the containers except the UX container first so the manifest in the UX container can contain all size and hash information. foreach (ContainerInfo container in containers.Values) { if (Compiler.BurnUXContainerId != container.Id && 0 < container.Payloads.Count) { this.CreateContainer(container, null); } } string manifestPath = Path.Combine(this.TempFilesLocation, "bundle-manifest.xml"); this.CreateBurnManifest(bundleFile, bundleInfo, bundleUpdateRow, updateRegistrationInfo, manifestPath, allRelatedBundles, allVariables, orderedSearches, allPayloads, chain, containers, catalogs, bundle.Tables["WixBundleTag"], approvedExesForElevation, commandLinesByPackage); this.UpdateBurnResources(bundleTempPath, bundleFile, bundleInfo); // update the .wixburn section to point to at the UX and attached container(s) then attach the container(s) if they should be attached. using (BurnWriter writer = BurnWriter.Open(bundleTempPath, this.core)) { FileInfo burnStubFile = new FileInfo(bundleTempPath); writer.InitializeBundleSectionData(burnStubFile.Length, bundleInfo.BundleId); // Always create UX container and attach it first this.CreateContainer(burnUXContainer, manifestPath); writer.AppendContainer(burnUXContainer.TempPath, BurnWriter.Container.UX); // Now append all other attached containers foreach (ContainerInfo container in containers.Values) { if (container.Type == "attached") { // The container was only created if it had payloads. if (Compiler.BurnUXContainerId != container.Id && 0 < container.Payloads.Count) { writer.AppendContainer(container.TempPath, BurnWriter.Container.Attached); } } } } // Output the bundle to a file if (null != this.pdbFile) { Pdb pdb = new Pdb(null); pdb.Output = bundle; pdb.Save(this.pdbFile, null, this.WixVariableResolver, this.TempFilesLocation); } // Add detached containers to the list of file transfers. foreach (ContainerInfo container in containers.Values) { if ("detached" == container.Type) { FileTransfer transfer; if (FileTransfer.TryCreate(Path.Combine(this.TempFilesLocation, container.Name), Path.Combine(layoutDirectory, container.Name), true, "Container", container.SourceLineNumbers, out transfer)) { transfer.Built = true; fileTransfers.Add(transfer); } } } // layout media string bundleFilename = Path.GetFileName(bundleFile); if ("setup.exe".Equals(bundleFilename, StringComparison.OrdinalIgnoreCase)) { this.core.OnMessage(WixErrors.InsecureBundleFilename(bundleFilename)); } try { this.core.OnMessage(WixVerboses.LayingOutMedia()); this.LayoutMedia(fileTransfers, this.suppressAclReset); } finally { if (!String.IsNullOrEmpty(this.contentsFile)) { this.CreateContentsFile(this.contentsFile, allPayloads.Values); } if (!String.IsNullOrEmpty(this.outputsFile)) { this.CreateOutputsFile(this.outputsFile, fileTransfers, this.pdbFile); } if (!String.IsNullOrEmpty(this.builtOutputsFile)) { this.CreateBuiltOutputsFile(this.builtOutputsFile, fileTransfers, this.pdbFile); } } return !this.core.EncounteredError; }
private bool BindDatabase(Output output, string databaseFile) { foreach (BinderExtension extension in this.extensions) { extension.DatabaseInitialize(output); } bool compressed = false; FileRowCollection fileRows = new FileRowCollection(OutputType.Patch == output.Type); bool longNames = false; MediaRowCollection mediaRows = new MediaRowCollection(); Hashtable suppressModularizationIdentifiers = null; StringCollection suppressedTableNames = new StringCollection(); Table propertyTable = output.Tables["Property"]; this.WriteBuildInfoTable(output, databaseFile); // gather all the wix variables Table wixVariableTable = output.Tables["WixVariable"]; if (null != wixVariableTable) { foreach (WixVariableRow wixVariableRow in wixVariableTable.Rows) { this.WixVariableResolver.AddVariable(wixVariableRow); } } // gather all the suppress modularization identifiers Table wixSuppressModularizationTable = output.Tables["WixSuppressModularization"]; if (null != wixSuppressModularizationTable) { suppressModularizationIdentifiers = new Hashtable(wixSuppressModularizationTable.Rows.Count); foreach (Row row in wixSuppressModularizationTable.Rows) { suppressModularizationIdentifiers[row[0]] = null; } } // localize fields, resolve wix variables, and resolve file paths Hashtable cabinets = new Hashtable(); ArrayList delayedFields = new ArrayList(); this.ResolveFields(output.Tables, cabinets, delayedFields); // if there are any fields to resolve later, create the cache to populate during bind IDictionary<string, string> variableCache = null; if (0 < delayedFields.Count) { variableCache = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); } this.LocalizeUI(output.Tables); // process the summary information table before the other tables string modularizationGuid = this.BindDatabaseSummaryInfo(output, out longNames, out compressed); // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // modularize identifiers and add tables with real streams to the import tables if (OutputType.Module == output.Type) { foreach (Table table in output.Tables) { table.Modularize(modularizationGuid, suppressModularizationIdentifiers); } // Reset the special property lists after modularization. The linker creates these properties before modularization // so we have to reconstruct them for merge modules after modularization in the binder. Table wixPropertyTable = output.Tables["WixProperty"]; if (null != wixPropertyTable) { // Create lists of the properties that contribute to the special lists of properties. SortedList adminProperties = new SortedList(); SortedList secureProperties = new SortedList(); SortedList hiddenProperties = new SortedList(); foreach (WixPropertyRow wixPropertyRow in wixPropertyTable.Rows) { if (wixPropertyRow.Admin) { adminProperties[wixPropertyRow.Id] = null; } if (wixPropertyRow.Hidden) { hiddenProperties[wixPropertyRow.Id] = null; } if (wixPropertyRow.Secure) { secureProperties[wixPropertyRow.Id] = null; } } if (0 < adminProperties.Count || 0 < hiddenProperties.Count || 0 < secureProperties.Count) { Table table = output.Tables["Property"]; foreach (Row propertyRow in table.Rows) { if ("AdminProperties" == (string)propertyRow[0]) { propertyRow[1] = GetPropertyListString(adminProperties); } if ("MsiHiddenProperties" == (string)propertyRow[0]) { propertyRow[1] = GetPropertyListString(hiddenProperties); } if ("SecureCustomProperties" == (string)propertyRow[0]) { propertyRow[1] = GetPropertyListString(secureProperties); } } } } } // merge unreal table data into the real tables // this must occur after all variables and source paths have been resolved this.MergeUnrealTables(output.Tables); if (this.core.EncounteredError) { return false; } if (OutputType.Patch == output.Type) { foreach (SubStorage substorage in output.SubStorages) { Output transform = (Output)substorage.Data; this.ResolveFields(transform.Tables, cabinets, null); this.MergeUnrealTables(transform.Tables); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // index the File table for quicker access later // this must occur after the unreal data has been merged in Table fileTable = output.Tables["File"]; if (null != fileTable) { fileRows.AddRange(fileTable.Rows); } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // add binder variables for all properties propertyTable = output.Tables["Property"]; if (null != propertyTable) { foreach (Row propertyRow in propertyTable.Rows) { string property = propertyRow[0].ToString(); // set the ProductCode if its generated if (OutputType.Product == output.Type && "ProductCode" == property && "*" == propertyRow[1].ToString()) { propertyRow[1] = Common.GenerateGuid(); // Update the target ProductCode in any instance transforms foreach (SubStorage subStorage in output.SubStorages) { Output subStorageOutput = (Output)subStorage.Data; if (OutputType.Transform != subStorageOutput.Type) { continue; } Table instanceSummaryInformationTable = subStorageOutput.Tables["_SummaryInformation"]; foreach (Row row in instanceSummaryInformationTable.Rows) { if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) { row[1] = ((string)row[1]).Replace("*", (string)propertyRow[1]); break; } } } } // add the property name and value to the variableCache if (0 != delayedFields.Count) { string key = String.Concat("property.", Demodularize(output, modularizationGuid, property)); variableCache[key] = (string)propertyRow[1]; } } } // extract files that come from cabinet files (this does not extract files from merge modules) this.ExtractCabinets(cabinets); // retrieve files and their information from merge modules if (OutputType.Product == output.Type) { this.ProcessMergeModules(output, fileRows); } else if (OutputType.Patch == output.Type) { // merge transform data into the output object this.CopyTransformData(output, fileRows); } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // assign files to media AutoMediaAssigner autoMediaAssigner = new AutoMediaAssigner(output, this.core, compressed); autoMediaAssigner.AssignFiles(fileRows); // update file version, hash, assembly, etc.. information this.core.OnMessage(WixVerboses.UpdatingFileInformation()); Hashtable indexedFileRows = this.UpdateFileInformation(output, fileRows, autoMediaAssigner.MediaRows, variableCache, modularizationGuid); // set generated component guids this.SetComponentGuids(output); // With the Component Guids set now we can create instance transforms. this.CreateInstanceTransforms(output); this.ValidateComponentGuids(output); this.UpdateControlText(output); if (0 < delayedFields.Count) { this.ResolveDelayedFields(output, delayedFields, variableCache, modularizationGuid); } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // Extended binder extensions can be called now that fields are resolved. foreach (BinderExtension extension in this.extensions) { BinderExtensionEx extensionEx = extension as BinderExtensionEx; if (null != extensionEx) { output.EnsureTable(this.core.TableDefinitions["WixBindUpdatedFiles"]); extensionEx.DatabaseAfterResolvedFields(output); } } Table updatedFiles = output.Tables["WixBindUpdatedFiles"]; if (null != updatedFiles) { foreach (Row updatedFile in updatedFiles.Rows) { FileRow updatedFileRow = (FileRow)indexedFileRows[updatedFile[0]]; this.UpdateFileRow(output, null, modularizationGuid, indexedFileRows, updatedFileRow, true); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // create cabinet files and process uncompressed files string layoutDirectory = Path.GetDirectoryName(databaseFile); FileRowCollection uncompressedFileRows = null; if (!this.suppressLayout || OutputType.Module == output.Type) { this.core.OnMessage(WixVerboses.CreatingCabinetFiles()); uncompressedFileRows = this.CreateCabinetFiles(output, fileRows, this.fileTransfers, autoMediaAssigner.MediaRows, layoutDirectory, compressed, autoMediaAssigner); } if (OutputType.Patch == output.Type) { // copy output data back into the transforms this.CopyTransformData(output, null); } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // add back suppressed tables which must be present prior to merging in modules if (OutputType.Product == output.Type) { Table wixMergeTable = output.Tables["WixMerge"]; if (null != wixMergeTable && 0 < wixMergeTable.Rows.Count) { foreach (SequenceTable sequence in Enum.GetValues(typeof(SequenceTable))) { string sequenceTableName = sequence.ToString(); Table sequenceTable = output.Tables[sequenceTableName]; if (null == sequenceTable) { sequenceTable = output.EnsureTable(this.core.TableDefinitions[sequenceTableName]); } if (0 == sequenceTable.Rows.Count) { suppressedTableNames.Add(sequenceTableName); } } } } foreach (BinderExtension extension in this.extensions) { extension.DatabaseFinalize(output); } // generate database file this.core.OnMessage(WixVerboses.GeneratingDatabase()); string tempDatabaseFile = Path.Combine(this.TempFilesLocation, Path.GetFileName(databaseFile)); this.GenerateDatabase(output, tempDatabaseFile, false, false); FileTransfer transfer; if (FileTransfer.TryCreate(tempDatabaseFile, databaseFile, true, output.Type.ToString(), null, out transfer)) // note where this database needs to move in the future { transfer.Built = true; this.fileTransfers.Add(transfer); } // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } // Output the output to a file if (null != this.pdbFile) { Pdb pdb = new Pdb(null); pdb.Output = output; pdb.Save(this.pdbFile, null, this.WixVariableResolver, this.TempFilesLocation); } // merge modules if (OutputType.Product == output.Type) { this.core.OnMessage(WixVerboses.MergingModules()); this.MergeModules(tempDatabaseFile, output, fileRows, suppressedTableNames); // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } } // inspect the MSI prior to running ICEs InspectorCore inspectorCore = new InspectorCore(this.MessageHandler); foreach (InspectorExtension inspectorExtension in this.inspectorExtensions) { inspectorExtension.Core = inspectorCore; inspectorExtension.InspectDatabase(tempDatabaseFile, output); // reset inspectorExtension.Core = null; } if (inspectorCore.EncounteredError) { return false; } // validate the output if there is an MSI validator if (null != this.validator) { Stopwatch stopwatch = Stopwatch.StartNew(); // set the output file for source line information this.validator.Output = output; this.core.OnMessage(WixVerboses.ValidatingDatabase()); this.core.EncounteredError = !this.validator.Validate(tempDatabaseFile); stopwatch.Stop(); this.core.OnMessage(WixVerboses.ValidatedDatabase(stopwatch.ElapsedMilliseconds)); // stop processing if an error previously occurred if (this.core.EncounteredError) { return false; } } // process uncompressed files if (!this.suppressLayout) { this.ProcessUncompressedFiles(tempDatabaseFile, uncompressedFileRows, this.fileTransfers, autoMediaAssigner.MediaRows, layoutDirectory, compressed, longNames); } // layout media try { this.core.OnMessage(WixVerboses.LayingOutMedia()); this.LayoutMedia(this.fileTransfers, this.suppressAclReset); } finally { if (!String.IsNullOrEmpty(this.contentsFile)) { this.CreateContentsFile(this.contentsFile, fileRows); } if (!String.IsNullOrEmpty(this.outputsFile)) { this.CreateOutputsFile(this.outputsFile, this.fileTransfers, this.pdbFile); } if (!String.IsNullOrEmpty(this.builtOutputsFile)) { this.CreateBuiltOutputsFile(this.builtOutputsFile, this.fileTransfers, this.pdbFile); } } return !this.core.EncounteredError; }
/// <summary> /// Adds the PatchFiles action to the sequence table if it does not already exist. /// </summary> /// <param name="table">The sequence table to check or modify.</param> /// <param name="mainTransform">The primary authoring transform.</param> /// <param name="pairedTransform">The secondary patch transform.</param> /// <param name="mainFileRow">The file row that contains information about the patched file.</param> private void AddPatchFilesActionToSequenceTable(SequenceTable table, Output mainTransform, Output pairedTransform, Row mainFileRow) { // Find/add PatchFiles action (also determine sequence for it). // Search mainTransform first, then pairedTransform (pairedTransform overrides). bool hasPatchFilesAction = false; int seqInstallFiles = 0; int seqDuplicateFiles = 0; string tableName = table.ToString(); TestSequenceTableForPatchFilesAction( mainTransform.Tables[tableName], ref hasPatchFilesAction, ref seqInstallFiles, ref seqDuplicateFiles); TestSequenceTableForPatchFilesAction( pairedTransform.Tables[tableName], ref hasPatchFilesAction, ref seqInstallFiles, ref seqDuplicateFiles); if (!hasPatchFilesAction) { Table iesTable = pairedTransform.EnsureTable(this.core.TableDefinitions[tableName]); if (0 == iesTable.Rows.Count) { iesTable.Operation = TableOperation.Add; } Row patchAction = iesTable.CreateRow(null); WixActionRow wixPatchAction = Installer.GetStandardActions()[table, "PatchFiles"]; int sequence = wixPatchAction.Sequence; // Test for default sequence value's appropriateness if (seqInstallFiles >= sequence || (0 != seqDuplicateFiles && seqDuplicateFiles <= sequence)) { if (0 != seqDuplicateFiles) { if (seqDuplicateFiles < seqInstallFiles) { throw new WixException(WixErrors.InsertInvalidSequenceActionOrder(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); } else { sequence = (seqDuplicateFiles + seqInstallFiles) / 2; if (seqInstallFiles == sequence || seqDuplicateFiles == sequence) { throw new WixException(WixErrors.InsertSequenceNoSpace(mainFileRow.SourceLineNumbers, iesTable.Name, "InstallFiles", "DuplicateFiles", wixPatchAction.Action)); } } } else { sequence = seqInstallFiles + 1; } } patchAction[0] = wixPatchAction.Action; patchAction[1] = wixPatchAction.Condition; patchAction[2] = sequence; patchAction.Operation = RowOperation.Add; } }
private void AutomaticallySlipstreamPatches(Output bundle, ICollection<ChainPackageInfo> packages) { List<ChainPackageInfo> msiPackages = new List<ChainPackageInfo>(); Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsProductCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); Dictionary<string, List<WixBundlePatchTargetCodeRow>> targetsUpgradeCode = new Dictionary<string, List<WixBundlePatchTargetCodeRow>>(); foreach (ChainPackageInfo package in packages) { if (Compiler.ChainPackageType.Msi == package.ChainPackageType) { // Keep track of all MSI packages. msiPackages.Add(package); } else if (Compiler.ChainPackageType.Msp == package.ChainPackageType && package.Slipstream) { // Index target ProductCodes and UpgradeCodes for slipstreamed MSPs. foreach (WixBundlePatchTargetCodeRow row in package.TargetCodes) { if (row.TargetsProductCode) { List<WixBundlePatchTargetCodeRow> rows; if (!targetsProductCode.TryGetValue(row.TargetCode, out rows)) { rows = new List<WixBundlePatchTargetCodeRow>(); targetsProductCode.Add(row.TargetCode, rows); } rows.Add(row); } else if (row.TargetsUpgradeCode) { List<WixBundlePatchTargetCodeRow> rows; if (!targetsUpgradeCode.TryGetValue(row.TargetCode, out rows)) { rows = new List<WixBundlePatchTargetCodeRow>(); targetsUpgradeCode.Add(row.TargetCode, rows); } } } } } Table slipstreamMspTable = bundle.EnsureTable(this.core.TableDefinitions["SlipstreamMsp"]); RowDictionary<Row> slipstreamMspRows = new RowDictionary<Row>(slipstreamMspTable); // Loop through the MSI and slipstream patches targeting it. foreach (ChainPackageInfo msi in msiPackages) { List<WixBundlePatchTargetCodeRow> rows; if (targetsProductCode.TryGetValue(msi.ProductCode, out rows)) { foreach (WixBundlePatchTargetCodeRow row in rows) { Row slipstreamMspRow = slipstreamMspTable.CreateRow(row.SourceLineNumbers, false); slipstreamMspRow[0] = msi.Id; slipstreamMspRow[1] = row.MspPackageId; if (slipstreamMspRows.TryAdd(slipstreamMspRow)) { slipstreamMspTable.Rows.Add(slipstreamMspRow); } } rows = null; } if (!String.IsNullOrEmpty(msi.UpgradeCode) && targetsUpgradeCode.TryGetValue(msi.UpgradeCode, out rows)) { foreach (WixBundlePatchTargetCodeRow row in rows) { Row slipstreamMspRow = slipstreamMspTable.CreateRow(row.SourceLineNumbers, false); slipstreamMspRow[0] = msi.Id; slipstreamMspRow[1] = row.MspPackageId; if (slipstreamMspRows.TryAdd(slipstreamMspRow)) { slipstreamMspTable.Rows.Add(slipstreamMspRow); } } rows = null; } } }
/// <summary> /// Initializes package state from the MSP contents. /// </summary> /// <param name="core">BinderCore for messages.</param> private void ResolveMspPackage(BinderCore core, Output bundle) { string sourcePath = this.PackagePayload.FullFileName; try { // Read data out of the msp database... using (Microsoft.Deployment.WindowsInstaller.SummaryInfo sumInfo = new Microsoft.Deployment.WindowsInstaller.SummaryInfo(sourcePath, false)) { this.PatchCode = sumInfo.RevisionNumber.Substring(0, 38); } using (Microsoft.Deployment.WindowsInstaller.Database db = new Microsoft.Deployment.WindowsInstaller.Database(sourcePath)) { if (String.IsNullOrEmpty(this.DisplayName)) { this.DisplayName = ChainPackageInfo.GetPatchMetadataProperty(db, "DisplayName"); } if (String.IsNullOrEmpty(this.Description)) { this.Description = ChainPackageInfo.GetPatchMetadataProperty(db, "Description"); } this.Manufacturer = ChainPackageInfo.GetPatchMetadataProperty(db, "ManufacturerName"); } this.PatchXml = Microsoft.Deployment.WindowsInstaller.Installer.ExtractPatchXmlData(sourcePath); XmlDocument doc = new XmlDocument(); doc.LoadXml(this.PatchXml); XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("p", "http://www.microsoft.com/msi/patch_applicability.xsd"); foreach (XmlNode node in doc.SelectNodes("/p:MsiPatch/p:TargetProduct", nsmgr)) { // If this patch targes a product code, this is the best case. XmlNode targetCode = node.SelectSingleNode("p:TargetProductCode", nsmgr); WixBundlePatchTargetCodeAttributes attributes = WixBundlePatchTargetCodeAttributes.None; if (null != targetCode) { attributes = WixBundlePatchTargetCodeAttributes.TargetsProductCode; } else // maybe targets and upgrade code? { targetCode = node.SelectSingleNode("p:UpgradeCode", nsmgr); if (null != targetCode) { attributes = WixBundlePatchTargetCodeAttributes.TargetsUpgradeCode; } else // this patch targets a unknown number of products { this.TargetUnspecified = true; } } Table table = bundle.EnsureTable(core.TableDefinitions["WixBundlePatchTargetCode"]); WixBundlePatchTargetCodeRow row = (WixBundlePatchTargetCodeRow)table.CreateRow(this.PackagePayload.SourceLineNumbers, false); row.MspPackageId = this.PackagePayload.Id; row.TargetCode = targetCode.InnerText; row.Attributes = attributes; if (this.TargetCodes.TryAdd(row)) { table.Rows.Add(row); } } } catch (Microsoft.Deployment.WindowsInstaller.InstallerException e) { core.OnMessage(WixErrors.UnableToReadPackageInformation(this.PackagePayload.SourceLineNumbers, sourcePath, e.Message)); return; } if (String.IsNullOrEmpty(this.CacheId)) { this.CacheId = this.PatchCode; } }
/// <summary> /// Unbind an MSI transform file. /// </summary> /// <param name="transformFile">The transform file.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <returns>The unbound transform.</returns> private Output UnbindTransform(string transformFile, string exportBasePath) { Output transform = new Output(SourceLineNumberCollection.FromFileName(transformFile)); transform.Type = OutputType.Transform; // get the summary information table using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) { Table table = transform.Tables.EnsureTable(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(transform.SourceLineNumbers); row[0] = i; row[1] = value; } } } // create a schema msi which hopefully matches the table schemas in the transform Output schemaOutput = new Output(null); string msiDatabaseFile = Path.Combine(this.tempFiles.BasePath, "schema.msi"); foreach (TableDefinition tableDefinition in this.tableDefinitions) { // skip unreal tables and the Patch table if (!tableDefinition.IsUnreal && "Patch" != tableDefinition.Name) { schemaOutput.EnsureTable(tableDefinition); } } Hashtable addedRows = new Hashtable(); Table transformViewTable; // bind the schema msi using (Binder binder = new Binder()) { binder.SuppressAddingValidationRows = true; binder.WixVariableResolver = new WixVariableResolver(); binder.GenerateDatabase(schemaOutput, msiDatabaseFile, true, false); // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform with the ViewTransform option to collect all the modifications msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); // index the added and possibly modified rows (added rows may also appears as modified rows) transformViewTable = transformViewOutput.Tables["_TransformView"]; Hashtable modifiedRows = new Hashtable(); foreach (Row row in transformViewTable.Rows) { string tableName = (string) row[0]; string columnName = (string) row[1]; string primaryKeys = (string) row[2]; if ("INSERT" == columnName) { string index = String.Concat(tableName, ':', primaryKeys); addedRows.Add(index, null); } else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); modifiedRows[index] = row; } } // create placeholder rows for modified rows to make the transform insert the updated values when its applied foreach (Row row in modifiedRows.Values) { string tableName = (string) row[0]; string columnName = (string) row[1]; string primaryKeys = (string) row[2]; string index = String.Concat(tableName, ':', primaryKeys); // ignore information for added rows if (!addedRows.Contains(index)) { Table table = schemaOutput.Tables[tableName]; this.CreateRow(table, primaryKeys, true); } } } // re-bind the schema output with the placeholder rows binder.GenerateDatabase(schemaOutput, msiDatabaseFile, true, false); } // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { try { // apply the transform msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); // commit the database to guard against weird errors with streams msiDatabase.Commit(); } catch (Win32Exception ex) { if (0x65B == ex.NativeErrorCode) { // this commonly happens when the transform was built // against a database schema different from the internal // table definitions throw new WixException(WixErrors.TransformSchemaMismatch()); } } // unbind the database Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath, true); // index all the rows to easily find modified rows Hashtable rows = new Hashtable(); foreach (Table table in output.Tables) { foreach (Row row in table.Rows) { rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t', " ")), row); } } // process the _TransformView rows into transform rows foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; Table table = transform.Tables.EnsureTable(null, this.tableDefinitions[tableName]); if ("CREATE" == columnName) // added table { table.Operation = TableOperation.Add; } else if ("DELETE" == columnName) // deleted row { Row deletedRow = this.CreateRow(table, primaryKeys, false); deletedRow.Operation = RowOperation.Delete; } else if ("DROP" == columnName) // dropped table { table.Operation = TableOperation.Drop; } else if ("INSERT" == columnName) // added row { string index = String.Concat(tableName, ':', primaryKeys); Row addedRow = (Row)rows[index]; addedRow.Operation = RowOperation.Add; table.Rows.Add(addedRow); } else if (null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); // the _TransformView table includes information for added rows // that looks like modified rows so it sometimes needs to be ignored if (!addedRows.Contains(index)) { Row modifiedRow = (Row)rows[index]; // mark the field as modified int indexOfModifiedValue = modifiedRow.TableDefinition.Columns.IndexOf(columnName); modifiedRow.Fields[indexOfModifiedValue].Modified = true; // move the modified row into the transform the first time its encountered if (RowOperation.None == modifiedRow.Operation) { modifiedRow.Operation = RowOperation.Modify; table.Rows.Add(modifiedRow); } } } else // added column { table.Definition.Columns[columnName].Added = true; } } } return transform; }
/// <summary> /// Unbind an MSI transform file. /// </summary> /// <param name="transformFile">The transform file.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <returns>The unbound transform.</returns> private Output UnbindTransform(string transformFile, string exportBasePath) { Output transform = new Output(SourceLineNumberCollection.FromFileName(transformFile)); transform.Type = OutputType.Transform; // get the summary information table using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) { Table table = transform.Tables.EnsureTable(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(transform.SourceLineNumbers); row[0] = i; row[1] = value; } } } // create a schema msi which hopefully matches the table schemas in the transform Output schemaOutput = new Output(null); string msiDatabaseFile = Path.Combine(this.tempFiles.BasePath, "schema.msi"); foreach (TableDefinition tableDefinition in this.tableDefinitions) { // skip unreal tables and the Patch table if (!tableDefinition.IsUnreal && "Patch" != tableDefinition.Name) { schemaOutput.EnsureTable(tableDefinition); } } // bind the schema msi Binder binder = new Binder(); binder.SuppressAddingValidationRows = true; binder.WixVariableResolver = new WixVariableResolver(); binder.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications Hashtable addedRows = new Hashtable(); Table transformViewTable; using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform with the ViewTransform option to collect all the modifications msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath); // index the added and possibly modified rows (added rows may also appears as modified rows) transformViewTable = transformViewOutput.Tables["_TransformView"]; Hashtable modifiedRows = new Hashtable(); foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; if ("INSERT" == columnName) { string index = String.Concat(tableName, ':', primaryKeys); addedRows.Add(index, null); } else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); modifiedRows[index] = row; } } // create placeholder rows for modified rows to make the transform insert the updated values when its applied foreach (Row row in modifiedRows.Values) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; string index = String.Concat(tableName, ':', primaryKeys); // ignore information for added rows if (!addedRows.Contains(index)) { Table table = schemaOutput.Tables[tableName]; this.CreateRow(table, primaryKeys, true); } } } // re-bind the schema output with the placeholder rows binder.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); // commit the database to guard against weird errors with streams msiDatabase.Commit(); // unbind the database Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath); // index all the rows to easily find modified rows Hashtable rows = new Hashtable(); foreach (Table table in output.Tables) { foreach (Row row in table.Rows) { rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t')), row); } } // process the _TransformView rows into transform rows foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; Table table = transform.Tables.EnsureTable(null, this.tableDefinitions[tableName]); if ("CREATE" == columnName) // added table { table.Operation = TableOperation.Add; } else if ("DELETE" == columnName) // deleted row { Row deletedRow = this.CreateRow(table, primaryKeys, false); deletedRow.Operation = RowOperation.Delete; } else if ("DROP" == columnName) // dropped table { table.Operation = TableOperation.Drop; } else if ("INSERT" == columnName) // added row { string index = String.Concat(tableName, ':', primaryKeys); Row addedRow = (Row)rows[index]; addedRow.Operation = RowOperation.Add; table.Rows.Add(addedRow); } else if (null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); // the _TransformView table includes information for added rows // that looks like modified rows so it sometimes needs to be ignored if (!addedRows.Contains(index)) { Row modifiedRow = (Row)rows[index]; // mark the field as modified int indexOfModifiedValue = modifiedRow.TableDefinition.Columns.IndexOf(columnName); modifiedRow.Fields[indexOfModifiedValue].Modified = true; // move the modified row into the transform the first time its encountered if (RowOperation.None == modifiedRow.Operation) { modifiedRow.Operation = RowOperation.Modify; table.Rows.Add(modifiedRow); } } } else // added column { table.Definition.Columns[columnName].Added = true; } } } return(transform); }
private void PopulateChainInfoTables(Output bundle, WixBundleRow bundleInfo, List<ChainPackageInfo> chainPackages) { bool hasPerMachineNonPermanentPackages = false; foreach (ChainPackageInfo package in chainPackages) { // Update package scope from bundle scope if default. if (YesNoDefaultType.Default == package.PerMachine) { package.PerMachine = bundleInfo.PerMachine ? YesNoDefaultType.Yes : YesNoDefaultType.No; } // Keep track if any per-machine non-permanent packages exist. if (YesNoDefaultType.Yes == package.PerMachine && 0 < package.Provides.Count && !package.Permanent) { hasPerMachineNonPermanentPackages = true; } switch (package.ChainPackageType) { case Compiler.ChainPackageType.Msi: Table chainMsiPackageTable = bundle.EnsureTable(this.core.TableDefinitions["ChainMsiPackage"]); ChainMsiPackageRow row = (ChainMsiPackageRow)chainMsiPackageTable.CreateRow(null); row.ChainPackage = package.Id; row.ProductCode = package.ProductCode; row.ProductLanguage = Convert.ToInt32(package.Language, CultureInfo.InvariantCulture); row.ProductName = package.DisplayName; row.ProductVersion = package.Version; if (!String.IsNullOrEmpty(package.UpgradeCode)) { row.UpgradeCode = package.UpgradeCode; } break; default: break; } } // We will only register packages in the same scope as the bundle. // Warn if any packages with providers are in a different scope // and not permanent (permanents typically don't need a ref-count). if (!bundleInfo.PerMachine && hasPerMachineNonPermanentPackages) { this.core.OnMessage(WixWarnings.NoPerMachineDependencies()); } }
/// <summary> /// Binds a transform. /// </summary> /// <param name="transform">The transform to bind.</param> /// <param name="transformFile">The transform to create.</param> /// <returns>true if binding completed successfully; false otherwise</returns> private bool BindTransform(Output transform, string transformFile) { foreach (BinderExtension extension in this.extensions) { extension.TransformInitialize(transform); } int transformFlags = 0; Output targetOutput = new Output(null); Output updatedOutput = new Output(null); // TODO: handle added columns // to generate a localized transform, both the target and updated // databases need to have the same code page. the only reason to // set different code pages is to support localized primary key // columns, but that would only support deleting rows. if this // becomes necessary, define a PreviousCodepage property on the // Output class and persist this throughout transform generation. targetOutput.Codepage = transform.Codepage; updatedOutput.Codepage = transform.Codepage; // remove certain Property rows which will be populated from summary information values string targetUpgradeCode = null; string updatedUpgradeCode = null; Table propertyTable = transform.Tables["Property"]; if (null != propertyTable) { for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) { Row row = propertyTable.Rows[i]; if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) { propertyTable.Rows.RemoveAt(i); if ("UpgradeCode" == (string)row[0]) { updatedUpgradeCode = (string)row[1]; } } } } Table targetSummaryInfo = targetOutput.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); Table updatedSummaryInfo = updatedOutput.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); Table targetPropertyTable = targetOutput.EnsureTable(this.core.TableDefinitions["Property"]); Table updatedPropertyTable = updatedOutput.EnsureTable(this.core.TableDefinitions["Property"]); string targetProductCode = null; // process special summary information values foreach (Row row in transform.Tables["_SummaryInformation"].Rows) { if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) { // convert from a web name if provided string codePage = (string)row.Fields[1].Data; if (null == codePage) { codePage = "0"; } else { codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); } string previousCodePage = (string)row.Fields[1].PreviousData; if (null == previousCodePage) { previousCodePage = "0"; } else { previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); } Row targetCodePageRow = targetSummaryInfo.CreateRow(null); targetCodePageRow[0] = 1; // PID_CODEPAGE targetCodePageRow[1] = previousCodePage; Row updatedCodePageRow = updatedSummaryInfo.CreateRow(null); updatedCodePageRow[0] = 1; // PID_CODEPAGE updatedCodePageRow[1] = codePage; } else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] || (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) { // the target language string[] propertyData = ((string)row[1]).Split(';'); string lang = 2 == propertyData.Length ? propertyData[1] : "0"; Table tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetSummaryInfo : updatedSummaryInfo; Table tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0] ? targetPropertyTable : updatedPropertyTable; Row productLanguageRow = tempPropertyTable.CreateRow(null); productLanguageRow[0] = "ProductLanguage"; productLanguageRow[1] = lang; // set the platform;language on the MSI to be generated Row templateRow = tempSummaryInfo.CreateRow(null); templateRow[0] = 7; // PID_TEMPLATE templateRow[1] = (string)row[1]; } else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) { string[] propertyData = ((string)row[1]).Split(';'); Row targetProductCodeRow = targetPropertyTable.CreateRow(null); targetProductCodeRow[0] = "ProductCode"; targetProductCodeRow[1] = propertyData[0].Substring(0, 38); targetProductCode = propertyData[0].Substring(0, 38); Row targetProductVersionRow = targetPropertyTable.CreateRow(null); targetProductVersionRow[0] = "ProductVersion"; targetProductVersionRow[1] = propertyData[0].Substring(38); Row updatedProductCodeRow = updatedPropertyTable.CreateRow(null); updatedProductCodeRow[0] = "ProductCode"; updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); Row updatedProductVersionRow = updatedPropertyTable.CreateRow(null); updatedProductVersionRow[0] = "ProductVersion"; updatedProductVersionRow[1] = propertyData[1].Substring(38); // UpgradeCode is optional and may not exists in the target // or upgraded databases, so do not include a null-valued // UpgradeCode property. targetUpgradeCode = propertyData[2]; if (!String.IsNullOrEmpty(targetUpgradeCode)) { Row targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); targetUpgradeCodeRow[0] = "UpgradeCode"; targetUpgradeCodeRow[1] = targetUpgradeCode; // If the target UpgradeCode is specified, an updated // UpgradeCode is required. if (String.IsNullOrEmpty(updatedUpgradeCode)) { updatedUpgradeCode = targetUpgradeCode; } } if (!String.IsNullOrEmpty(updatedUpgradeCode)) { Row updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); updatedUpgradeCodeRow[0] = "UpgradeCode"; updatedUpgradeCodeRow[1] = updatedUpgradeCode; } } else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) { transformFlags = Convert.ToInt32(row[1], CultureInfo.InvariantCulture); } else if ((int)SummaryInformation.Transform.Reserved11 == (int)row[0]) { // PID_LASTPRINTED should be null for transforms row.Operation = RowOperation.None; } else { // add everything else as is Row targetRow = targetSummaryInfo.CreateRow(null); targetRow[0] = row[0]; targetRow[1] = row[1]; Row updatedRow = updatedSummaryInfo.CreateRow(null); updatedRow[0] = row[0]; updatedRow[1] = row[1]; } } // Validate that both databases have an UpgradeCode if the // authoring transform will validate the UpgradeCode; otherwise, // MsiCreateTransformSummaryinfo() will fail with 1620. if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) { this.core.OnMessage(WixErrors.BothUpgradeCodesRequired()); } foreach (Table table in transform.Tables) { // Ignore unreal tables when building transforms except the _Stream table. // These tables are ignored when generating the database so there is no reason // to process them here. if (table.Definition.IsUnreal && "_Streams" != table.Name) { continue; } // process table operations switch (table.Operation) { case TableOperation.Add: updatedOutput.EnsureTable(table.Definition); break; case TableOperation.Drop: targetOutput.EnsureTable(table.Definition); continue; default: targetOutput.EnsureTable(table.Definition); updatedOutput.EnsureTable(table.Definition); break; } // process row operations foreach (Row row in table.Rows) { switch (row.Operation) { case RowOperation.Add: Table updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(row); continue; case RowOperation.Delete: Table targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(row); // fill-in non-primary key values foreach (Field field in row.Fields) { if (!field.Column.IsPrimaryKey) { if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) { field.Data = field.Column.MinValue; } else if (ColumnType.Object == field.Column.Type) { if (null == this.emptyFile) { this.emptyFile = this.tempFiles.AddExtension("empty"); using (FileStream fileStream = File.Create(this.emptyFile)) { } } field.Data = emptyFile; } else { field.Data = "0"; } } } continue; } // Assure that the file table's sequence is populated if ("File" == table.Name) { foreach (Row fileRow in table.Rows) { if (null == fileRow[7]) { if (RowOperation.Add == fileRow.Operation) { this.core.OnMessage(WixErrors.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); break; } // Set to 1 to prevent invalid IDT file from being generated fileRow[7] = 1; } } } // process modified and unmodified rows bool modifiedRow = false; Row targetRow = new Row(null, table.Definition); Row updatedRow = row; for (int i = 0; i < row.Fields.Length; i++) { Field updatedField = row.Fields[i]; if (updatedField.Modified) { // set a different value in the target row to ensure this value will be modified during transform generation if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) { if (null == updatedField.Data || 1 != (int)updatedField.Data) { targetRow[i] = 1; } else { targetRow[i] = 2; } } else if (ColumnType.Object == updatedField.Column.Type) { if (null == emptyFile) { emptyFile = this.tempFiles.AddExtension("empty"); using (FileStream fileStream = File.Create(emptyFile)) { } } targetRow[i] = emptyFile; } else { if ("0" != (string)updatedField.Data) { targetRow[i] = "0"; } else { targetRow[i] = "1"; } } modifiedRow = true; } else if (ColumnType.Object == updatedField.Column.Type) { ObjectField objectField = (ObjectField)updatedField; // create an empty file for comparing against if (null == objectField.PreviousData) { if (null == emptyFile) { emptyFile = this.tempFiles.AddExtension("empty"); using (FileStream fileStream = File.Create(emptyFile)) { } } targetRow[i] = emptyFile; modifiedRow = true; } else if (!this.FileManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) { targetRow[i] = objectField.PreviousData; modifiedRow = true; } } else // unmodified { if (null != updatedField.Data) { targetRow[i] = updatedField.Data; } } } // modified rows and certain special rows go in the target and updated msi databases if (modifiedRow || ("Property" == table.Name && ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]))) { Table targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(targetRow); Table updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(updatedRow); } } } foreach (BinderExtension extension in this.extensions) { extension.TransformFinalize(transform); } // Any errors encountered up to this point can cause errors during generation. if (this.core.EncounteredError) { return false; } string transformFileName = Path.GetFileNameWithoutExtension(transformFile); string targetDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_target.msi")); string updatedDatabaseFile = Path.Combine(this.TempFilesLocation, String.Concat(transformFileName, "_updated.msi")); this.suppressAddingValidationRows = true; this.GenerateDatabase(targetOutput, targetDatabaseFile, false, true); this.GenerateDatabase(updatedOutput, updatedDatabaseFile, true, true); // make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(transformFile)); // create the transform file using (Database targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) { using (Database updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) { // Skip adding the patch transform if the product transform was empty. // Patch transforms are usually not empty due to changes such as Media Table Row insertions. if ((this.AllowEmptyTransforms && this.emptyTransformNames.Contains(transformFileName.Replace("#", ""))) || !updatedDatabase.GenerateTransform(targetDatabase, transformFile)) { if (this.AllowEmptyTransforms) { // Only output the message once for the product transform. if (!transformFileName.StartsWith("#")) { this.core.OnMessage(WixWarnings.NoDifferencesInTransform(transform.SourceLineNumbers)); } this.emptyTransformNames.Add(transformFileName); return false; } else { throw new WixException(WixErrors.NoDifferencesInTransform(transform.SourceLineNumbers)); } } else { if (targetProductCode != null && !this.nonEmptyProductCodes.Contains(targetProductCode)) { this.nonEmptyProductCodes.Add(targetProductCode); } if (!this.nonEmptyTransformNames.Contains(":" + transformFileName)) { this.nonEmptyTransformNames.Add(":" + transformFileName); } updatedDatabase.CreateTransformSummaryInfo(targetDatabase, transformFile, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); } } } return !this.core.EncounteredError; }
private void UpdateFileRow(Output output, IDictionary<string, string> infoCache, string modularizationGuid, Hashtable fileRowIndex, FileRow fileRow, bool overwriteHash) { FileInfo fileInfo = null; if (!this.suppressFileHashAndInfo || (!this.suppressAssemblies && FileAssemblyType.NotAnAssembly != fileRow.AssemblyType)) { try { fileInfo = new FileInfo(fileRow.Source); } catch (ArgumentException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); return; } catch (PathTooLongException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); return; } catch (NotSupportedException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); return; } } if (!this.suppressFileHashAndInfo) { if (fileInfo.Exists) { string version; string language; using (FileStream fileStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (Int32.MaxValue < fileStream.Length) { throw new WixException(WixErrors.FileTooLarge(fileRow.SourceLineNumbers, fileRow.Source)); } fileRow.FileSize = Convert.ToInt32(fileStream.Length, CultureInfo.InvariantCulture); } try { Installer.GetFileVersion(fileInfo.FullName, out version, out language); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(WixErrors.FileNotFound(fileRow.SourceLineNumbers, fileInfo.FullName)); } else { throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); } } // If there is no version, it is assumed there is no language because it won't matter in the versioning of the install. if (0 == version.Length) // unversioned files have their hashes added to the MsiFileHash table { if (null != fileRow.Version) { // Check if this is a companion file. If its not, it is a default version. if (!fileRowIndex.ContainsKey(fileRow.Version)) { this.core.OnMessage(WixWarnings.DefaultVersionUsedForUnversionedFile(fileRow.SourceLineNumbers, fileRow.Version, fileRow.File)); } } else { if (null != fileRow.Language) { this.core.OnMessage(WixWarnings.DefaultLanguageUsedForUnversionedFile(fileRow.SourceLineNumbers, fileRow.Language, fileRow.File)); } int[] hash; try { Installer.GetFileHash(fileInfo.FullName, 0, out hash); } catch (Win32Exception e) { if (0x2 == e.NativeErrorCode) // ERROR_FILE_NOT_FOUND { throw new WixException(WixErrors.FileNotFound(fileRow.SourceLineNumbers, fileInfo.FullName)); } else { throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, fileInfo.FullName, e.Message)); } } if (null == fileRow.HashRow) { Table msiFileHashTable = output.EnsureTable(this.core.TableDefinitions["MsiFileHash"]); fileRow.HashRow = msiFileHashTable.CreateRow(fileRow.SourceLineNumbers); } fileRow.HashRow[0] = fileRow.File; fileRow.HashRow[1] = 0; fileRow.HashRow[2] = hash[0]; fileRow.HashRow[3] = hash[1]; fileRow.HashRow[4] = hash[2]; fileRow.HashRow[5] = hash[3]; } } else // update the file row with the version and language information { // Check if the version field references a fileId because this would mean it has a companion file and the version should not be overwritten. if (null == fileRow.Version || !fileRowIndex.ContainsKey(fileRow.Version)) { fileRow.Version = version; } if (null != fileRow.Language && 0 == language.Length) { this.core.OnMessage(WixWarnings.DefaultLanguageUsedForVersionedFile(fileRow.SourceLineNumbers, fileRow.Language, fileRow.File)); } else { fileRow.Language = language; } // Populate the binder variables for this file information if requested. if (null != infoCache) { if (!String.IsNullOrEmpty(fileRow.Version)) { string key = String.Format(CultureInfo.InvariantCulture, "fileversion.{0}", Demodularize(output, modularizationGuid, fileRow.File)); infoCache[key] = fileRow.Version; } if (!String.IsNullOrEmpty(fileRow.Language)) { string key = String.Format(CultureInfo.InvariantCulture, "filelanguage.{0}", Demodularize(output, modularizationGuid, fileRow.File)); infoCache[key] = fileRow.Language; } } } } else { this.core.OnMessage(WixErrors.CannotFindFile(fileRow.SourceLineNumbers, fileRow.File, fileRow.FileName, fileRow.Source)); } } // if we're not suppressing automagically grabbing assembly information and this is a // CLR assembly, load the assembly and get the assembly name information if (!this.suppressAssemblies) { if (FileAssemblyType.DotNetAssembly == fileRow.AssemblyType) { StringDictionary assemblyNameValues = new StringDictionary(); CLRInterop.IReferenceIdentity referenceIdentity = null; Guid referenceIdentityGuid = CLRInterop.ReferenceIdentityGuid; uint result = CLRInterop.GetAssemblyIdentityFromFile(fileInfo.FullName, ref referenceIdentityGuid, out referenceIdentity); if (0 == result && null != referenceIdentity) { string culture = referenceIdentity.GetAttribute(null, "Culture"); if (null != culture) { assemblyNameValues.Add("Culture", culture); } else { assemblyNameValues.Add("Culture", "neutral"); } string name = referenceIdentity.GetAttribute(null, "Name"); if (null != name) { assemblyNameValues.Add("Name", name); } string processorArchitecture = referenceIdentity.GetAttribute(null, "ProcessorArchitecture"); if (null != processorArchitecture) { assemblyNameValues.Add("ProcessorArchitecture", processorArchitecture); } string publicKeyToken = referenceIdentity.GetAttribute(null, "PublicKeyToken"); if (null != publicKeyToken) { bool publicKeyIsNeutral = (String.Equals(publicKeyToken, "neutral", StringComparison.OrdinalIgnoreCase)); // Managed code expects "null" instead of "neutral", and // this won't be installed to the GAC since it's not signed anyway. assemblyNameValues.Add("publicKeyToken", publicKeyIsNeutral ? "null" : publicKeyToken.ToUpperInvariant()); assemblyNameValues.Add("publicKeyTokenPreservedCase", publicKeyIsNeutral ? "null" : publicKeyToken); } else if (fileRow.AssemblyApplication == null) { throw new WixException(WixErrors.GacAssemblyNoStrongName(fileRow.SourceLineNumbers, fileInfo.FullName, fileRow.Component)); } string version = referenceIdentity.GetAttribute(null, "Version"); if (null != version) { assemblyNameValues.Add("Version", version); } } else { this.core.OnMessage(WixErrors.InvalidAssemblyFile(fileRow.SourceLineNumbers, fileInfo.FullName, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", result))); return; } Table assemblyNameTable = output.EnsureTable(this.core.TableDefinitions["MsiAssemblyName"]); if (assemblyNameValues.ContainsKey("name")) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "name", assemblyNameValues["name"], infoCache, modularizationGuid); } string fileVersion = null; if (this.setMsiAssemblyNameFileVersion) { string language; Installer.GetFileVersion(fileInfo.FullName, out fileVersion, out language); SetMsiAssemblyName(output, assemblyNameTable, fileRow, "fileVersion", fileVersion, infoCache, modularizationGuid); } if (assemblyNameValues.ContainsKey("version")) { string assemblyVersion = assemblyNameValues["version"]; if (!this.exactAssemblyVersions) { // there is a bug in fusion that requires the assembly's "version" attribute // to be equal to or longer than the "fileVersion" in length when its present; // the workaround is to prepend zeroes to the last version number in the assembly version if (this.setMsiAssemblyNameFileVersion && null != fileVersion && fileVersion.Length > assemblyVersion.Length) { string padding = new string('0', fileVersion.Length - assemblyVersion.Length); string[] assemblyVersionNumbers = assemblyVersion.Split('.'); if (assemblyVersionNumbers.Length > 0) { assemblyVersionNumbers[assemblyVersionNumbers.Length - 1] = String.Concat(padding, assemblyVersionNumbers[assemblyVersionNumbers.Length - 1]); assemblyVersion = String.Join(".", assemblyVersionNumbers); } } } SetMsiAssemblyName(output, assemblyNameTable, fileRow, "version", assemblyVersion, infoCache, modularizationGuid); } if (assemblyNameValues.ContainsKey("culture")) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "culture", assemblyNameValues["culture"], infoCache, modularizationGuid); } if (assemblyNameValues.ContainsKey("publicKeyToken")) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "publicKeyToken", assemblyNameValues["publicKeyToken"], infoCache, modularizationGuid); } if (null != fileRow.ProcessorArchitecture && 0 < fileRow.ProcessorArchitecture.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "processorArchitecture", fileRow.ProcessorArchitecture, infoCache, modularizationGuid); } if (assemblyNameValues.ContainsKey("processorArchitecture")) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "processorArchitecture", assemblyNameValues["processorArchitecture"], infoCache, modularizationGuid); } // add the assembly name to the information cache if (null != infoCache) { string fileId = Demodularize(output, modularizationGuid, fileRow.File); string key = String.Concat("assemblyfullname.", fileId); string assemblyName = String.Concat(assemblyNameValues["name"], ", version=", assemblyNameValues["version"], ", culture=", assemblyNameValues["culture"], ", publicKeyToken=", String.IsNullOrEmpty(assemblyNameValues["publicKeyToken"]) ? "null" : assemblyNameValues["publicKeyToken"]); if (assemblyNameValues.ContainsKey("processorArchitecture")) { assemblyName = String.Concat(assemblyName, ", processorArchitecture=", assemblyNameValues["processorArchitecture"]); } infoCache[key] = assemblyName; // Add entries with the preserved case publicKeyToken string pcAssemblyNameKey = String.Concat("assemblyfullnamepreservedcase.", fileId); infoCache[pcAssemblyNameKey] = (assemblyNameValues["publicKeyToken"] == assemblyNameValues["publicKeyTokenPreservedCase"]) ? assemblyName : assemblyName.Replace(assemblyNameValues["publicKeyToken"], assemblyNameValues["publicKeyTokenPreservedCase"]); string pcPublicKeyTokenKey = String.Concat("assemblypublickeytokenpreservedcase.", fileId); infoCache[pcPublicKeyTokenKey] = assemblyNameValues["publicKeyTokenPreservedCase"]; } } else if (FileAssemblyType.Win32Assembly == fileRow.AssemblyType) { // Able to use the index because only the Source field is used and it is used only for more complete error messages. FileRow fileManifestRow = (FileRow)fileRowIndex[fileRow.AssemblyManifest]; if (null == fileManifestRow) { this.core.OnMessage(WixErrors.MissingManifestForWin32Assembly(fileRow.SourceLineNumbers, fileRow.File, fileRow.AssemblyManifest)); } string type = null; string name = null; string version = null; string processorArchitecture = null; string publicKeyToken = null; // loading the dom is expensive we want more performant APIs than the DOM // Navigator is cheaper than dom. Perhaps there is a cheaper API still. try { XPathDocument doc = new XPathDocument(fileManifestRow.Source); XPathNavigator nav = doc.CreateNavigator(); nav.MoveToRoot(); // this assumes a particular schema for a win32 manifest and does not // provide error checking if the file does not conform to schema. // The fallback case here is that nothing is added to the MsiAssemblyName // table for an out of tolerance Win32 manifest. Perhaps warnings needed. if (nav.MoveToFirstChild()) { while (nav.NodeType != XPathNodeType.Element || nav.Name != "assembly") { nav.MoveToNext(); } if (nav.MoveToFirstChild()) { bool hasNextSibling = true; while (nav.NodeType != XPathNodeType.Element || nav.Name != "assemblyIdentity" && hasNextSibling) { hasNextSibling = nav.MoveToNext(); } if (!hasNextSibling) { this.core.OnMessage(WixErrors.InvalidManifestContent(fileRow.SourceLineNumbers, fileManifestRow.Source)); return; } if (nav.MoveToAttribute("type", String.Empty)) { type = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("name", String.Empty)) { name = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("version", String.Empty)) { version = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("processorArchitecture", String.Empty)) { processorArchitecture = nav.Value; nav.MoveToParent(); } if (nav.MoveToAttribute("publicKeyToken", String.Empty)) { publicKeyToken = nav.Value; nav.MoveToParent(); } } } } catch (FileNotFoundException fe) { this.core.OnMessage(WixErrors.FileNotFound(SourceLineNumberCollection.FromFileName(fileManifestRow.Source), fe.FileName, "AssemblyManifest")); } catch (XmlException xe) { this.core.OnMessage(WixErrors.InvalidXml(SourceLineNumberCollection.FromFileName(fileManifestRow.Source), "manifest", xe.Message)); } Table assemblyNameTable = output.EnsureTable(this.core.TableDefinitions["MsiAssemblyName"]); if (null != name && 0 < name.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "name", name, infoCache, modularizationGuid); } if (null != version && 0 < version.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "version", version, infoCache, modularizationGuid); } if (null != type && 0 < type.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "type", type, infoCache, modularizationGuid); } if (null != processorArchitecture && 0 < processorArchitecture.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "processorArchitecture", processorArchitecture, infoCache, modularizationGuid); } if (null != publicKeyToken && 0 < publicKeyToken.Length) { SetMsiAssemblyName(output, assemblyNameTable, fileRow, "publicKeyToken", publicKeyToken, infoCache, modularizationGuid); } } } }
/// <summary> /// Copy file data between transform substorages and the patch output object /// </summary> /// <param name="output">The output to bind.</param> /// <param name="allFileRows">True if copying from transform to patch, false the other way.</param> /// <returns>true if binding completed successfully; false otherwise</returns> private bool CopyTransformData(Output output, FileRowCollection allFileRows) { bool copyToPatch = (allFileRows != null); bool copyFromPatch = !copyToPatch; if (OutputType.Patch != output.Type) { return true; } Hashtable patchMediaRows = new Hashtable(); Hashtable patchMediaFileRows = new Hashtable(); Table patchFileTable = output.EnsureTable(this.core.TableDefinitions["File"]); if (copyFromPatch) { // index patch files by diskId+fileId foreach (FileRow patchFileRow in patchFileTable.Rows) { int diskId = patchFileRow.DiskId; if (!patchMediaFileRows.Contains(diskId)) { patchMediaFileRows[diskId] = new FileRowCollection(); } FileRowCollection mediaFileRows = (FileRowCollection)patchMediaFileRows[diskId]; mediaFileRows.Add(patchFileRow); } Table patchMediaTable = output.EnsureTable(this.core.TableDefinitions["Media"]); foreach (MediaRow patchMediaRow in patchMediaTable.Rows) { patchMediaRows[patchMediaRow.DiskId] = patchMediaRow; } } // index paired transforms Hashtable pairedTransforms = new Hashtable(); foreach (SubStorage substorage in output.SubStorages) { if ("#" == substorage.Name.Substring(0, 1)) { pairedTransforms[substorage.Name.Substring(1)] = substorage.Data; } } try { // copy File bind data into substorages foreach (SubStorage substorage in output.SubStorages) { if ("#" == substorage.Name.Substring(0, 1)) { // no changes necessary for paired transforms continue; } Output mainTransform = (Output)substorage.Data; Table mainWixFileTable = mainTransform.Tables["WixFile"]; Table mainMsiFileHashTable = mainTransform.Tables["MsiFileHash"]; int numWixFileRows = (null != mainWixFileTable) ? mainWixFileTable.Rows.Count : 0; int numMsiFileHashRows = (null != mainMsiFileHashTable) ? mainMsiFileHashTable.Rows.Count : 0; this.FileManager.ActiveSubStorage = substorage; Hashtable wixFiles = new Hashtable(numWixFileRows); Hashtable mainMsiFileHashIndex = new Hashtable(numMsiFileHashRows); Table mainFileTable = mainTransform.Tables["File"]; Output pairedTransform = (Output)pairedTransforms[substorage.Name]; if (null != mainWixFileTable) { // Index the WixFile table for later use. foreach (WixFileRow row in mainWixFileTable.Rows) { wixFiles.Add(row.Fields[0].Data.ToString(), row); } } // copy Media.LastSequence and index the MsiFileHash table if it exists. if (copyFromPatch) { Table pairedMediaTable = pairedTransform.Tables["Media"]; foreach (MediaRow pairedMediaRow in pairedMediaTable.Rows) { MediaRow patchMediaRow = (MediaRow)patchMediaRows[pairedMediaRow.DiskId]; pairedMediaRow.Fields[1] = patchMediaRow.Fields[1]; } if (null != mainMsiFileHashTable) { // Index the MsiFileHash table for later use. foreach (Row row in mainMsiFileHashTable.Rows) { mainMsiFileHashIndex.Add(row[0], row); } } // Validate file row changes for keypath-related issues this.ValidateFileRowChanges(mainTransform); } // index File table of pairedTransform FileRowCollection pairedFileRows = new FileRowCollection(); Table pairedFileTable = pairedTransform.Tables["File"]; if (null != pairedFileTable) { pairedFileRows.AddRange(pairedFileTable.Rows); } if (null != mainFileTable) { if (copyFromPatch) { // Remove the MsiFileHash table because it will be updated later with the final file hash for each file mainTransform.Tables.Remove("MsiFileHash"); } foreach (FileRow mainFileRow in mainFileTable.Rows) { if (mainFileRow.Operation == RowOperation.Delete) { continue; } if (!copyToPatch && mainFileRow.Operation == RowOperation.None) { continue; } // When copying to the patch, we need compare the underlying files and include all file changes. else if (copyToPatch) { WixFileRow wixFileRow = (WixFileRow)wixFiles[mainFileRow.Fields[0].Data.ToString()]; ObjectField objectField = (ObjectField)wixFileRow.Fields[6]; FileRow pairedFileRow = pairedFileRows[mainFileRow.Fields[0].Data.ToString()]; // If the file is new, we always need to add it to the patch. if (mainFileRow.Operation != RowOperation.Add) { // If PreviousData doesn't exist, target and upgrade layout point to the same location. No need to compare. if (null == objectField.PreviousData) { if (mainFileRow.Operation == RowOperation.None) { continue; } } else { // TODO: should this entire condition be placed in the binder file manager? if ((0 == (PatchAttributeType.Ignore & wixFileRow.PatchAttributes)) && !this.FileManager.CompareFiles(objectField.PreviousData.ToString(), objectField.Data.ToString())) { // If the file is different, we need to mark the mainFileRow and pairedFileRow as modified. mainFileRow.Operation = RowOperation.Modify; if (null != pairedFileRow) { // Always patch-added, but never non-compressed. pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Modify; } } else { // The File is same. We need mark all the attributes as unchanged. mainFileRow.Operation = RowOperation.None; foreach (Field field in mainFileRow.Fields) { field.Modified = false; } if (null != pairedFileRow) { pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Fields[6].Modified = false; pairedFileRow.Operation = RowOperation.None; } continue; } } } else if (null != pairedFileRow) // RowOperation.Add { // Always patch-added, but never non-compressed. pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = RowOperation.Add; } } // index patch files by diskId+fileId int diskId = mainFileRow.DiskId; if (!patchMediaFileRows.Contains(diskId)) { patchMediaFileRows[diskId] = new FileRowCollection(); } FileRowCollection mediaFileRows = (FileRowCollection)patchMediaFileRows[diskId]; string fileId = mainFileRow.File; FileRow patchFileRow = mediaFileRows[fileId]; if (copyToPatch) { if (null == patchFileRow) { patchFileRow = (FileRow)patchFileTable.CreateRow(null); patchFileRow.CopyFrom(mainFileRow); mediaFileRows.Add(patchFileRow); allFileRows.Add(patchFileRow); } else { // TODO: confirm the rest of data is identical? // make sure Source is same. Otherwise we are silently ignoring a file. if (0 != String.Compare(patchFileRow.Source, mainFileRow.Source, StringComparison.OrdinalIgnoreCase)) { this.core.OnMessage(WixErrors.SameFileIdDifferentSource(mainFileRow.SourceLineNumbers, fileId, patchFileRow.Source, mainFileRow.Source)); } // capture the previous file versions (and associated data) from this targeted instance of the baseline into the current filerow. patchFileRow.AppendPreviousDataFrom(mainFileRow); } } else { // copy data from the patch back to the transform if (null != patchFileRow) { FileRow pairedFileRow = (FileRow)pairedFileRows[fileId]; for (int i = 0; i < patchFileRow.Fields.Length; i++) { string patchValue = patchFileRow[i] == null ? "" : patchFileRow[i].ToString(); string mainValue = mainFileRow[i] == null ? "" : mainFileRow[i].ToString(); if (1 == i) { // File.Component_ changes should not come from the shared file rows // that contain the file information as each individual transform might // have different changes (or no changes at all). } // File.Attributes should not changed for binary deltas else if (6 == i) { if (null != patchFileRow.Patch) { // File.Attribute should not change for binary deltas pairedFileRow.Attributes = mainFileRow.Attributes; mainFileRow.Fields[i].Modified = false; } } // File.Sequence is updated in pairedTransform, not mainTransform else if (7 == i) { // file sequence is updated in Patch table instead of File table for delta patches if (null != patchFileRow.Patch) { pairedFileRow.Fields[i].Modified = false; } else { pairedFileRow[i] = patchFileRow[i]; pairedFileRow.Fields[i].Modified = true; } mainFileRow.Fields[i].Modified = false; } else if (patchValue != mainValue) { mainFileRow[i] = patchFileRow[i]; mainFileRow.Fields[i].Modified = true; if (mainFileRow.Operation == RowOperation.None) { mainFileRow.Operation = RowOperation.Modify; } } } // copy MsiFileHash row for this File Row patchHashRow; if (mainMsiFileHashIndex.ContainsKey(patchFileRow.File)) { patchHashRow = (Row)mainMsiFileHashIndex[patchFileRow.File]; } else { patchHashRow = patchFileRow.HashRow; } if (null != patchHashRow) { Table mainHashTable = mainTransform.EnsureTable(this.core.TableDefinitions["MsiFileHash"]); Row mainHashRow = mainHashTable.CreateRow(mainFileRow.SourceLineNumbers); for (int i = 0; i < patchHashRow.Fields.Length; i++) { mainHashRow[i] = patchHashRow[i]; if (i > 1) { // assume all hash fields have been modified mainHashRow.Fields[i].Modified = true; } } // assume the MsiFileHash operation follows the File one mainHashRow.Operation = mainFileRow.Operation; } // copy MsiAssemblyName rows for this File RowCollection patchAssemblyNameRows = patchFileRow.AssemblyNameRows; if (null != patchAssemblyNameRows) { Table mainAssemblyNameTable = mainTransform.EnsureTable(this.core.TableDefinitions["MsiAssemblyName"]); foreach (Row patchAssemblyNameRow in patchAssemblyNameRows) { // Copy if there isn't an identical modified/added row already in the transform. bool foundMatchingModifiedRow = false; foreach (Row mainAssemblyNameRow in mainAssemblyNameTable.Rows) { if (RowOperation.None != mainAssemblyNameRow.Operation && mainAssemblyNameRow.GetPrimaryKey('/').Equals(patchAssemblyNameRow.GetPrimaryKey('/'))) { foundMatchingModifiedRow = true; break; } } if (!foundMatchingModifiedRow) { Row mainAssemblyNameRow = mainAssemblyNameTable.CreateRow(mainFileRow.SourceLineNumbers); for (int i = 0; i < patchAssemblyNameRow.Fields.Length; i++) { mainAssemblyNameRow[i] = patchAssemblyNameRow[i]; } // assume value field has been modified mainAssemblyNameRow.Fields[2].Modified = true; mainAssemblyNameRow.Operation = mainFileRow.Operation; } } } // Add patch header for this file if (null != patchFileRow.Patch) { // Add the PatchFiles action automatically to the AdminExecuteSequence and InstallExecuteSequence tables. AddPatchFilesActionToSequenceTable(SequenceTable.AdminExecuteSequence, mainTransform, pairedTransform, mainFileRow); AddPatchFilesActionToSequenceTable(SequenceTable.InstallExecuteSequence, mainTransform, pairedTransform, mainFileRow); // Add to Patch table Table patchTable = pairedTransform.EnsureTable(this.core.TableDefinitions["Patch"]); if (0 == patchTable.Rows.Count) { patchTable.Operation = TableOperation.Add; } Row patchRow = patchTable.CreateRow(mainFileRow.SourceLineNumbers); patchRow[0] = patchFileRow.File; patchRow[1] = patchFileRow.Sequence; FileInfo patchFile = new FileInfo(patchFileRow.Source); patchRow[2] = (int)patchFile.Length; patchRow[3] = 0 == (PatchAttributeType.AllowIgnoreOnError & patchFileRow.PatchAttributes) ? 0 : 1; string streamName = patchTable.Name + "." + patchRow[0] + "." + patchRow[1]; if (MsiInterop.MsiMaxStreamNameLength < streamName.Length) { streamName = "_" + Guid.NewGuid().ToString("D").ToUpper(CultureInfo.InvariantCulture).Replace('-', '_'); Table patchHeadersTable = pairedTransform.EnsureTable(this.core.TableDefinitions["MsiPatchHeaders"]); if (0 == patchHeadersTable.Rows.Count) { patchHeadersTable.Operation = TableOperation.Add; } Row patchHeadersRow = patchHeadersTable.CreateRow(mainFileRow.SourceLineNumbers); patchHeadersRow[0] = streamName; patchHeadersRow[1] = patchFileRow.Patch; patchRow[5] = streamName; patchHeadersRow.Operation = RowOperation.Add; } else { patchRow[4] = patchFileRow.Patch; } patchRow.Operation = RowOperation.Add; } } else { // TODO: throw because all transform rows should have made it into the patch } } } } if (copyFromPatch) { output.Tables.Remove("Media"); output.Tables.Remove("File"); output.Tables.Remove("MsiFileHash"); output.Tables.Remove("MsiAssemblyName"); } } } finally { this.FileManager.ActiveSubStorage = null; } return true; }
/// <summary> /// Creates the MSI/MSM/PCP database. /// </summary> /// <param name="output">Output to create database for.</param> /// <param name="databaseFile">The database file to create.</param> /// <param name="keepAddedColumns">Whether to keep columns added in a transform.</param> /// <param name="useSubdirectory">Whether to use a subdirectory based on the <paramref name="databaseFile"/> file name for intermediate files.</param> internal void GenerateDatabase(Output output, string databaseFile, bool keepAddedColumns, bool useSubdirectory) { // add the _Validation rows if (!this.suppressAddingValidationRows) { Table validationTable = output.EnsureTable(this.core.TableDefinitions["_Validation"]); foreach (Table table in output.Tables) { if (!table.Definition.IsUnreal) { // add the validation rows for this table table.Definition.AddValidationRows(validationTable); } } } // set the base directory string baseDirectory = this.TempFilesLocation; if (useSubdirectory) { string filename = Path.GetFileNameWithoutExtension(databaseFile); baseDirectory = Path.Combine(baseDirectory, filename); // make sure the directory exists Directory.CreateDirectory(baseDirectory); } try { OpenDatabase type = OpenDatabase.CreateDirect; // set special flag for patch files if (OutputType.Patch == output.Type) { type |= OpenDatabase.OpenPatchFile; } // try to create the database using (Database db = new Database(databaseFile, type)) { // localize the codepage if a value was specified by the localizer if (null != this.Localizer && -1 != this.Localizer.Codepage) { output.Codepage = this.Localizer.Codepage; } // if we're not using the default codepage, import a new one into our // database before we add any tables (or the tables would be added // with the wrong codepage) if (0 != output.Codepage) { this.SetDatabaseCodepage(db, output); } // insert substorages (like transforms inside a patch) if (0 < output.SubStorages.Count) { using (View storagesView = new View(db, "SELECT `Name`, `Data` FROM `_Storages`")) { foreach (SubStorage subStorage in output.SubStorages) { string transformFile = Path.Combine(this.TempFilesLocation, String.Concat(subStorage.Name, ".mst")); // bind the transform if (this.BindTransform(subStorage.Data, transformFile)) { // add the storage using (Record record = new Record(2)) { record.SetString(1, subStorage.Name); record.SetStream(2, transformFile); storagesView.Modify(ModifyView.Assign, record); } } } } // some empty transforms may have been excluded // we need to remove these from the final patch summary information if (OutputType.Patch == output.Type && this.AllowEmptyTransforms) { Table patchSummaryInfo = output.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) { Row row = patchSummaryInfo.Rows[i]; if ((int)SummaryInformation.Patch.ProductCodes == (int)row[0]) { if (nonEmptyProductCodes.Count > 0) { string[] productCodes = new string[nonEmptyProductCodes.Count]; nonEmptyProductCodes.CopyTo(productCodes, 0); row[1] = String.Join(";", productCodes); } else { row[1] = Binder.NullString; } } else if ((int)SummaryInformation.Patch.TransformNames == (int)row[0]) { if (nonEmptyTransformNames.Count > 0) { string[] transformNames = new string[nonEmptyTransformNames.Count]; nonEmptyTransformNames.CopyTo(transformNames, 0); row[1] = String.Join(";", transformNames); } else { row[1] = Binder.NullString; } } } } } foreach (Table table in output.Tables) { Table importTable = table; bool hasBinaryColumn = false; // skip all unreal tables other than _Streams if (table.Definition.IsUnreal && "_Streams" != table.Name) { continue; } // Do not put the _Validation table in patches, it is not needed if (OutputType.Patch == output.Type && "_Validation" == table.Name) { continue; } // The only way to import binary data is to copy it to a local subdirectory first. // To avoid this extra copying and perf hit, import an empty table with the same // definition and later import the binary data from source using records. foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (ColumnType.Object == columnDefinition.Type) { importTable = new Table(table.Section, table.Definition); hasBinaryColumn = true; break; } } // create the table via IDT import if ("_Streams" != importTable.Name) { try { db.ImportTable(output.Codepage, this.core, importTable, baseDirectory, keepAddedColumns); } catch (WixInvalidIdtException) { // If ValidateRows finds anything it doesn't like, it throws importTable.ValidateRows(); // Otherwise we rethrow the InvalidIdt throw; } } // insert the rows via SQL query if this table contains object fields if (hasBinaryColumn) { StringBuilder query = new StringBuilder("SELECT "); // build the query for the view bool firstColumn = true; foreach (ColumnDefinition columnDefinition in table.Definition.Columns) { if (!firstColumn) { query.Append(","); } query.AppendFormat(" `{0}`", columnDefinition.Name); firstColumn = false; } query.AppendFormat(" FROM `{0}`", table.Name); using (View tableView = db.OpenExecuteView(query.ToString())) { // import each row containing a stream foreach (Row row in table.Rows) { using (Record record = new Record(table.Definition.Columns.Count)) { StringBuilder streamName = new StringBuilder(); bool needStream = false; // the _Streams table doesn't prepend the table name (or a period) if ("_Streams" != table.Name) { streamName.Append(table.Name); } for (int i = 0; i < table.Definition.Columns.Count; i++) { ColumnDefinition columnDefinition = table.Definition.Columns[i]; switch (columnDefinition.Type) { case ColumnType.Localized: case ColumnType.Preserved: case ColumnType.String: if (columnDefinition.IsPrimaryKey) { if (0 < streamName.Length) { streamName.Append("."); } streamName.Append((string)row[i]); } record.SetString(i + 1, (string)row[i]); break; case ColumnType.Number: record.SetInteger(i + 1, Convert.ToInt32(row[i], CultureInfo.InvariantCulture)); break; case ColumnType.Object: if (null != row[i]) { needStream = true; try { record.SetStream(i + 1, (string)row[i]); } catch (Win32Exception e) { if (0xA1 == e.NativeErrorCode) // ERROR_BAD_PATHNAME { throw new WixException(WixErrors.FileNotFound(row.SourceLineNumbers, (string)row[i])); } else { throw new WixException(WixErrors.Win32Exception(e.NativeErrorCode, e.Message)); } } } break; } } // stream names are created by concatenating the name of the table with the values // of the primary key (delimited by periods) // check for a stream name that is more than 62 characters long (the maximum allowed length) if (needStream && MsiInterop.MsiMaxStreamNameLength < streamName.Length) { this.core.OnMessage(WixErrors.StreamNameTooLong(row.SourceLineNumbers, table.Name, streamName.ToString(), streamName.Length)); } else // add the row to the database { tableView.Modify(ModifyView.Assign, record); } } } } // Remove rows from the _Streams table for wixpdbs. if ("_Streams" == table.Name) { table.Rows.Clear(); } } } // we're good, commit the changes to the new MSI db.Commit(); } } catch (IOException) { // TODO: this error message doesn't seem specific enough throw new WixFileNotFoundException(SourceLineNumberCollection.FromFileName(databaseFile), databaseFile); } }
/// <summary> /// Creates a work item to create a cabinet. /// </summary> /// <param name="output">Output for the current database.</param> /// <param name="cabinetDir">Directory to create cabinet in.</param> /// <param name="mediaRow">MediaRow containing information about the cabinet.</param> /// <param name="fileRows">Collection of files in this cabinet.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> /// <returns>created CabinetWorkItem object</returns> private CabinetWorkItem CreateCabinetWorkItem(Output output, string cabinetDir, MediaRow mediaRow, FileRowCollection fileRows, ArrayList fileTransfers) { CabinetWorkItem cabinetWorkItem = null; string tempCabinetFile = Path.Combine(this.TempFilesLocation, mediaRow.Cabinet); // check for an empty cabinet if (0 == fileRows.Count) { string cabinetName = mediaRow.Cabinet; // remove the leading '#' from the embedded cabinet name to make the warning easier to understand if (cabinetName.StartsWith("#", StringComparison.Ordinal)) { cabinetName = cabinetName.Substring(1); } // If building a patch, remind them to run -p for torch. if (OutputType.Patch == output.Type) { this.core.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName, true)); } else { this.core.OnMessage(WixWarnings.EmptyCabinet(mediaRow.SourceLineNumbers, cabinetName)); } } CabinetBuildOption cabinetBuildOption = this.FileManager.ResolveCabinet(fileRows, ref tempCabinetFile); // create a cabinet work item if it's not being skipped if (CabinetBuildOption.BuildAndCopy == cabinetBuildOption || CabinetBuildOption.BuildAndMove == cabinetBuildOption) { int maxThreshold = 0; // default to the threshold for best smartcabbing (makes smallest cabinet). Cab.CompressionLevel compressionLevel = this.defaultCompressionLevel; if (mediaRow.HasExplicitCompressionLevel) { compressionLevel = mediaRow.CompressionLevel; } cabinetWorkItem = new CabinetWorkItem(fileRows, tempCabinetFile, maxThreshold, compressionLevel, this.FileManager); } else // reuse the cabinet from the cabinet cache. { this.core.OnMessage(WixVerboses.ReusingCabCache(mediaRow.SourceLineNumbers, mediaRow.Cabinet, tempCabinetFile)); try { // Ensure the cached cabinet timestamp is current to prevent perpetual incremental builds. The // problematic scenario goes like this. Imagine two cabinets in the cache. Update a file that // goes into one of the cabinets. One cabinet will get rebuilt, the other will be copied from // the cache. Now the file (an input) has a newer timestamp than the reused cabient (an output) // causing the project to look like it perpetually needs a rebuild until all of the reused // cabinets get newer timestamps. File.SetLastWriteTime(tempCabinetFile, DateTime.Now); } catch (Exception e) { this.core.OnMessage(WixWarnings.CannotUpdateCabCache(mediaRow.SourceLineNumbers, tempCabinetFile, e.Message)); } } if (mediaRow.Cabinet.StartsWith("#", StringComparison.Ordinal)) { Table streamsTable = output.EnsureTable(this.core.TableDefinitions["_Streams"]); Row streamRow = streamsTable.CreateRow(null); streamRow[0] = mediaRow.Cabinet.Substring(1); streamRow[1] = tempCabinetFile; } else { string destinationPath = Path.Combine(cabinetDir, mediaRow.Cabinet); FileTransfer transfer; if (FileTransfer.TryCreate(tempCabinetFile, destinationPath, CabinetBuildOption.BuildAndMove == cabinetBuildOption, "Cabinet", mediaRow.SourceLineNumbers, out transfer)) { transfer.Built = true; fileTransfers.Add(transfer); } } return cabinetWorkItem; }
/// <summary> /// Process the complex references. /// </summary> /// <param name="output">Active output to add sections to.</param> /// <param name="sections">Sections that are referenced during the link process.</param> /// <param name="referencedSymbols">Collection of all symbols referenced during linking.</param> /// <param name="componentsToFeatures">Component to feature complex references.</param> /// <param name="featuresToFeatures">Feature to feature complex references.</param> /// <param name="modulesToFeatures">Module to feature complex references.</param> private void ProcessComplexReferences( Output output, SectionCollection sections, StringCollection referencedSymbols, ConnectToFeatureCollection componentsToFeatures, ConnectToFeatureCollection featuresToFeatures, ConnectToFeatureCollection modulesToFeatures) { Hashtable componentsToModules = new Hashtable(); foreach (Section section in sections) { Table wixComplexReferenceTable = section.Tables["WixComplexReference"]; if (null != wixComplexReferenceTable) { foreach (WixComplexReferenceRow wixComplexReferenceRow in wixComplexReferenceTable.Rows) { ConnectToFeature connection; switch (wixComplexReferenceRow.ParentType) { case ComplexReferenceParentType.Feature: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: connection = componentsToFeatures[wixComplexReferenceRow.ChildId]; if (null == connection) { componentsToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId); } // add a row to the FeatureComponents table Table featureComponentsTable = output.EnsureTable(this.tableDefinitions["FeatureComponents"]); Row row = featureComponentsTable.CreateRow(null); if (this.sectionIdOnRows) { row.SectionId = section.Id; } row[0] = wixComplexReferenceRow.ParentId; row[1] = wixComplexReferenceRow.ChildId; // index the component for finding orphaned records string symbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId); if (!referencedSymbols.Contains(symbolName)) { referencedSymbols.Add(symbolName); } break; case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.ChildId]; if (null != connection) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); break; case ComplexReferenceChildType.Module: connection = modulesToFeatures[wixComplexReferenceRow.ChildId]; if (null == connection) { modulesToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentId, wixComplexReferenceRow.IsPrimary)); } else if (wixComplexReferenceRow.IsPrimary) { if (connection.IsExplicitPrimaryFeature) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } else { connection.ConnectFeatures.Add(connection.PrimaryFeature); // move the guessed primary feature to the list of connects connection.PrimaryFeature = wixComplexReferenceRow.ParentId; // set the new primary feature connection.IsExplicitPrimaryFeature = true; // and make sure we remember that we set it so we can fail if we try to set it again } } else { connection.ConnectFeatures.Add(wixComplexReferenceRow.ParentId); } break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Module: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Component: if (componentsToModules.ContainsKey(wixComplexReferenceRow.ChildId)) { this.OnMessage(WixErrors.ComponentReferencedTwice(section.SourceLineNumbers, wixComplexReferenceRow.ChildId)); continue; } else { componentsToModules.Add(wixComplexReferenceRow.ChildId, wixComplexReferenceRow); // should always be new // add a row to the ModuleComponents table Table moduleComponentsTable = output.EnsureTable(this.tableDefinitions["ModuleComponents"]); Row row = moduleComponentsTable.CreateRow(null); if (this.sectionIdOnRows) { row.SectionId = section.Id; } row[0] = wixComplexReferenceRow.ChildId; row[1] = wixComplexReferenceRow.ParentId; row[2] = wixComplexReferenceRow.ParentLanguage; } // index the component for finding orphaned records string componentSymbolName = String.Concat("Component:", wixComplexReferenceRow.ChildId); if (!referencedSymbols.Contains(componentSymbolName)) { referencedSymbols.Add(componentSymbolName); } break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Patch: switch(wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.PatchFamily: case ComplexReferenceChildType.PatchFamilyGroup: break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; case ComplexReferenceParentType.Product: switch (wixComplexReferenceRow.ChildType) { case ComplexReferenceChildType.Feature: connection = featuresToFeatures[wixComplexReferenceRow.ChildId]; if (null != connection) { this.OnMessage(WixErrors.MultiplePrimaryReferences(section.SourceLineNumbers, wixComplexReferenceRow.ChildType.ToString(), wixComplexReferenceRow.ChildId, wixComplexReferenceRow.ParentType.ToString(), wixComplexReferenceRow.ParentId, (null != connection.PrimaryFeature ? "Feature" : "Product"), (null != connection.PrimaryFeature ? connection.PrimaryFeature : this.activeOutput.EntrySection.Id))); continue; } featuresToFeatures.Add(new ConnectToFeature(section, wixComplexReferenceRow.ChildId, null, wixComplexReferenceRow.IsPrimary)); break; default: throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceChildType), wixComplexReferenceRow.ChildType))); } break; default: // Note: Groups have been processed before getting here so they are not handled by any case above. throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedComplexReferenceChildType, Enum.GetName(typeof(ComplexReferenceParentType), wixComplexReferenceRow.ParentType))); } } } } }
/// <summary> /// Creates instance transform substorages in the output. /// </summary> /// <param name="output">Output containing instance transform definitions.</param> private void CreateInstanceTransforms(Output output) { // Create and add substorages for instance transforms. Table wixInstanceTransformsTable = output.Tables["WixInstanceTransforms"]; if (null != wixInstanceTransformsTable && 0 <= wixInstanceTransformsTable.Rows.Count) { string targetProductCode = null; string targetUpgradeCode = null; string targetProductVersion = null; Table targetSummaryInformationTable = output.Tables["_SummaryInformation"]; Table targetPropertyTable = output.Tables["Property"]; // Get the data from target database foreach (Row propertyRow in targetPropertyTable.Rows) { if ("ProductCode" == (string)propertyRow[0]) { targetProductCode = (string)propertyRow[1]; } else if ("ProductVersion" == (string)propertyRow[0]) { targetProductVersion = (string)propertyRow[1]; } else if ("UpgradeCode" == (string)propertyRow[0]) { targetUpgradeCode = (string)propertyRow[1]; } } // Index the Instance Component Rows. Dictionary<string, ComponentRow> instanceComponentGuids = new Dictionary<string, ComponentRow>(); Table targetInstanceComponentTable = output.Tables["WixInstanceComponent"]; if (null != targetInstanceComponentTable && 0 < targetInstanceComponentTable.Rows.Count) { foreach (Row row in targetInstanceComponentTable.Rows) { // Build up all the instances, we'll get the Components rows from the real Component table. instanceComponentGuids.Add((string)row[0], null); } Table targetComponentTable = output.Tables["Component"]; foreach (ComponentRow componentRow in targetComponentTable.Rows) { string component = (string)componentRow[0]; if (instanceComponentGuids.ContainsKey(component)) { instanceComponentGuids[component] = componentRow; } } } // Generate the instance transforms foreach (Row instanceRow in wixInstanceTransformsTable.Rows) { string instanceId = (string)instanceRow[0]; Output instanceTransform = new Output(instanceRow.SourceLineNumbers); instanceTransform.Type = OutputType.Transform; instanceTransform.Codepage = output.Codepage; Table instanceSummaryInformationTable = instanceTransform.EnsureTable(this.core.TableDefinitions["_SummaryInformation"]); string targetPlatformAndLanguage = null; foreach (Row summaryInformationRow in targetSummaryInformationTable.Rows) { if (7 == (int)summaryInformationRow[0]) // PID_TEMPLATE { targetPlatformAndLanguage = (string)summaryInformationRow[1]; } // Copy the row's data to the transform. Row copyOfSummaryRow = instanceSummaryInformationTable.CreateRow(null); copyOfSummaryRow[0] = summaryInformationRow[0]; copyOfSummaryRow[1] = summaryInformationRow[1]; } // Modify the appropriate properties. Table propertyTable = instanceTransform.EnsureTable(this.core.TableDefinitions["Property"]); // Change the ProductCode property string productCode = (string)instanceRow[2]; if ("*" == productCode) { productCode = Common.GenerateGuid(); } Row productCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); productCodeRow.Operation = RowOperation.Modify; productCodeRow.Fields[1].Modified = true; productCodeRow[0] = "ProductCode"; productCodeRow[1] = productCode; // Change the instance property Row instanceIdRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); instanceIdRow.Operation = RowOperation.Modify; instanceIdRow.Fields[1].Modified = true; instanceIdRow[0] = (string)instanceRow[1]; instanceIdRow[1] = instanceId; if (null != instanceRow[3]) { // Change the ProductName property Row productNameRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); productNameRow.Operation = RowOperation.Modify; productNameRow.Fields[1].Modified = true; productNameRow[0] = "ProductName"; productNameRow[1] = (string)instanceRow[3]; } if (null != instanceRow[4]) { // Change the UpgradeCode property Row upgradeCodeRow = propertyTable.CreateRow(instanceRow.SourceLineNumbers); upgradeCodeRow.Operation = RowOperation.Modify; upgradeCodeRow.Fields[1].Modified = true; upgradeCodeRow[0] = "UpgradeCode"; upgradeCodeRow[1] = instanceRow[4]; // Change the Upgrade table Table targetUpgradeTable = output.Tables["Upgrade"]; if (null != targetUpgradeTable && 0 <= targetUpgradeTable.Rows.Count) { string upgradeId = (string)instanceRow[4]; Table upgradeTable = instanceTransform.EnsureTable(this.core.TableDefinitions["Upgrade"]); foreach (Row row in targetUpgradeTable.Rows) { // In case they are upgrading other codes to this new product, leave the ones that don't match the // Product.UpgradeCode intact. if (targetUpgradeCode == (string)row[0]) { Row upgradeRow = upgradeTable.CreateRow(null); upgradeRow.Operation = RowOperation.Add; upgradeRow.Fields[0].Modified = true; // I was hoping to be able to RowOperation.Modify, but that didn't appear to function. // upgradeRow.Fields[0].PreviousData = (string)row[0]; // Inserting a new Upgrade record with the updated UpgradeCode upgradeRow[0] = upgradeId; upgradeRow[1] = row[1]; upgradeRow[2] = row[2]; upgradeRow[3] = row[3]; upgradeRow[4] = row[4]; upgradeRow[5] = row[5]; upgradeRow[6] = row[6]; // Delete the old row Row upgradeRemoveRow = upgradeTable.CreateRow(null); upgradeRemoveRow.Operation = RowOperation.Delete; upgradeRemoveRow[0] = row[0]; upgradeRemoveRow[1] = row[1]; upgradeRemoveRow[2] = row[2]; upgradeRemoveRow[3] = row[3]; upgradeRemoveRow[4] = row[4]; upgradeRemoveRow[5] = row[5]; upgradeRemoveRow[6] = row[6]; } } } } // If there are instance Components generate new GUIDs for them. if (0 < instanceComponentGuids.Count) { Table componentTable = instanceTransform.EnsureTable(this.core.TableDefinitions["Component"]); foreach (ComponentRow targetComponentRow in instanceComponentGuids.Values) { string guid = targetComponentRow.Guid; if (!String.IsNullOrEmpty(guid)) { Row instanceComponentRow = componentTable.CreateRow(targetComponentRow.SourceLineNumbers); instanceComponentRow.Operation = RowOperation.Modify; instanceComponentRow.Fields[1].Modified = true; instanceComponentRow[0] = targetComponentRow[0]; instanceComponentRow[1] = Uuid.NewUuid(Binder.WixComponentGuidNamespace, String.Concat(guid, instanceId), this.backwardsCompatibleGuidGen).ToString("B").ToUpper(CultureInfo.InvariantCulture); instanceComponentRow[2] = targetComponentRow[2]; instanceComponentRow[3] = targetComponentRow[3]; instanceComponentRow[4] = targetComponentRow[4]; instanceComponentRow[5] = targetComponentRow[5]; } } } // Update the summary information Hashtable summaryRows = new Hashtable(instanceSummaryInformationTable.Rows.Count); foreach (Row row in instanceSummaryInformationTable.Rows) { summaryRows[row[0]] = row; if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) { row[1] = targetPlatformAndLanguage; } else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) { row[1] = String.Concat(targetProductCode, targetProductVersion, ';', productCode, targetProductVersion, ';', targetUpgradeCode); } else if ((int)SummaryInformation.Transform.ValidationFlags == (int)row[0]) { row[1] = 0; } else if ((int)SummaryInformation.Transform.Security == (int)row[0]) { row[1] = "4"; } } if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) { Row summaryRow = instanceSummaryInformationTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; summaryRow[1] = targetPlatformAndLanguage; } else if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) { Row summaryRow = instanceSummaryInformationTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; summaryRow[1] = "0"; } else if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) { Row summaryRow = instanceSummaryInformationTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.Security; summaryRow[1] = "4"; } output.SubStorages.Add(new SubStorage(instanceId, instanceTransform)); } } }
/// <summary> /// Creates a transform by diffing two outputs. /// </summary> /// <param name="targetOutput">The target output.</param> /// <param name="updatedOutput">The updated output.</param> /// <returns>The transform.</returns> public Output Diff(Output targetOutput, Output updatedOutput) { Output transform = new Output(null); transform.Type = OutputType.Transform; transform.Codepage = updatedOutput.Codepage; string targetProductCode = null; string targetProductVersion = null; string targetUpgradeCode = null; string updatedProductCode = null; string updatedProductVersion = null; // compare the codepages if (targetOutput.Codepage != updatedOutput.Codepage) { this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage)); if (null != updatedOutput.SourceLineNumbers) { this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers)); } } // compare the output types if (targetOutput.Type != updatedOutput.Type) { throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString())); } // compare the contents of the tables foreach (Table targetTable in targetOutput.Tables) { Table updatedTable = updatedOutput.Tables[targetTable.Name]; // dropped tables if (null == updatedTable) { Table droppedTable = transform.Tables.EnsureTable(null, targetTable.Definition); droppedTable.Operation = TableOperation.Drop; } else // possibly modified tables { SortedList updatedPrimaryKeys = new SortedList(); SortedList targetPrimaryKeys = new SortedList(); // TODO compare the table definitions - they must be identical for the type, size, primary keys, etc... if (targetTable.Definition.Columns.Count != updatedTable.Definition.Columns.Count) { throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Different numbers of columns for {0}.", targetTable.Name)); } // index the target rows foreach (Row row in targetTable.Rows) { string primaryKey = row.GetPrimaryKey('/'); if (null != primaryKey) { targetPrimaryKeys.Add(primaryKey, row); } else // use the string representation of the row as its primary key (it may not be unique) { // this is provided for compatibility with unreal tables with no primary key // all real tables must specify at least one column as the primary key targetPrimaryKeys[row.ToString()] = row; } if ("Property" == targetTable.Name) { if ("ProductCode" == (string)row[0]) { targetProductCode = (string)row[1]; } else if ("ProductVersion" == (string)row[0]) { targetProductVersion = (string)row[1]; } else if ("UpgradeCode" == (string)row[0]) { targetUpgradeCode = (string)row[1]; } } } // index the updated rows foreach (Row row in updatedTable.Rows) { string primaryKey = row.GetPrimaryKey('/'); if (null != primaryKey) { updatedPrimaryKeys.Add(primaryKey, row); } else // use the string representation of the row as its primary key (it may not be unique) { // this is provided for compatibility with unreal tables with no primary key // all real tables must specify at least one column as the primary key updatedPrimaryKeys[row.ToString()] = row; } if ("Property" == targetTable.Name) { if ("ProductCode" == (string)row[0]) { updatedProductCode = (string)row[1]; } else if ("ProductVersion" == (string)row[0]) { updatedProductVersion = (string)row[1]; } } } // diff the target and updated rows foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys) { string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key; Row targetRow = (Row)targetPrimaryKeyEntry.Value; Row updatedRow = (Row)updatedPrimaryKeys[targetPrimaryKey]; if (null == updatedRow) // deleted row { Table modifiedTable = transform.EnsureTable(targetTable.Definition); targetRow.Operation = RowOperation.Delete; targetRow.SectionId = targetRow.SectionId + sectionDelimiter; modifiedTable.Rows.Add(targetRow); } else // possibly modified { updatedRow.Operation = RowOperation.None; if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name) { Table table = transform.EnsureTable(updatedTable.Definition); updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; table.Rows.Add(updatedRow); } else { bool keepRow = false; if (this.preserveUnchangedRows) { keepRow = true; } for (int i = 0; i < updatedRow.Fields.Length; i++) { ColumnDefinition columnDefinition = updatedRow.Fields[i].Column; if (!columnDefinition.IsPrimaryKey) { bool modified = false; if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) { if (null == targetRow[i] ^ null == updatedRow[i]) { modified = true; } else if (null != targetRow[i] && null != updatedRow[i]) { modified = ((int)targetRow[i] != (int)updatedRow[i]); } } else if (ColumnType.Object == columnDefinition.Type) { ObjectField targetObjectField = (ObjectField)targetRow.Fields[i]; ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i]; if (null != targetObjectField.CabinetFileId) { // TODO: handle this } if ((string)targetObjectField.Data != (string)updatedObjectField.Data) { updatedObjectField.PreviousData = (string)targetObjectField.Data; } // keep rows containing object fields so the files can be compared in the binder keepRow = !this.suppressKeepingSpecialRows; } else { modified = ((string)targetRow[i] != (string)updatedRow[i]); } if (modified) { updatedRow.Fields[i].Modified = true; updatedRow.Operation = RowOperation.Modify; keepRow = true; } } } if (keepRow) { Table modifiedTable = transform.EnsureTable(updatedTable.Definition); updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId; modifiedTable.Rows.Add(updatedRow); } } } } // find the inserted rows foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys) { string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key; if (!targetPrimaryKeys.Contains(updatedPrimaryKey)) { Row updatedRow = (Row)updatedPrimaryKeyEntry.Value; Table modifiedTable = transform.EnsureTable(updatedTable.Definition); updatedRow.Operation = RowOperation.Add; updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; modifiedTable.Rows.Add(updatedRow); } } } } // added tables foreach (Table updatedTable in updatedOutput.Tables) { if (null == targetOutput.Tables[updatedTable.Name]) { Table addedTable = transform.Tables.EnsureTable(null, updatedTable.Definition); addedTable.Operation = TableOperation.Add; foreach (Row updatedRow in updatedTable.Rows) { updatedRow.Operation = RowOperation.Add; updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId; addedTable.Rows.Add(updatedRow); } } } // create the PID_REVNUMBER summary information property if (!this.suppressKeepingSpecialRows) { foreach (Row row in transform.Tables["_SummaryInformation"].Rows) { if (9 == (int)row[0]) { row[1] = String.Concat(targetProductCode, targetProductVersion, ';', updatedProductCode, updatedProductVersion, ';', targetUpgradeCode); } } } return(transform); }
private void GenerateBAManifestBundleTables(Output bundle, WixBundleRow bundleInfo) { Table wixBundlePropertiesTable = bundle.EnsureTable(this.core.TableDefinitions["WixBundleProperties"]); Row row = wixBundlePropertiesTable.CreateRow(bundleInfo.SourceLineNumbers); row[0] = bundleInfo.Name; row[1] = bundleInfo.LogPathVariable; row[2] = (YesNoDefaultType.Yes == bundleInfo.Compressed) ? "yes" : "no"; row[3] = bundleInfo.BundleId.ToString("B"); row[4] = bundleInfo.UpgradeCode; row[5] = bundleInfo.PerMachine ? "yes" : "no"; }
public Output BuildPairedTransform(string patchId, string clientPatchId, Output mainTransform, MediaRow mediaRow, int validationFlags, out string productCode) { productCode = null; Output pairedTransform = new Output(null); pairedTransform.Type = OutputType.Transform; pairedTransform.Codepage = mainTransform.Codepage; // lookup productVersion property to correct summaryInformation string newProductVersion = null; Table mainPropertyTable = mainTransform.Tables["Property"]; if (null != mainPropertyTable) { foreach (Row row in mainPropertyTable.Rows) { if ("ProductVersion" == (string)row[0]) { newProductVersion = (string)row[1]; } } } // TODO: build class for manipulating SummaryInformation table Table mainSummaryTable = mainTransform.Tables["_SummaryInformation"]; // add required properties Hashtable mainSummaryRows = new Hashtable(); foreach (Row mainSummaryRow in mainSummaryTable.Rows) { mainSummaryRows[mainSummaryRow[0]] = mainSummaryRow; } if (!mainSummaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) { Row mainSummaryRow = mainSummaryTable.CreateRow(null); mainSummaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; mainSummaryRow[1] = validationFlags.ToString(CultureInfo.InvariantCulture); } // copy summary information from core transform Table pairedSummaryTable = pairedTransform.EnsureTable(this.tableDefinitions["_SummaryInformation"]); foreach (Row mainSummaryRow in mainSummaryTable.Rows) { string value = (string)mainSummaryRow[1]; switch ((SummaryInformation.Transform)mainSummaryRow[0]) { case SummaryInformation.Transform.ProductCodes: string[] propertyData = value.Split(';'); string oldProductVersion = propertyData[0].Substring(38); string upgradeCode = propertyData[2]; productCode = propertyData[0].Substring(0, 38); if (newProductVersion == null) { newProductVersion = oldProductVersion; } // force mainTranform to old;new;upgrade and pairedTransform to new;new;upgrade mainSummaryRow[1] = String.Concat(productCode, oldProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); value = String.Concat(productCode, newProductVersion, ';', productCode, newProductVersion, ';', upgradeCode); break; case SummaryInformation.Transform.ValidationFlags: // use validation flags authored into the patch XML mainSummaryRow[1] = value = validationFlags.ToString(CultureInfo.InvariantCulture); break; } Row pairedSummaryRow = pairedSummaryTable.CreateRow(null); pairedSummaryRow[0] = mainSummaryRow[0]; pairedSummaryRow[1] = value; } if (productCode == null) { throw new InvalidOperationException(WixStrings.EXP_CouldnotDetermineProductCodeFromTransformSummaryInfo); } // copy File table Table mainFileTable = mainTransform.Tables["File"]; if (null != mainFileTable && 0 < mainFileTable.Rows.Count) { // We require file source information. Table mainWixFileTable = mainTransform.Tables["WixFile"]; if (null == mainWixFileTable) { throw new WixException(WixErrors.AdminImageRequired(productCode)); } FileRowCollection mainFileRows = new FileRowCollection(); mainFileRows.AddRange(mainFileTable.Rows); Table pairedFileTable = pairedTransform.EnsureTable(mainFileTable.Definition); foreach (WixFileRow mainWixFileRow in mainWixFileTable.Rows) { FileRow mainFileRow = mainFileRows[mainWixFileRow.File]; // set File.Sequence to non null to satisfy transform bind mainFileRow.Sequence = 1; // delete's don't need rows in the paired transform if (mainFileRow.Operation == RowOperation.Delete) { continue; } FileRow pairedFileRow = (FileRow)pairedFileTable.CreateRow(null); pairedFileRow.Operation = RowOperation.Modify; for (int i = 0; i < mainFileRow.Fields.Length; i++) { pairedFileRow[i] = mainFileRow[i]; } // override authored media for patch bind // TODO: consider using File/@DiskId for patch media mainFileRow.DiskId = mediaRow.DiskId; mainWixFileRow.DiskId = mediaRow.DiskId; // suppress any change to File.Sequence to avoid bloat mainFileRow.Fields[7].Modified = false; // force File row to appear in the transform switch (mainFileRow.Operation) { case RowOperation.Modify: case RowOperation.Add: // set msidbFileAttributesPatchAdded pairedFileRow.Attributes |= MsiInterop.MsidbFileAttributesPatchAdded; pairedFileRow.Fields[6].Modified = true; pairedFileRow.Operation = mainFileRow.Operation; break; default: pairedFileRow.Fields[6].Modified = false; break; } } } // add Media row to pairedTransform Table pairedMediaTable = pairedTransform.EnsureTable(this.tableDefinitions["Media"]); Row pairedMediaRow = pairedMediaTable.CreateRow(null); pairedMediaRow.Operation = RowOperation.Add; for (int i = 0; i < mediaRow.Fields.Length; i++) { pairedMediaRow[i] = mediaRow[i]; } // add PatchPackage for this Media Table pairedPackageTable = pairedTransform.EnsureTable(this.tableDefinitions["PatchPackage"]); pairedPackageTable.Operation = TableOperation.Add; Row pairedPackageRow = pairedPackageTable.CreateRow(null); pairedPackageRow.Operation = RowOperation.Add; pairedPackageRow[0] = patchId; pairedPackageRow[1] = mediaRow.DiskId; // add property to both identify client patches and whether those patches are removable or not int allowRemoval = 0; Table msiPatchMetadataTable = this.patch.Tables["MsiPatchMetadata"]; if (null != msiPatchMetadataTable) { foreach (Row msiPatchMetadataRow in msiPatchMetadataTable.Rows) { // get the value of the standard AllowRemoval property, if present string company = (string)msiPatchMetadataRow[0]; if ((null == company || 0 == company.Length) && "AllowRemoval" == (string)msiPatchMetadataRow[1]) { allowRemoval = Int32.Parse((string)msiPatchMetadataRow[2], CultureInfo.InvariantCulture); } } } // add the property to the patch transform's Property table Table pairedPropertyTable = pairedTransform.EnsureTable(this.tableDefinitions["Property"]); pairedPropertyTable.Operation = TableOperation.Add; Row pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = string.Concat(clientPatchId, ".AllowRemoval"); pairedPropertyRow[1] = allowRemoval.ToString(CultureInfo.InvariantCulture); // add this patch code GUID to the patch transform to identify // which patches are installed, including in multi-patch // installations. pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = string.Concat(clientPatchId, ".PatchCode"); pairedPropertyRow[1] = patchId; // add PATCHNEWPACKAGECODE to apply to admin layouts pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = "PATCHNEWPACKAGECODE"; pairedPropertyRow[1] = patchId; // add PATCHNEWSUMMARYCOMMENTS and PATCHNEWSUMMARYSUBJECT to apply to admin layouts Table _summaryInformationTable = this.patch.Tables["_SummaryInformation"]; if (null != _summaryInformationTable) { foreach (Row row in _summaryInformationTable.Rows) { if (3 == (int)row[0]) // PID_SUBJECT { pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = "PATCHNEWSUMMARYSUBJECT"; pairedPropertyRow[1] = row[1]; } else if (6 == (int)row[0]) // PID_COMMENTS { pairedPropertyRow = pairedPropertyTable.CreateRow(null); pairedPropertyRow.Operation = RowOperation.Add; pairedPropertyRow[0] = "PATCHNEWSUMMARYCOMMENTS"; pairedPropertyRow[1] = row[1]; } } } return pairedTransform; }
/// <summary> /// Include transforms in a patch. /// </summary> /// <param name="transforms">List of transforms to attach.</param> public void AttachTransforms(ArrayList transforms) { int emptyTransform = 0; if (transforms == null || transforms.Count == 0) { throw new WixException(WixErrors.PatchWithoutTransforms()); } // Get the patch id from the WixPatchId table. string patchId = null; Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) { Row patchIdRow = wixPatchIdTable.Rows[0]; if (null != patchIdRow) { patchId = patchIdRow[0].ToString(); } } // enumerate patch.Media to map diskId to Media row Hashtable mediaRows = new Hashtable(); Table patchMediaTable = patch.Tables["Media"]; if (patchMediaTable != null) { foreach (MediaRow row in patchMediaTable.Rows) { int media = row.DiskId; mediaRows[media] = row; } } // enumerate patch.WixPatchBaseline to map baseline to diskId Hashtable baselineMedia = new Hashtable(); Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; if (patchBaselineTable != null) { foreach (Row row in patchBaselineTable.Rows) { string baseline = (string)row[0]; int media = (int)row[1]; if (baselineMedia.Contains(baseline)) { throw new InvalidOperationException(String.Format("PatchBaseline '{0}' authored into multiple Media.", baseline)); } baselineMedia[baseline] = media; } } // enumerate transforms ArrayList productCodes = new ArrayList(); ArrayList transformNames = new ArrayList(); int transformCount = 0; foreach (PatchTransform mainTransform in transforms) { string baseline = null; int media = -1; if (baselineMedia.Contains(mainTransform.Baseline)) { int newMedia = (int)baselineMedia[mainTransform.Baseline]; if (media != -1 && media != newMedia) { throw new InvalidOperationException(String.Format("Transform authored into multiple Media '{0}' and '{1}'.", media, newMedia)); } baseline = mainTransform.Baseline; media = newMedia; } if (media == -1) { // transform's baseline not attached to any Media continue; } Table patchRefTable = patch.Tables["WixPatchRef"]; if (patchRefTable != null && patchRefTable.Rows.Count > 0) { if (!this.ReduceTransform(mainTransform.Transform, patchRefTable)) { // transform has none of the content authored into this patch emptyTransform++; continue; } } // ensure consistent File.Sequence within each Media MediaRow mediaRow = (MediaRow)mediaRows[media]; // TODO: should this be authored rather than inferring it from DiskId? mediaRow.LastSequence = mediaRow.DiskId; // ignore media table from transform. mainTransform.Transform.Tables.Remove("Media"); mainTransform.Transform.Tables.Remove("WixMedia"); mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); string productCode = null; Output pairedTransform = this.BuildPairedTransform(patchId, mainTransform.Transform, mediaRow, ref productCode); productCodes.Add(productCode); // attach these transforms to the patch object // TODO: is this an acceptable way to auto-generate transform stream names? string transformName = baseline + "." + (++transformCount).ToString(); patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); transformNames.Add(":" + transformName); transformNames.Add(":#" + transformName); } if (emptyTransform == transforms.Count) { throw new WixException(WixErrors.PatchWithoutValidTransforms()); } // populate MSP summary information Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); // remove any existing data for these fields for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) { Row row = patchSummaryInfo.Rows[i]; switch ((SummaryInformation.Patch)row[0]) { case SummaryInformation.Patch.ProductCodes: case SummaryInformation.Patch.TransformNames: case SummaryInformation.Patch.PatchCode: case SummaryInformation.Patch.InstallerRequirement: patchSummaryInfo.Rows.RemoveAt(i); break; } } // Semicolon delimited list of the product codes that can accept the patch. Row templateRow = patchSummaryInfo.CreateRow(null); templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; templateRow[1] = String.Join(";", (string[])productCodes.ToArray(typeof(string))); // Semicolon delimited list of transform substorage names in the order they are applied. Row savedbyRow = patchSummaryInfo.CreateRow(null); savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); // GUID patch code for the patch. Row revisionRow = patchSummaryInfo.CreateRow(null); revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; revisionRow[1] = patchId; // Indicates the minimum Windows Installer version that is required to install the patch. Row wordsRow = patchSummaryInfo.CreateRow(null); wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(); Row security = patchSummaryInfo.CreateRow(null); security[0] = 19; //PID_SECURITY security[1] = "4"; Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; if (null != msiPatchMetadataTable) { Hashtable metadataTable = new Hashtable(); foreach (Row row in msiPatchMetadataTable.Rows) { metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); } if (metadataTable.Contains("DisplayName")) { string comment = String.Concat("This patch contains the logic and data required to install ", metadataTable["DisplayName"]); Row title = patchSummaryInfo.CreateRow(null); title[0] = 2; //PID_TITLE title[1] = metadataTable["DisplayName"]; Row comments = patchSummaryInfo.CreateRow(null); comments[0] = 6; //PID_COMMENTS comments[1] = comment; } if (metadataTable.Contains("CodePage")) { Row codePage = patchSummaryInfo.CreateRow(null); codePage[0] = 1; //PID_CODEPAGE codePage[1] = metadataTable["CodePage"]; } if (metadataTable.Contains("Description")) { Row subject = patchSummaryInfo.CreateRow(null); subject[0] = 3; //PID_SUBJECT subject[1] = metadataTable["Description"]; } if (metadataTable.Contains("ManufacturerName")) { Row author = patchSummaryInfo.CreateRow(null); author[0] = 4; //PID_AUTHOR author[1] = metadataTable["ManufacturerName"]; } } }