/// <summary> /// Merges in any modules to the output database. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="output">Output that specifies database and modules to merge.</param> /// <param name="fileRows">The indexed file rows.</param> /// <param name="suppressedTableNames">The names of tables that are suppressed.</param> /// <remarks>Expects that output's database has already been generated.</remarks> private void MergeModules(string tempDatabaseFile, Output output, FileRowCollection fileRows, StringCollection suppressedTableNames) { Debug.Assert(OutputType.Product == output.Type); Table wixMergeTable = output.Tables["WixMerge"]; Table wixFeatureModulesTable = output.Tables["WixFeatureModules"]; // check for merge rows to see if there is any work to do if (null == wixMergeTable || 0 == wixMergeTable.Rows.Count) { return; } IMsmMerge2 merge = null; bool commit = true; bool logOpen = false; bool databaseOpen = false; string logPath = null; try { merge = NativeMethods.GetMsmMerge(); logPath = Path.Combine(this.TempFilesLocation, "merge.log"); merge.OpenLog(logPath); logOpen = true; merge.OpenDatabase(tempDatabaseFile); databaseOpen = true; // process all the merge rows foreach (WixMergeRow wixMergeRow in wixMergeTable.Rows) { bool moduleOpen = false; try { short mergeLanguage; try { mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); } catch (System.FormatException) { this.core.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); continue; } this.core.OnMessage(WixVerboses.OpeningMergeModule(wixMergeRow.SourceFile, mergeLanguage)); merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); moduleOpen = true; // If there is merge configuration data, create a callback object to contain it all. ConfigurationCallback callback = null; if (!String.IsNullOrEmpty(wixMergeRow.ConfigurationData)) { callback = new ConfigurationCallback(wixMergeRow.ConfigurationData); } // merge the module into the database that's being built this.core.OnMessage(WixVerboses.MergingMergeModule(wixMergeRow.SourceFile)); merge.MergeEx(wixMergeRow.Feature, wixMergeRow.Directory, callback); // connect any non-primary features if (null != wixFeatureModulesTable) { foreach (Row row in wixFeatureModulesTable.Rows) { if (wixMergeRow.Id == (string)row[1]) { this.core.OnMessage(WixVerboses.ConnectingMergeModule(wixMergeRow.SourceFile, (string)row[0])); merge.Connect((string)row[0]); } } } } catch (COMException) { commit = false; } finally { IMsmErrors mergeErrors = merge.Errors; // display all the errors encountered during the merge operations for this module for (int i = 1; i <= mergeErrors.Count; i++) { IMsmError mergeError = mergeErrors[i]; StringBuilder databaseKeys = new StringBuilder(); StringBuilder moduleKeys = new StringBuilder(); // build a string of the database keys for (int j = 1; j <= mergeError.DatabaseKeys.Count; j++) { if (1 != j) { databaseKeys.Append(';'); } databaseKeys.Append(mergeError.DatabaseKeys[j]); } // build a string of the module keys for (int j = 1; j <= mergeError.ModuleKeys.Count; j++) { if (1 != j) { moduleKeys.Append(';'); } moduleKeys.Append(mergeError.ModuleKeys[j]); } // display the merge error based on the msm error type switch (mergeError.Type) { case MsmErrorType.msmErrorExclusion: this.core.OnMessage(WixErrors.MergeExcludedModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleKeys.ToString())); break; case MsmErrorType.msmErrorFeatureRequired: this.core.OnMessage(WixErrors.MergeFeatureRequired(wixMergeRow.SourceLineNumbers, mergeError.ModuleTable, moduleKeys.ToString(), wixMergeRow.SourceFile, wixMergeRow.Id)); break; case MsmErrorType.msmErrorLanguageFailed: this.core.OnMessage(WixErrors.MergeLanguageFailed(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorLanguageUnsupported: this.core.OnMessage(WixErrors.MergeLanguageUnsupported(wixMergeRow.SourceLineNumbers, mergeError.Language, wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorResequenceMerge: this.core.OnMessage(WixWarnings.MergeRescheduledAction(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); break; case MsmErrorType.msmErrorTableMerge: if ("_Validation" != mergeError.DatabaseTable) // ignore merge errors in the _Validation table { this.core.OnMessage(WixWarnings.MergeTableFailed(wixMergeRow.SourceLineNumbers, mergeError.DatabaseTable, databaseKeys.ToString(), wixMergeRow.SourceFile)); } break; case MsmErrorType.msmErrorPlatformMismatch: this.core.OnMessage(WixErrors.MergePlatformMismatch(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); break; default: this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorWithType, Enum.GetName(typeof(MsmErrorType), mergeError.Type), logPath), "InvalidOperationException", Environment.StackTrace)); break; } } if (0 >= mergeErrors.Count && !commit) { this.core.OnMessage(WixErrors.UnexpectedException(String.Format(CultureInfo.CurrentUICulture, WixStrings.EXP_UnexpectedMergerErrorInSourceFile, wixMergeRow.SourceFile, logPath), "InvalidOperationException", Environment.StackTrace)); } if (moduleOpen) { merge.CloseModule(); } } } } finally { if (databaseOpen) { merge.CloseDatabase(commit); } if (logOpen) { merge.CloseLog(); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return; } using (Database db = new Database(tempDatabaseFile, OpenDatabase.Direct)) { Table suppressActionTable = output.Tables["WixSuppressAction"]; // suppress individual actions if (null != suppressActionTable) { foreach (Row row in suppressActionTable.Rows) { if (db.TableExists((string)row[0])) { string query = String.Format(CultureInfo.InvariantCulture, "SELECT * FROM {0} WHERE `Action` = '{1}'", row[0].ToString(), (string)row[1]); using (View view = db.OpenExecuteView(query)) { using (Record record = view.Fetch()) { if (null != record) { this.core.OnMessage(WixWarnings.SuppressMergedAction((string)row[1], row[0].ToString())); view.Modify(ModifyView.Delete, record); } } } } } } // query for merge module actions in suppressed sequences and drop them foreach (string tableName in suppressedTableNames) { if (!db.TableExists(tableName)) { continue; } using (View view = db.OpenExecuteView(String.Concat("SELECT `Action` FROM ", tableName))) { while (true) { using (Record resultRecord = view.Fetch()) { if (null == resultRecord) { break; } this.core.OnMessage(WixWarnings.SuppressMergedAction(resultRecord.GetString(1), tableName)); } } } // drop suppressed sequences using (View view = db.OpenExecuteView(String.Concat("DROP TABLE ", tableName))) { } // delete the validation rows using (View view = db.OpenView(String.Concat("DELETE FROM _Validation WHERE `Table` = ?"))) { using (Record record = new Record(1)) { record.SetString(1, tableName); view.Execute(record); } } } // now update the Attributes column for the files from the Merge Modules this.core.OnMessage(WixVerboses.ResequencingMergeModuleFiles()); using (View view = db.OpenView("SELECT `Sequence`, `Attributes` FROM `File` WHERE `File`=?")) { foreach (FileRow fileRow in fileRows) { if (!fileRow.FromModule) { continue; } using (Record record = new Record(1)) { record.SetString(1, fileRow.File); view.Execute(record); } using (Record recordUpdate = view.Fetch()) { if (null == recordUpdate) { throw new InvalidOperationException("Failed to fetch a File row from the database that was merged in from a module."); } recordUpdate.SetInteger(1, fileRow.Sequence); // update the file attributes to match the compression specified // on the Merge element or on the Package element int attributes = 0; // get the current value if its not null if (!recordUpdate.IsNull(2)) { attributes = recordUpdate.GetInteger(2); } if (YesNoType.Yes == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } else if (YesNoType.No == fileRow.Compressed) { // these are mutually exclusive attributes |= MsiInterop.MsidbFileAttributesNoncompressed; attributes &= ~MsiInterop.MsidbFileAttributesCompressed; } else // not specified { Debug.Assert(YesNoType.NotSet == fileRow.Compressed); // clear any compression bits attributes &= ~MsiInterop.MsidbFileAttributesCompressed; attributes &= ~MsiInterop.MsidbFileAttributesNoncompressed; } recordUpdate.SetInteger(2, attributes); view.Modify(ModifyView.Update, recordUpdate); } } } db.Commit(); } }
/// <summary> /// Assigns the file rows to media rows based on Media or MediaTemplate authoring. Updates uncompressed files collection. /// </summary> /// <param name="fileRows">FileRowCollection</param> public void AssignFiles(FileRowCollection fileRows) { bool autoAssign = false; MediaRow mergeModuleMediaRow = null; Table mediaTable = this.output.Tables["Media"]; Table mediaTemplateTable = this.output.Tables["WixMediaTemplate"]; // If both tables are authored, it is an error. if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) && (mediaTable != null && mediaTable.Rows.Count > 1)) { throw new WixException(WixErrors.MediaTableCollision(null)); } autoAssign = mediaTemplateTable != null && OutputType.Module != this.output.Type ? true : false; // When building merge module, all the files go to "#MergeModule.CABinet" if (OutputType.Module == this.output.Type) { Table mergeModuleMediaTable = new Table(null, this.core.TableDefinitions["Media"]); mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; this.cabinets.Add(mergeModuleMediaRow, new FileRowCollection()); } if (autoAssign) { this.AutoAssignFiles(mediaTable, fileRows); } else { this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, fileRows); } }
/// <summary> /// Instantiate a new CabinetWorkItem. /// </summary> /// <param name="fileRows">The collection of files in this cabinet.</param> /// <param name="cabinetFile">The cabinet file.</param> /// <param name="maxThreshold">Maximum threshold for each cabinet.</param> /// <param name="compressionLevel">The compression level of the cabinet.</param> /// <param name="binderFileManager">The binder file manager.</param> public CabinetWorkItem(FileRowCollection fileRows, string cabinetFile, int maxThreshold, Cab.CompressionLevel compressionLevel, BinderFileManager binderFileManager) { this.cabinetFile = cabinetFile; this.compressionLevel = compressionLevel; this.fileRows = fileRows; this.binderFileManager = binderFileManager; this.maxThreshold = maxThreshold; }
/// <summary> /// Constructor for auto media assigner. /// </summary> /// <param name="output">Output</param> /// <param name="core">Binder core.</param> /// <param name="filesCompressed">True if files are compressed by default. </param> public AutoMediaAssigner(Output output, BinderCore core, bool filesCompressed) { this.output = output; this.core = core; this.filesCompressed = filesCompressed; this.cabinetNameTemplate = "Cab{0}.cab"; uncompressedFileRows = new FileRowCollection(); mediaRows = new MediaRowCollection(); cabinets = new Hashtable(); }
/// <summary> /// Process uncompressed files. /// </summary> /// <param name="tempDatabaseFile">The temporary database file.</param> /// <param name="fileRows">The collection of files to copy into the image.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> /// <param name="mediaRows">The indexed media rows.</param> /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> /// <param name="compressed">Flag if source image should be compressed.</param> /// <param name="longNamesInImage">Flag if long names should be used.</param> private void ProcessUncompressedFiles(string tempDatabaseFile, FileRowCollection fileRows, ArrayList fileTransfers, MediaRowCollection mediaRows, string layoutDirectory, bool compressed, bool longNamesInImage) { if (0 == fileRows.Count || this.core.EncounteredError) { return; } Hashtable directories = new Hashtable(); using (Database db = new Database(tempDatabaseFile, OpenDatabase.ReadOnly)) { using (View directoryView = db.OpenExecuteView("SELECT `Directory`, `Directory_Parent`, `DefaultDir` FROM `Directory`")) { while (true) { using (Record directoryRecord = directoryView.Fetch()) { if (null == directoryRecord) { break; } string sourceName = Installer.GetName(directoryRecord.GetString(3), true, longNamesInImage); directories.Add(directoryRecord.GetString(1), new ResolvedDirectory(directoryRecord.GetString(2), sourceName)); } } } using (View fileView = db.OpenView("SELECT `Directory_`, `FileName` FROM `Component`, `File` WHERE `Component`.`Component`=`File`.`Component_` AND `File`.`File`=?")) { using (Record fileQueryRecord = new Record(1)) { // for each file in the array of uncompressed files foreach (FileRow fileRow in fileRows) { string relativeFileLayoutPath = null; string mediaLayoutDirectory = this.FileManager.ResolveMedia(mediaRows[fileRow.DiskId], layoutDirectory); // setup up the query record and find the appropriate file in the // previously executed file view fileQueryRecord[1] = fileRow.File; fileView.Execute(fileQueryRecord); using (Record fileRecord = fileView.Fetch()) { if (null == fileRecord) { throw new WixException(WixErrors.FileIdentifierNotFound(fileRow.SourceLineNumbers, fileRow.File)); } relativeFileLayoutPath = Binder.GetFileSourcePath(directories, fileRecord[1], fileRecord[2], compressed, longNamesInImage); } // finally put together the base media layout path and the relative file layout path string fileLayoutPath = Path.Combine(mediaLayoutDirectory, relativeFileLayoutPath); FileTransfer transfer; if (FileTransfer.TryCreate(fileRow.Source, fileLayoutPath, false, "File", fileRow.SourceLineNumbers, out transfer)) { fileTransfers.Add(transfer); } } } } } }
/// <summary> /// Retrieve files and their information from merge modules. /// </summary> /// <param name="output">Internal representation of the msi database to operate upon.</param> /// <param name="fileRows">The indexed file rows.</param> private void ProcessMergeModules(Output output, FileRowCollection fileRows) { Table wixMergeTable = output.Tables["WixMerge"]; if (null != wixMergeTable) { IMsmMerge2 merge = NativeMethods.GetMsmMerge(); // 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; } } } foreach (Row row in wixMergeTable.Rows) { bool containsFiles = false; WixMergeRow wixMergeRow = (WixMergeRow)row; try { // read the module's File table to get its FileMediaInformation entries and gather any other information needed from the module. using (Database db = new Database(wixMergeRow.SourceFile, OpenDatabase.ReadOnly)) { if (db.TableExists("File") && db.TableExists("Component")) { Hashtable uniqueModuleFileIdentifiers = System.Collections.Specialized.CollectionsUtil.CreateCaseInsensitiveHashtable(); using (View view = db.OpenExecuteView("SELECT `File`, `Directory_` FROM `File`, `Component` WHERE `Component_`=`Component`")) { // add each file row from the merge module into the file row collection (check for errors along the way) while (true) { using (Record record = view.Fetch()) { if (null == record) { break; } // NOTE: this is very tricky - the merge module file rows are not added to the // file table because they should not be created via idt import. Instead, these // rows are created by merging in the actual modules FileRow fileRow = new FileRow(null, this.core.TableDefinitions["File"]); fileRow.File = record[1]; fileRow.Compressed = wixMergeRow.FileCompression; fileRow.Directory = record[2]; fileRow.DiskId = wixMergeRow.DiskId; fileRow.FromModule = true; fileRow.PatchGroup = -1; fileRow.Source = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat), Path.DirectorySeparatorChar, record[1]); FileRow collidingFileRow = fileRows[fileRow.File]; FileRow collidingModuleFileRow = (FileRow)uniqueModuleFileIdentifiers[fileRow.File]; if (null == collidingFileRow && null == collidingModuleFileRow) { fileRows.Add(fileRow); // keep track of file identifiers in this merge module uniqueModuleFileIdentifiers.Add(fileRow.File, fileRow); } else // collision(s) detected { // case-sensitive collision with another merge module or a user-authored file identifier if (null != collidingFileRow) { this.core.OnMessage(WixErrors.DuplicateModuleFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, collidingFileRow.File)); } // case-insensitive collision with another file identifier in the same merge module if (null != collidingModuleFileRow) { this.core.OnMessage(WixErrors.DuplicateModuleCaseInsensitiveFileIdentifier(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, fileRow.File, collidingModuleFileRow.File)); } } containsFiles = true; } } } } // Get the summary information to detect the Schema using (SummaryInformation summaryInformation = new SummaryInformation(db)) { string moduleInstallerVersionString = summaryInformation.GetProperty(14); try { int moduleInstallerVersion = Convert.ToInt32(moduleInstallerVersionString, CultureInfo.InvariantCulture); if (moduleInstallerVersion > outputInstallerVersion) { this.core.OnMessage(WixWarnings.InvalidHigherInstallerVersionInModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, moduleInstallerVersion, outputInstallerVersion)); } } catch (FormatException) { throw new WixException(WixErrors.MissingOrInvalidModuleInstallerVersion(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile, moduleInstallerVersionString)); } } } } catch (FileNotFoundException) { throw new WixException(WixErrors.FileNotFound(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile)); } catch (Win32Exception) { throw new WixException(WixErrors.CannotOpenMergeModule(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.SourceFile)); } // if the module has files and creating layout if (containsFiles && !this.suppressLayout) { bool moduleOpen = false; short mergeLanguage; try { mergeLanguage = Convert.ToInt16(wixMergeRow.Language, CultureInfo.InvariantCulture); } catch (System.FormatException) { this.core.OnMessage(WixErrors.InvalidMergeLanguage(wixMergeRow.SourceLineNumbers, wixMergeRow.Id, wixMergeRow.Language)); continue; } try { merge.OpenModule(wixMergeRow.SourceFile, mergeLanguage); moduleOpen = true; string safeMergeId = wixMergeRow.Number.ToString(CultureInfo.InvariantCulture.NumberFormat); // extract the module cabinet, then explode all of the files to a temp directory string moduleCabPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, safeMergeId, ".module.cab"); merge.ExtractCAB(moduleCabPath); string mergeIdPath = String.Concat(this.TempFilesLocation, Path.DirectorySeparatorChar, "MergeId.", safeMergeId); Directory.CreateDirectory(mergeIdPath); using (WixExtractCab extractCab = new WixExtractCab()) { try { extractCab.Extract(moduleCabPath, mergeIdPath); } catch (FileNotFoundException) { throw new WixException(WixErrors.CabFileDoesNotExist(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); } catch { throw new WixException(WixErrors.CabExtractionFailed(moduleCabPath, wixMergeRow.SourceFile, mergeIdPath)); } } } catch (COMException ce) { throw new WixException(WixErrors.UnableToOpenModule(wixMergeRow.SourceLineNumbers, wixMergeRow.SourceFile, ce.Message)); } finally { if (moduleOpen) { merge.CloseModule(); } } } } } }
/// <summary> /// Instantiate a new CabinetWorkItem. /// </summary> /// <param name="fileRows">The collection of files in this cabinet.</param> /// <param name="cabinetFile">The cabinet file.</param> /// <param name="compressionLevel">The compression level of the cabinet.</param> public CabinetWorkItem(FileRowCollection fileRows, string cabinetFile, Cab.CompressionLevel compressionLevel) { this.cabinetFile = cabinetFile; this.compressionLevel = compressionLevel; this.fileRows = fileRows; }
/// <summary> /// Writes the paths to the content files included in the package to a text file. /// </summary> /// <param name="path">Path to write file.</param> /// <param name="fileRows">Collection of file rows whose source will be written to file.</param> private void CreateContentsFile(string path, FileRowCollection fileRows) { string directory = Path.GetDirectoryName(path); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } using (StreamWriter contents = new StreamWriter(path, false)) { foreach (FileRow fileRow in fileRows) { contents.WriteLine(fileRow.Source); } } }
/// <summary> /// Assign files to cabinets based on Media authoring. /// </summary> /// <param name="mediaTable"></param> /// <param name="mergeModuleMediaRow"></param> /// <param name="fileRows"></param> private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, FileRowCollection fileRows) { if (OutputType.Module != this.output.Type) { if (null != mediaTable) { Dictionary<string, MediaRow> cabinetMediaRows = new Dictionary<string, MediaRow>(StringComparer.InvariantCultureIgnoreCase); foreach (MediaRow mediaRow in mediaTable.Rows) { // If the Media row has a cabinet, make sure it is unique across all Media rows. if (!String.IsNullOrEmpty(mediaRow.Cabinet)) { MediaRow existingRow; if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) { this.core.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); this.core.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); } else { cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); } } this.mediaRows.Add(mediaRow); } } foreach (MediaRow mediaRow in this.mediaRows) { if (null != mediaRow.Cabinet) { this.cabinets.Add(mediaRow, new FileRowCollection()); } } } foreach (FileRow fileRow in fileRows) { if (OutputType.Module == output.Type) { ((FileRowCollection)this.cabinets[mergeModuleMediaRow]).Add(fileRow); } else { MediaRow mediaRow = this.mediaRows[fileRow.DiskId]; // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == output.Type && (YesNoType.No == fileRow.Compressed || (YesNoType.NotSet == fileRow.Compressed && !this.filesCompressed))) { uncompressedFileRows.Add(fileRow); } else // file in a Module or marked compressed { FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[mediaRow]; if (null != cabinetFileRow) { cabinetFileRow.Add(fileRow); } else { this.core.OnMessage(WixErrors.ExpectedMediaCabinet(fileRow.SourceLineNumbers, fileRow.File, fileRow.DiskId)); } } } } }
/// <summary> /// Assign files to cabinets based on MediaTemplate authoring. /// </summary> /// <param name="fileRows">FileRowCollection</param> private void AutoAssignFiles(Table mediaTable, FileRowCollection fileRows) { const int MaxCabIndex = 999; ulong currentPreCabSize = 0; ulong maxPreCabSizeInBytes; int maxPreCabSizeInMB = 0; int currentCabIndex = 0; MediaRow currentMediaRow = null; Table mediaTemplateTable = this.output.Tables["WixMediaTemplate"]; // Auto assign files to cabinets based on maximum uncompressed media size mediaTable.Rows.Clear(); WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) { this.cabinetNameTemplate = mediaTemplateRow.CabinetTemplate; } string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); try { // Override authored mums value if environment variable is authored. if (!String.IsNullOrEmpty(mumsString)) { maxPreCabSizeInMB = Int32.Parse(mumsString); } else { maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; } maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; } catch (FormatException) { throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); } catch (OverflowException) { throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); } foreach (FileRow fileRow in fileRows) { // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == output.Type && (YesNoType.No == fileRow.Compressed || (YesNoType.NotSet == fileRow.Compressed && !this.filesCompressed))) { uncompressedFileRows.Add(fileRow); continue; } FileInfo fileInfo = null; // Get the file size try { fileInfo = new FileInfo(fileRow.Source); } catch (ArgumentException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } catch (PathTooLongException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } catch (NotSupportedException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } if (fileInfo.Exists) { if (fileInfo.Length > Int32.MaxValue) { throw new WixException(WixErrors.FileTooLarge(fileRow.SourceLineNumbers, fileRow.Source)); } fileRow.FileSize = Convert.ToInt32(fileInfo.Length, CultureInfo.InvariantCulture); } if (currentCabIndex == MaxCabIndex) { // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); continue; } // Update current cab size. currentPreCabSize += (ulong)fileRow.FileSize; if (currentPreCabSize > maxPreCabSizeInBytes) { // Overflow due to current file currentMediaRow = this.AddMediaRow(mediaTable, ++currentCabIndex, mediaTemplateRow.CompressionLevel); FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); // Now files larger than MaxUncompressedMediaSize will be the only file in its cabinet so as to respect MaxUncompressedMediaSize currentPreCabSize = (ulong)fileRow.FileSize; } else { // File fits in the current cab. if (currentMediaRow == null) { // Create new cab and MediaRow currentMediaRow = this.AddMediaRow(mediaTable, ++currentCabIndex, mediaTemplateRow.CompressionLevel); } // Associate current file with current cab. FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); } } // If there are uncompressed files and no MediaRow, create a default one. if (uncompressedFileRows.Count > 0 && mediaTable.Rows.Count == 0) { MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); defaultMediaRow.DiskId = 1; mediaRows.Add(defaultMediaRow); } }
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; }
public virtual CabinetBuildOption ResolveCabinet(FileRowCollection fileRows, ref string cabinetPath) { if (fileRows == null) { throw new ArgumentNullException("fileRows"); } // no special behavior specified, use the default if (null == this.cabCachePath && !this.reuseCabinets) { return(CabinetBuildOption.BuildAndMove); } // if a cabinet cache path was provided, change the location for the cabinet // to be built to if (null != this.cabCachePath) { string cabinetName = Path.GetFileName(cabinetPath); cabinetPath = Path.Combine(this.cabCachePath, cabinetName); } // if we still think we're going to reuse the cabinet check to see if the cabinet exists first if (this.reuseCabinets) { bool cabinetValid = false; if (BinderFileManager.CheckFileExists(cabinetPath)) { // check to see if // 1. any files are added or removed // 2. order of files changed or names changed // 3. modified time changed cabinetValid = true; // Need to force garbage collection of WixEnumerateCab to ensure the handle // associated with it is closed before it is reused. using (Cab.WixEnumerateCab wixEnumerateCab = new Cab.WixEnumerateCab()) { ArrayList fileList = wixEnumerateCab.Enumerate(cabinetPath); if (fileRows.Count != fileList.Count) { cabinetValid = false; } else { int i = 0; foreach (FileRow fileRow in fileRows) { // First check that the file identifiers match because that is quick and easy. CabinetFileInfo cabFileInfo = fileList[i] as CabinetFileInfo; cabinetValid = (cabFileInfo.FileId == fileRow.File); if (cabinetValid) { // Still valid so ensure the source time stamp hasn't changed. Thus we need // to convert the source file time stamp into a cabinet compatible data/time. DateTime sourceFileTime = File.GetLastWriteTime(fileRow.Source); ushort sourceCabDate; ushort sourceCabTime; Cab.Interop.CabInterop.DateTimeToCabDateAndTime(sourceFileTime, out sourceCabDate, out sourceCabTime); cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime); } if (!cabinetValid) { break; } i++; } } } } return(cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy); } else // by default move the built cabinet { return(CabinetBuildOption.BuildAndMove); } }
public virtual CabinetBuildOption ResolveCabinet(FileRowCollection fileRows, ref string cabinetPath) { if (fileRows == null) { throw new ArgumentNullException("fileRows"); } // no special behavior specified, use the default if (null == this.cabCachePath && !this.reuseCabinets) { return CabinetBuildOption.BuildAndMove; } // if a cabinet cache path was provided, change the location for the cabinet // to be built to if (null != this.cabCachePath) { string cabinetName = Path.GetFileName(cabinetPath); cabinetPath = Path.Combine(this.cabCachePath, cabinetName); } // if we still think we're going to reuse the cabinet check to see if the cabinet exists first if (this.reuseCabinets) { bool cabinetValid = false; if (BinderFileManager.CheckFileExists(cabinetPath)) { // check to see if // 1. any files are added or removed // 2. order of files changed or names changed // 3. modified time changed cabinetValid = true; // Need to force garbage collection of WixEnumerateCab to ensure the handle // associated with it is closed before it is reused. using (Cab.WixEnumerateCab wixEnumerateCab = new Cab.WixEnumerateCab()) { ArrayList fileList = wixEnumerateCab.Enumerate(cabinetPath); if (fileRows.Count != fileList.Count) { cabinetValid = false; } else { int i = 0; foreach (FileRow fileRow in fileRows) { // First check that the file identifiers match because that is quick and easy. CabinetFileInfo cabFileInfo = fileList[i] as CabinetFileInfo; cabinetValid = (cabFileInfo.FileId == fileRow.File); if (cabinetValid) { // Still valid so ensure the source time stamp hasn't changed. Thus we need // to convert the source file time stamp into a cabinet compatible data/time. FileInfo fileInfo = new FileInfo(fileRow.Source); ushort sourceCabDate; ushort sourceCabTime; Cab.Interop.CabInterop.DateTimeToCabDateAndTime(fileInfo.LastWriteTime, out sourceCabDate, out sourceCabTime); cabinetValid = (cabFileInfo.Date == sourceCabDate && cabFileInfo.Time == sourceCabTime) && (cabFileInfo.Size == fileInfo.Length); } if (!cabinetValid) { break; } i++; } } } } return (cabinetValid ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy); } else // by default move the built cabinet { return CabinetBuildOption.BuildAndMove; } }
/// <summary> /// Assigns the file rows to media rows based on Media or MediaTemplate authoring. Updates uncompressed files collection. /// </summary> /// <param name="fileRows">FileRowCollection</param> public void AssignFiles(FileRowCollection fileRows) { bool autoAssign = false; MediaRow mergeModuleMediaRow = null; Table mediaTable = this.output.Tables["Media"]; Table mediaTemplateTable = this.output.Tables["WixMediaTemplate"]; // If both tables are authored, it is an error. if ((mediaTemplateTable != null && mediaTemplateTable.Rows.Count > 0) &&( mediaTable != null && mediaTable.Rows.Count > 1)) { throw new WixException(WixErrors.MediaTableCollision(null)); } autoAssign = mediaTemplateTable != null && OutputType.Module != this.output.Type ? true : false; // When building merge module, all the files go to "#MergeModule.CABinet" if (OutputType.Module == this.output.Type) { Table mergeModuleMediaTable = new Table(null, this.core.TableDefinitions["Media"]); mergeModuleMediaRow = (MediaRow)mergeModuleMediaTable.CreateRow(null); mergeModuleMediaRow.Cabinet = "#MergeModule.CABinet"; this.cabinets.Add(mergeModuleMediaRow, new FileRowCollection()); } if (autoAssign) { this.AutoAssignFiles(mediaTable, fileRows); } else { this.ManuallyAssignFiles(mediaTable, mergeModuleMediaRow, fileRows); } }
/// <summary> /// Update several msi tables with data contained in files references in the File table. /// </summary> /// <remarks> /// For versioned files, update the file version and language in the File table. For /// unversioned files, add a row to the MsiFileHash table for the file. For assembly /// files, add a row to the MsiAssembly table and add AssemblyName information by adding /// MsiAssemblyName rows. /// </remarks> /// <param name="output">Internal representation of the msi database to operate upon.</param> /// <param name="fileRows">The indexed file rows.</param> /// <param name="mediaRows">The indexed media rows.</param> /// <param name="infoCache">A hashtable to populate with the file information (optional).</param> /// <param name="modularizationGuid">The modularization guid (used in case of a merge module).</param> private Hashtable UpdateFileInformation(Output output, FileRowCollection fileRows, MediaRowCollection mediaRows, IDictionary<string, string> infoCache, string modularizationGuid) { // Index for all the fileId's // NOTE: When dealing with patches, there is a file table for each transform. In most cases, the data in these rows will be the same, however users of this index need to be aware of this. Hashtable fileRowIndex = new Hashtable(fileRows.Count); Table mediaTable = output.Tables["Media"]; // calculate sequence numbers and media disk id layout for all file media information objects if (OutputType.Module == output.Type) { int lastSequence = 0; foreach (FileRow fileRow in fileRows) { fileRow.Sequence = ++lastSequence; fileRowIndex[fileRow.File] = fileRow; } } else if (null != mediaTable) { int lastSequence = 0; MediaRow mediaRow = null; SortedList patchGroups = new SortedList(); // sequence the non-patch-added files foreach (FileRow fileRow in fileRows) { fileRowIndex[fileRow.File] = fileRow; if (null == mediaRow) { mediaRow = mediaRows[fileRow.DiskId]; if (OutputType.Patch == output.Type) { // patch Media cannot start at zero lastSequence = mediaRow.LastSequence; } } else if (mediaRow.DiskId != fileRow.DiskId) { mediaRow.LastSequence = lastSequence; mediaRow = mediaRows[fileRow.DiskId]; } if (0 < fileRow.PatchGroup) { ArrayList patchGroup = (ArrayList)patchGroups[fileRow.PatchGroup]; if (null == patchGroup) { patchGroup = new ArrayList(); patchGroups.Add(fileRow.PatchGroup, patchGroup); } patchGroup.Add(fileRow); } else { fileRow.Sequence = ++lastSequence; } } if (null != mediaRow) { mediaRow.LastSequence = lastSequence; mediaRow = null; } // sequence the patch-added files foreach (ArrayList patchGroup in patchGroups.Values) { foreach (FileRow fileRow in patchGroup) { if (null == mediaRow) { mediaRow = mediaRows[fileRow.DiskId]; } else if (mediaRow.DiskId != fileRow.DiskId) { mediaRow.LastSequence = lastSequence; mediaRow = mediaRows[fileRow.DiskId]; } fileRow.Sequence = ++lastSequence; } } if (null != mediaRow) { mediaRow.LastSequence = lastSequence; } } else { foreach (FileRow fileRow in fileRows) { fileRowIndex[fileRow.File] = fileRow; } } // no more work to do here if there are no file rows in the file table // note that this does not mean there are no files - the files from // merge modules are never put in the output's file table Table fileTable = output.Tables["File"]; if (null == fileTable) { return fileRowIndex; } // gather information about files that did not come from merge modules foreach (FileRow row in fileTable.Rows) { UpdateFileRow(output, infoCache, modularizationGuid, fileRowIndex, row, false); } return fileRowIndex; }
/// <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; }
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); }
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> /// Assign files to cabinets based on Media authoring. /// </summary> /// <param name="mediaTable"></param> /// <param name="mergeModuleMediaRow"></param> /// <param name="fileRows"></param> private void ManuallyAssignFiles(Table mediaTable, MediaRow mergeModuleMediaRow, FileRowCollection fileRows) { if (OutputType.Module != this.output.Type) { if (null != mediaTable) { Dictionary <string, MediaRow> cabinetMediaRows = new Dictionary <string, MediaRow>(StringComparer.InvariantCultureIgnoreCase); foreach (MediaRow mediaRow in mediaTable.Rows) { // If the Media row has a cabinet, make sure it is unique across all Media rows. if (!String.IsNullOrEmpty(mediaRow.Cabinet)) { MediaRow existingRow; if (cabinetMediaRows.TryGetValue(mediaRow.Cabinet, out existingRow)) { this.core.OnMessage(WixErrors.DuplicateCabinetName(mediaRow.SourceLineNumbers, mediaRow.Cabinet)); this.core.OnMessage(WixErrors.DuplicateCabinetName2(existingRow.SourceLineNumbers, existingRow.Cabinet)); } else { cabinetMediaRows.Add(mediaRow.Cabinet, mediaRow); } } this.mediaRows.Add(mediaRow); } } foreach (MediaRow mediaRow in this.mediaRows) { if (null != mediaRow.Cabinet) { this.cabinets.Add(mediaRow, new FileRowCollection()); } } } foreach (FileRow fileRow in fileRows) { if (OutputType.Module == output.Type) { ((FileRowCollection)this.cabinets[mergeModuleMediaRow]).Add(fileRow); } else { MediaRow mediaRow = this.mediaRows[fileRow.DiskId]; if (null == mediaRow) { this.core.OnMessage(WixErrors.MissingMedia(fileRow.SourceLineNumbers, fileRow.DiskId)); continue; } // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == output.Type && (YesNoType.No == fileRow.Compressed || (YesNoType.NotSet == fileRow.Compressed && !this.filesCompressed))) { uncompressedFileRows.Add(fileRow); } else // file in a Module or marked compressed { FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[mediaRow]; if (null != cabinetFileRow) { cabinetFileRow.Add(fileRow); } else { this.core.OnMessage(WixErrors.ExpectedMediaCabinet(fileRow.SourceLineNumbers, fileRow.File, fileRow.DiskId)); } } } } }
/// <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); }
/// <summary> /// Creates cabinet files. /// </summary> /// <param name="output">Output to generate image for.</param> /// <param name="fileRows">The indexed file rows.</param> /// <param name="fileTransfers">Array of files to be transfered.</param> /// <param name="mediaRows">The indexed media rows.</param> /// <param name="layoutDirectory">The directory in which the image should be layed out.</param> /// <param name="compressed">Flag if source image should be compressed.</param> /// <returns>The uncompressed file rows.</returns> private FileRowCollection CreateCabinetFiles(Output output, FileRowCollection fileRows, ArrayList fileTransfers, MediaRowCollection mediaRows, string layoutDirectory, bool compressed, AutoMediaAssigner autoMediaAssigner) { this.SetCabbingThreadCount(); // Send Binder object to Facilitate NewCabNamesCallBack Callback CabinetBuilder cabinetBuilder = new CabinetBuilder(this.cabbingThreadCount, Marshal.GetFunctionPointerForDelegate(this.newCabNamesCallBack)); // Supply Compile MediaTemplate Attributes to Cabinet Builder int MaximumCabinetSizeForLargeFileSplitting; int MaximumUncompressedMediaSize; this.GetMediaTemplateAttributes(out MaximumCabinetSizeForLargeFileSplitting, out MaximumUncompressedMediaSize); cabinetBuilder.MaximumCabinetSizeForLargeFileSplitting = MaximumCabinetSizeForLargeFileSplitting; cabinetBuilder.MaximumUncompressedMediaSize = MaximumUncompressedMediaSize; if (null != this.MessageHandler) { cabinetBuilder.Message += new MessageEventHandler(this.MessageHandler); } foreach (DictionaryEntry entry in autoMediaAssigner.Cabinets) { MediaRow mediaRow = (MediaRow)entry.Key; FileRowCollection files = (FileRowCollection)entry.Value; string cabinetDir = this.FileManager.ResolveMedia(mediaRow, layoutDirectory); CabinetWorkItem cabinetWorkItem = this.CreateCabinetWorkItem(output, cabinetDir, mediaRow, files, fileTransfers); if (null != cabinetWorkItem) { cabinetBuilder.Enqueue(cabinetWorkItem); } } // stop processing if an error previously occurred if (this.core.EncounteredError) { return null; } // create queued cabinets with multiple threads int cabError = cabinetBuilder.CreateQueuedCabinets(); if (0 != cabError) { this.core.EncounteredError = true; return null; } return autoMediaAssigner.UncompressedFileRows; }
/// <summary> /// Assigne files to cabinets based on MediaTemplate authoring. /// </summary> /// <param name="fileRows">FileRowCollection</param> private void AutoAssignFiles(Table mediaTable, FileRowCollection fileRows) { const int MaxCabIndex = 999; ulong currentPreCabSize = 0; ulong maxPreCabSizeInBytes; int maxPreCabSizeInMB = 0; int currentCabIndex = 0; MediaRow currentMediaRow = null; Table mediaTemplateTable = this.output.Tables["WixMediaTemplate"]; // Auto assign files to cabinets based on maximum uncompressed media size mediaTable.Rows.Clear(); WixMediaTemplateRow mediaTemplateRow = (WixMediaTemplateRow)mediaTemplateTable.Rows[0]; if (!String.IsNullOrEmpty(mediaTemplateRow.CabinetTemplate)) { this.cabinetNameTemplate = mediaTemplateRow.CabinetTemplate; } string mumsString = Environment.GetEnvironmentVariable("WIX_MUMS"); try { // Override authored mums value if environment variable is authored. if (!String.IsNullOrEmpty(mumsString)) { maxPreCabSizeInMB = Int32.Parse(mumsString); } else { maxPreCabSizeInMB = mediaTemplateRow.MaximumUncompressedMediaSize; } maxPreCabSizeInBytes = (ulong)maxPreCabSizeInMB * 1024 * 1024; } catch (FormatException) { throw new WixException(WixErrors.IllegalEnvironmentVariable("WIX_MUMS", mumsString)); } catch (OverflowException) { throw new WixException(WixErrors.MaximumUncompressedMediaSizeTooLarge(null, maxPreCabSizeInMB)); } foreach (FileRow fileRow in fileRows) { // When building a product, if the current file is not to be compressed or if // the package set not to be compressed, don't cab it. if (OutputType.Product == output.Type && (YesNoType.No == fileRow.Compressed || (YesNoType.NotSet == fileRow.Compressed && !this.filesCompressed))) { uncompressedFileRows.Add(fileRow); continue; } FileInfo fileInfo = null; // Get the file size try { fileInfo = new FileInfo(fileRow.Source); } catch (ArgumentException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } catch (PathTooLongException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } catch (NotSupportedException) { this.core.OnMessage(WixErrors.InvalidFileName(fileRow.SourceLineNumbers, fileRow.Source)); } if (fileInfo.Exists) { if (fileInfo.Length > Int32.MaxValue) { throw new WixException(WixErrors.FileTooLarge(fileRow.SourceLineNumbers, fileRow.Source)); } fileRow.FileSize = Convert.ToInt32(fileInfo.Length, CultureInfo.InvariantCulture); } if (currentCabIndex == MaxCabIndex) { // Associate current file with last cab (irrespective of the size) and cab index is not incremented anymore. FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); continue; } // Update current cab size. currentPreCabSize += (ulong)fileRow.FileSize; if (currentPreCabSize > maxPreCabSizeInBytes) { // Overflow due to current file if (currentPreCabSize == (ulong)fileRow.FileSize) { // Only one file in this cab. currentMediaRow = AddMediaRow(mediaTable, ++currentCabIndex); FileRowCollection cabinetFileRow = (FileRowCollection)cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); currentPreCabSize = 0; } else { currentMediaRow = this.AddMediaRow(mediaTable, ++currentCabIndex); // Finish current media row and create new one. FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); currentPreCabSize = (ulong)fileRow.FileSize; } } else { // File fits in the current cab. if (currentMediaRow == null) { // Create new cab and MediaRow currentMediaRow = AddMediaRow(mediaTable, ++currentCabIndex); } // Associate current file with current cab. FileRowCollection cabinetFileRow = (FileRowCollection)this.cabinets[currentMediaRow]; fileRow.DiskId = currentCabIndex; cabinetFileRow.Add(fileRow); } } // If there are uncompressed files and no MediaRow, create a default one. if (uncompressedFileRows.Count > 0 && mediaTable.Rows.Count == 0 ) { MediaRow defaultMediaRow = (MediaRow)mediaTable.CreateRow(null); defaultMediaRow.DiskId = 1; mediaRows.Add(defaultMediaRow); } }
/// <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> /// Resolves the source path of a cabinet file. /// </summary> /// <param name="fileRows">Collection of files in this cabinet.</param> /// <param name="cabinetPath">Path to cabinet to generate. Path may be modified by delegate.</param> /// <returns>The CabinetBuildOption. By default the cabinet is built and moved to its target location.</returns> public virtual CabinetBuildOption ResolveCabinet(FileRowCollection fileRows, ref string cabinetPath) { // no special behavior specified, use the default if (null == this.cabCachePath && !this.reuseCabinets) { return(CabinetBuildOption.BuildAndMove); } // if a cabinet cache path was provided, change the location for the cabinet // to be built to if (null != this.cabCachePath) { string cabinetName = Path.GetFileName(cabinetPath); cabinetPath = Path.Combine(this.cabCachePath, cabinetName); } // if we still think we're going to reuse the cabinet check to see if the cabinet exists first if (this.reuseCabinets) { bool cabinetExists = false; if (File.Exists(cabinetPath)) { // check to see if // 1. any files are added or removed // 2. order of files changed or names changed // 3. modified time changed cabinetExists = true; Cab.WixEnumerateCab wixEnumerateCab = new Cab.WixEnumerateCab(); ArrayList fileList = wixEnumerateCab.Enumerate(cabinetPath); if (fileRows.Count != fileList.Count) { cabinetExists = false; } else { int i = 0; foreach (FileRow fileRow in fileRows) { CabinetFileInfo fileInfo = fileList[i] as CabinetFileInfo; DateTime fileTime = File.GetLastWriteTime(fileRow.Source); ushort cabDate; ushort cabTime; Cab.Interop.CabInterop.DateTimeToCabDateAndTime(fileTime, out cabDate, out cabTime); if (fileRow.File != fileInfo.FileId || fileInfo.Date != cabDate || fileInfo.Time != cabTime) { cabinetExists = false; break; } i++; } } } return(cabinetExists ? CabinetBuildOption.Copy : CabinetBuildOption.BuildAndCopy); } else // by default move the built cabinet { return(CabinetBuildOption.BuildAndMove); } }