/// <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> /// 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> /// 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); } }
/// <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; }