/// <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> /// Adds a row to the media table with cab name template filled in. /// </summary> /// <param name="mediaTable"></param> /// <param name="cabIndex"></param> /// <returns></returns> private MediaRow AddMediaRow(Table mediaTable, int cabIndex) { MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(null); currentMediaRow.DiskId = cabIndex; mediaRows.Add(currentMediaRow); currentMediaRow.Cabinet = String.Format(this.cabinetNameTemplate, cabIndex); cabinets.Add(currentMediaRow, new FileRowCollection()); return(currentMediaRow); }
/// <summary> /// Create a deleted or modified row. /// </summary> /// <param name="table">The table containing the row.</param> /// <param name="primaryKeys">The primary keys of the row.</param> /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param> /// <returns>The new row.</returns> private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) { Row row = table.CreateRow(null); string[] primaryKeyParts = primaryKeys.Split('\t'); int primaryKeyPartIndex = 0; for (int i = 0; i < table.Definition.Columns.Count; i++) { ColumnDefinition columnDefinition = table.Definition.Columns[i]; if (columnDefinition.IsPrimaryKey) { if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) { row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); } else { row[i] = primaryKeyParts[primaryKeyPartIndex++]; } } else if (setRequiredFields) { if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) { row[i] = 1; } else if (ColumnType.Object == columnDefinition.Type) { if (null == this.emptyFile) { this.emptyFile = this.tempFiles.AddExtension("empty"); using (FileStream fileStream = File.Create(this.emptyFile)) { } } row[i] = this.emptyFile; } else { row[i] = "1"; } } } return(row); }
/// <summary> /// Adds the validation rows to the _Validation table. /// </summary> /// <param name="validationTable">The _Validation table.</param> internal void AddValidationRows(Table validationTable) { foreach (ColumnDefinition columnDef in this.columns) { Row row = validationTable.CreateRow(null); row[0] = this.name; row[1] = columnDef.Name; if (columnDef.IsNullable) { row[2] = "Y"; } else { row[2] = "N"; } if (columnDef.IsMinValueSet) { row[3] = columnDef.MinValue; } if (columnDef.IsMaxValueSet) { row[4] = columnDef.MaxValue; } row[5] = columnDef.KeyTable; if (columnDef.IsKeyColumnSet) { row[6] = columnDef.KeyColumn; } if (ColumnCategory.Unknown != columnDef.Category) { row[7] = columnDef.Category.ToString(); } row[8] = columnDef.Possibilities; row[9] = columnDef.Description; } }
/// <summary> /// Adds a row to the media table with cab name template filled in. /// </summary> /// <param name="mediaTable"></param> /// <param name="cabIndex"></param> /// <returns></returns> private MediaRow AddMediaRow(Table mediaTable, int cabIndex, string compressionLevel) { MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(null); currentMediaRow.DiskId = cabIndex; currentMediaRow.Cabinet = String.Format(this.cabinetNameTemplate, cabIndex); this.mediaRows.Add(currentMediaRow); this.cabinets.Add(currentMediaRow, new FileRowCollection()); Table wixMediaTable = this.output.EnsureTable(this.core.TableDefinitions["WixMedia"]); Row row = wixMediaTable.CreateRow(null); row[0] = cabIndex; row[1] = compressionLevel; return(currentMediaRow); }
public static PayloadInfoRow Create(SourceLineNumberCollection sourceLineNumbers, Output output, string id, string name, string sourceFile, bool contentFile, bool suppressSignatureValidation, string downloadUrl, string container, PackagingType packaging) { Table table = output.Tables["PayloadInfo"]; PayloadInfoRow row = (PayloadInfoRow)table.CreateRow(sourceLineNumbers); row.Id = id; row.Name = name; row.SourceFile = sourceFile; row.ContentFile = contentFile; row.SuppressSignatureValidation = suppressSignatureValidation; row.DownloadUrl = downloadUrl; row.Container = container; row.Packaging = packaging; PayloadInfoRow.ResolvePayloadInfo(row); return(row); }
/// <summary> /// Creates new WixGroup rows for a list of items. /// </summary> /// <param name="parentType">The group type for the parent group in the new rows.</param> /// <param name="parentId">The identifier of the parent group in the new rows.</param> /// <param name="orderedItems">The list of new items.</param> private void CreateNewGroupRows(string parentType, string parentId, List <Item> orderedItems) { // TODO: MSIs don't guarantee that rows stay in the same order, and technically, neither // does WiX (although they do, currently). We probably want to "upgrade" this to a new // table that includes a sequence number, and then change the code that uses ordered // groups to read from that table instead. Table wixGroupTable = this.output.Tables["WixGroup"]; Debug.Assert(null != wixGroupTable); foreach (Item item in orderedItems) { Row row = wixGroupTable.CreateRow(item.Row.SourceLineNumbers); row[0] = parentId; row[1] = parentType; row[2] = item.Id; row[3] = item.Type; } }
private void UpdateTransformSummaryInformationTable(Table summaryInfoTable, TransformFlags validationFlags) { // calculate the minimum version of MSI required to process the transform int targetMin; int updatedMin; int minimumVersion = 100; if (Int32.TryParse(this.transformSummaryInfo.TargetMinimumVersion, out targetMin) && Int32.TryParse(this.transformSummaryInfo.UpdatedMinimumVersion, out updatedMin)) { minimumVersion = Math.Max(targetMin, updatedMin); } Hashtable summaryRows = new Hashtable(summaryInfoTable.Rows.Count); foreach (Row row in summaryInfoTable.Rows) { summaryRows[row[0]] = row; if ((int)SummaryInformation.Transform.CodePage == (int)row[0]) { row.Fields[1].Data = this.transformSummaryInfo.UpdatedSummaryInfoCodepage; row.Fields[1].PreviousData = this.transformSummaryInfo.TargetSummaryInfoCodepage; } else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == (int)row[0]) { row[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; } else if ((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == (int)row[0]) { row[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; } else if ((int)SummaryInformation.Transform.ProductCodes == (int)row[0]) { row[1] = String.Concat(this.transformSummaryInfo.TargetProductCode, this.transformSummaryInfo.TargetProductVersion, ';', this.transformSummaryInfo.UpdatedProductCode, this.transformSummaryInfo.UpdatedProductVersion, ';', this.transformSummaryInfo.TargetUpgradeCode); } else if ((int)SummaryInformation.Transform.InstallerRequirement == (int)row[0]) { row[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); } else if ((int)SummaryInformation.Transform.Security == (int)row[0]) { row[1] = "4"; } } if (!summaryRows.Contains((int)SummaryInformation.Transform.TargetPlatformAndLanguage)) { Row summaryRow = summaryInfoTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.TargetPlatformAndLanguage; summaryRow[1] = this.transformSummaryInfo.TargetPlatformAndLanguage; } if (!summaryRows.Contains((int)SummaryInformation.Transform.UpdatedPlatformAndLanguage)) { Row summaryRow = summaryInfoTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage; summaryRow[1] = this.transformSummaryInfo.UpdatedPlatformAndLanguage; } if (!summaryRows.Contains((int)SummaryInformation.Transform.ValidationFlags)) { Row summaryRow = summaryInfoTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.ValidationFlags; summaryRow[1] = ((int)validationFlags).ToString(CultureInfo.InvariantCulture); } if (!summaryRows.Contains((int)SummaryInformation.Transform.InstallerRequirement)) { Row summaryRow = summaryInfoTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.InstallerRequirement; summaryRow[1] = minimumVersion.ToString(CultureInfo.InvariantCulture); } if (!summaryRows.Contains((int)SummaryInformation.Transform.Security)) { Row summaryRow = summaryInfoTable.CreateRow(null); summaryRow[0] = (int)SummaryInformation.Transform.Security; summaryRow[1] = "4"; } }
/// <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> /// 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> /// Finalize the sequence tables. /// </summary> /// <param name="tables">The collection of all tables.</param> /// <remarks> /// Creates the sequence elements. Occurs during finalization because its /// not known if sequences refer to custom actions or dialogs during decompilation. /// </remarks> private void FinalizeSequenceTables(TableCollection tables) { // finalize the normal sequence tables if (OutputType.Product == this.outputType && !this.treatProductAsModule) { foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) { // if suppressing UI elements, skip UI-related sequence tables if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) { continue; } Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]); Table table = tables[sequenceTable.ToString()]; if (null != table) { ArrayList actionRows = new ArrayList(); bool needAbsoluteScheduling = this.suppressRelativeActionSequencing; WixActionRowCollection nonSequencedActionRows = new WixActionRowCollection(); WixActionRowCollection suppressedRelativeActionRows = new WixActionRowCollection(); // create a sorted array of actions in this table foreach (Row row in table.Rows) { WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null); actionRow.Action = Convert.ToString(row[0]); if (null != row[1]) { actionRow.Condition = Convert.ToString(row[1]); } actionRow.Sequence = Convert.ToInt32(row[2]); actionRow.SequenceTable = sequenceTable; actionRows.Add(actionRow); } actionRows.Sort(); for (int i = 0; i < actionRows.Count && !needAbsoluteScheduling; i++) { WixActionRow actionRow = (WixActionRow)actionRows[i]; WixActionRow standardActionRow = this.standardActions[actionRow.SequenceTable, actionRow.Action]; // create actions for custom actions, dialogs, AppSearch when its moved, and standard actions with non-standard conditions if ("AppSearch" == actionRow.Action || null == standardActionRow || actionRow.Condition != standardActionRow.Condition) { WixActionRow previousActionRow = null; WixActionRow nextActionRow = null; // find the previous action row if there is one if (0 <= i - 1) { previousActionRow = (WixActionRow)actionRows[i - 1]; } // find the next action row if there is one if (actionRows.Count > i + 1) { nextActionRow = (WixActionRow)actionRows[i + 1]; } // the logic for setting the before or after attribute for an action: // 1. If more than one action shares the same sequence number, everything must be absolutely sequenced. // 2. If the next action is a standard action and is 1 sequence number higher, this action occurs before it. // 3. If the previous action is a standard action and is 1 sequence number lower, this action occurs after it. // 4. If this action is not standard and the previous action is 1 sequence number lower and does not occur before this action, this action occurs after it. // 5. If this action is not standard and the previous action does not have the same sequence number and the next action is 1 sequence number higher, this action occurs before it. // 6. If this action is AppSearch and has all standard information, ignore it. // 7. If this action is standard and has a non-standard condition, create the action without any scheduling information. // 8. Everything must be absolutely sequenced. if ((null != previousActionRow && actionRow.Sequence == previousActionRow.Sequence) || (null != nextActionRow && actionRow.Sequence == nextActionRow.Sequence)) { needAbsoluteScheduling = true; } else if (null != nextActionRow && null != this.standardActions[sequenceTable, nextActionRow.Action] && actionRow.Sequence + 1 == nextActionRow.Sequence) { actionRow.Before = nextActionRow.Action; } else if (null != previousActionRow && null != this.standardActions[sequenceTable, previousActionRow.Action] && actionRow.Sequence - 1 == previousActionRow.Sequence) { actionRow.After = previousActionRow.Action; } else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence - 1 == previousActionRow.Sequence && previousActionRow.Before != actionRow.Action) { actionRow.After = previousActionRow.Action; } else if (null == standardActionRow && null != previousActionRow && actionRow.Sequence != previousActionRow.Sequence && null != nextActionRow && actionRow.Sequence + 1 == nextActionRow.Sequence) { actionRow.Before = nextActionRow.Action; } else if ("AppSearch" == actionRow.Action && null != standardActionRow && actionRow.Sequence == standardActionRow.Sequence && actionRow.Condition == standardActionRow.Condition) { // ignore an AppSearch row which has the WiX standard sequence and a standard condition } else if (null != standardActionRow && actionRow.Condition != standardActionRow.Condition) // standard actions get their standard sequence numbers { nonSequencedActionRows.Add(actionRow); } else if (0 < actionRow.Sequence) { needAbsoluteScheduling = true; } } else { suppressedRelativeActionRows.Add(actionRow); } } // create the actions now that we know if they must be absolutely or relatively scheduled foreach (WixActionRow actionRow in actionRows) { if (needAbsoluteScheduling) { // remove any before/after information to ensure this is absolutely sequenced actionRow.Before = null; actionRow.After = null; } else if (nonSequencedActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) { // clear the sequence attribute to ensure this action is scheduled without a sequence number (or before/after) actionRow.Sequence = 0; } else if (suppressedRelativeActionRows.Contains(actionRow.SequenceTable, actionRow.Action)) { // skip the suppressed relatively scheduled action rows continue; } // create the action element this.CreateActionElement(actionRow); } } } } else if (OutputType.Module == this.outputType || this.treatProductAsModule) // finalize the Module sequence tables { foreach (SequenceTable sequenceTable in Enum.GetValues(typeof(SequenceTable))) { // if suppressing UI elements, skip UI-related sequence tables if (this.suppressUI && ("AdminUISequence" == sequenceTable.ToString() || "InstallUISequence" == sequenceTable.ToString())) { continue; } Table actionsTable = new Table(null, this.tableDefinitions["WixAction"]); Table table = tables[String.Concat("Module", sequenceTable.ToString())]; if (null != table) { foreach (Row row in table.Rows) { WixActionRow actionRow = (WixActionRow)actionsTable.CreateRow(null); actionRow.Action = Convert.ToString(row[0]); if (null != row[1]) { actionRow.Sequence = Convert.ToInt32(row[1]); } if (null != row[2] && null != row[3]) { switch (Convert.ToInt32(row[3])) { case 0: actionRow.Before = Convert.ToString(row[2]); break; case 1: actionRow.After = Convert.ToString(row[2]); break; default: this.core.OnMessage(WixWarnings.IllegalColumnValue(row.SourceLineNumbers, table.Name, row.Fields[3].Column.Name, row[3])); break; } } if (null != row[4]) { actionRow.Condition = Convert.ToString(row[4]); } actionRow.SequenceTable = sequenceTable; // create action elements for non-standard actions if (null == this.standardActions[actionRow.SequenceTable, actionRow.Action] || null != actionRow.After || null != actionRow.Before) { this.CreateActionElement(actionRow); } } } } } }
/// <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> /// Unbind an MSI transform file. /// </summary> /// <param name="transformFile">The transform file.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <returns>The unbound transform.</returns> private Output UnbindTransform(string transformFile, string exportBasePath) { Output transform = new Output(SourceLineNumberCollection.FromFileName(transformFile)); transform.Type = OutputType.Transform; // get the summary information table using (SummaryInformation summaryInformation = new SummaryInformation(transformFile)) { Table table = transform.Tables.EnsureTable(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(transform.SourceLineNumbers); row[0] = i; row[1] = value; } } } // create a schema msi which hopefully matches the table schemas in the transform Output schemaOutput = new Output(null); string msiDatabaseFile = Path.Combine(this.tempFiles.BasePath, "schema.msi"); foreach (TableDefinition tableDefinition in this.tableDefinitions) { // skip unreal tables and the Patch table if (!tableDefinition.IsUnreal && "Patch" != tableDefinition.Name) { schemaOutput.EnsureTable(tableDefinition); } } // bind the schema msi Binder binder = new Binder(); binder.SuppressAddingValidationRows = true; binder.WixVariableResolver = new WixVariableResolver(); binder.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications Hashtable addedRows = new Hashtable(); Table transformViewTable; using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform with the ViewTransform option to collect all the modifications msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All | TransformErrorConditions.ViewTransform); // unbind the database Output transformViewOutput = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath); // index the added and possibly modified rows (added rows may also appears as modified rows) transformViewTable = transformViewOutput.Tables["_TransformView"]; Hashtable modifiedRows = new Hashtable(); foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; if ("INSERT" == columnName) { string index = String.Concat(tableName, ':', primaryKeys); addedRows.Add(index, null); } else if ("CREATE" != columnName && "DELETE" != columnName && "DROP" != columnName && null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); modifiedRows[index] = row; } } // create placeholder rows for modified rows to make the transform insert the updated values when its applied foreach (Row row in modifiedRows.Values) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; string index = String.Concat(tableName, ':', primaryKeys); // ignore information for added rows if (!addedRows.Contains(index)) { Table table = schemaOutput.Tables[tableName]; this.CreateRow(table, primaryKeys, true); } } } // re-bind the schema output with the placeholder rows binder.GenerateDatabase(schemaOutput, msiDatabaseFile); // apply the transform to the database and retrieve the modifications using (Database msiDatabase = new Database(msiDatabaseFile, OpenDatabase.Transact)) { // apply the transform msiDatabase.ApplyTransform(transformFile, TransformErrorConditions.All); // commit the database to guard against weird errors with streams msiDatabase.Commit(); // unbind the database Output output = this.UnbindDatabase(msiDatabaseFile, msiDatabase, OutputType.Product, exportBasePath); // index all the rows to easily find modified rows Hashtable rows = new Hashtable(); foreach (Table table in output.Tables) { foreach (Row row in table.Rows) { rows.Add(String.Concat(table.Name, ':', row.GetPrimaryKey('\t')), row); } } // process the _TransformView rows into transform rows foreach (Row row in transformViewTable.Rows) { string tableName = (string)row[0]; string columnName = (string)row[1]; string primaryKeys = (string)row[2]; Table table = transform.Tables.EnsureTable(null, this.tableDefinitions[tableName]); if ("CREATE" == columnName) // added table { table.Operation = TableOperation.Add; } else if ("DELETE" == columnName) // deleted row { Row deletedRow = this.CreateRow(table, primaryKeys, false); deletedRow.Operation = RowOperation.Delete; } else if ("DROP" == columnName) // dropped table { table.Operation = TableOperation.Drop; } else if ("INSERT" == columnName) // added row { string index = String.Concat(tableName, ':', primaryKeys); Row addedRow = (Row)rows[index]; addedRow.Operation = RowOperation.Add; table.Rows.Add(addedRow); } else if (null != primaryKeys) // modified row { string index = String.Concat(tableName, ':', primaryKeys); // the _TransformView table includes information for added rows // that looks like modified rows so it sometimes needs to be ignored if (!addedRows.Contains(index)) { Row modifiedRow = (Row)rows[index]; // mark the field as modified int indexOfModifiedValue = modifiedRow.TableDefinition.Columns.IndexOf(columnName); modifiedRow.Fields[indexOfModifiedValue].Modified = true; // move the modified row into the transform the first time its encountered if (RowOperation.None == modifiedRow.Operation) { modifiedRow.Operation = RowOperation.Modify; table.Rows.Add(modifiedRow); } } } else // added column { table.Definition.Columns[columnName].Added = true; } } } return(transform); }
/// <summary> /// Unbind an MSI database file. /// </summary> /// <param name="databaseFile">The database file.</param> /// <param name="database">The opened database.</param> /// <param name="outputType">The type of output to create.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <returns>The output representing the database.</returns> private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath) { string modularizationGuid = null; Output output = new Output(SourceLineNumberCollection.FromFileName(databaseFile)); View validationView = null; // set the output type output.Type = outputType; // get the codepage database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) { string line; while (null != (line = sr.ReadLine())) { string[] data = line.Split('\t'); if (2 == data.Length) { output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); } } } // get the summary information table using (SummaryInformation summaryInformation = new SummaryInformation(database)) { Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(output.SourceLineNumbers); row[0] = i; row[1] = value; } } output.Tables.Add(table); } try { // open a view on the validation table if it exists if (database.TableExists("_Validation")) { validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); } // get the normal tables using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) { Record tableRecord; while (null != (tableRecord = tablesView.Fetch())) { string tableName = tableRecord.GetString(1); using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) { Record rowRecord; Table table; TableDefinition tableDefinition; if (this.tableDefinitions.Contains(tableName)) { tableDefinition = this.tableDefinitions[tableName]; // TODO: verify table definitions // - error for different column name or data type // - warn for additional columns // - need extra info for new columns in standard tables (MSI 4.0 changes) } else // custom table { TableDefinition customTableDefinition = new TableDefinition(tableName, false, false); Hashtable customTablePrimaryKeys = new Hashtable(); Record customColumnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES); Record customColumnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES); int customColumnCount = customColumnNameRecord.GetFieldCount(); // index the primary keys using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) { int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); for (int i = 1; i <= primaryKeysFieldCount; i++) { customTablePrimaryKeys[primaryKeysRecord.GetString(i)] = null; } } for (int i = 1; i <= customColumnCount; i++) { string columnName = customColumnNameRecord.GetString(i); string idtType = customColumnTypeRecord.GetString(i); ColumnType columnType; int length; bool nullable; ColumnCategory columnCategory = ColumnCategory.Unknown; ColumnModularizeType columnModularizeType = ColumnModularizeType.None; bool primary = customTablePrimaryKeys.Contains(columnName); bool minValueSet = false; int minValue = -1; bool maxValueSet = false; int maxValue = -1; string keyTable = null; bool keyColumnSet = false; int keyColumn = -1; string category = null; string set = null; string description = null; // get the column type, length, and whether its nullable switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) { case 'i': columnType = ColumnType.Number; break; case 'l': columnType = ColumnType.Localized; break; case 's': columnType = ColumnType.String; break; case 'v': columnType = ColumnType.Object; break; default: // TODO: error columnType = ColumnType.Unknown; break; } length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); nullable = Char.IsUpper(idtType[0]); // try to get validation information if (null != validationView) { Record validationRecord = new Record(2); validationRecord.SetString(1, tableName); validationRecord.SetString(2, columnName); validationView.Execute(validationRecord); validationRecord.Close(); if (null != (validationRecord = validationView.Fetch())) { string validationNullable = validationRecord.GetString(3); minValueSet = !validationRecord.IsNull(4); minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); maxValueSet = !validationRecord.IsNull(5); maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); keyColumnSet = !validationRecord.IsNull(7); keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); // check the validation nullable value against the column definition if (null == validationNullable) { // TODO: warn for illegal validation nullable column } else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) { // TODO: warn for mismatch between column definition and validation nullable } // convert category to ColumnCategory if (null != category) { try { columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); } catch (ArgumentException) { columnCategory = ColumnCategory.Unknown; } } validationRecord.Close(); } else { // TODO: warn about no validation information } } // guess the modularization type if ("Icon" == keyTable && 1 == keyColumn) { columnModularizeType = ColumnModularizeType.Icon; } else if ("Condition" == columnName) { columnModularizeType = ColumnModularizeType.Condition; } else if (ColumnCategory.Formatted == columnCategory) { columnModularizeType = ColumnModularizeType.Property; } else if (ColumnCategory.Identifier == columnCategory) { columnModularizeType = ColumnModularizeType.Column; } customTableDefinition.Columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); } tableDefinition = customTableDefinition; customColumnNameRecord.Close(); customColumnTypeRecord.Close(); } table = new Table(null, tableDefinition); while (null != (rowRecord = tableView.Fetch())) { int recordCount = rowRecord.GetFieldCount(); Row row = table.CreateRow(output.SourceLineNumbers); for (int i = 0; recordCount > i && row.Fields.Length > i; i++) { if (rowRecord.IsNull(i + 1)) { if (!row.Fields[i].Column.IsNullable) { // TODO: display an error for a null value in a non-nullable field OR // display a warning and put an empty string in the value to let the compiler handle it // (the second option is risky because the later code may make certain assumptions about // the contents of a row value) } } else { switch (row.Fields[i].Column.Type) { case ColumnType.Number: if (row.Fields[i].Column.IsLocalizable) { row[i] = Convert.ToString(rowRecord.GetInteger(i + 1), CultureInfo.InvariantCulture); } else { row[i] = rowRecord.GetInteger(i + 1); } break; case ColumnType.Object: string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; if (null != exportBasePath) { string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); sourceFile = Path.Combine(exportBasePath, relativeSourceFile); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); using (FileStream fs = System.IO.File.Create(sourceFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } } row[i] = sourceFile; break; default: string value = rowRecord.GetString(i + 1); switch (row.Fields[i].Column.Category) { case ColumnCategory.Guid: value = value.ToUpper(CultureInfo.InvariantCulture); break; } // de-modularize if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) { Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); if (null == modularizationGuid) { Match match = modularization.Match(value); if (match.Success) { modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); } } value = modularization.Replace(value, String.Empty); } // escape "$(" for the preprocessor value = value.Replace("$(", "$$("); // escape things that look like wix variables MatchCollection matches = Common.WixVariableRegex.Matches(value); for (int j = matches.Count - 1; 0 <= j; j--) { value = value.Insert(matches[j].Index, "!"); } row[i] = value; break; } } } rowRecord.Close(); } output.Tables.Add(table); } tableRecord.Close(); } } } finally { if (null != validationView) { validationView.Close(); } } // set the modularization guid as the PackageCode if (null != modularizationGuid) { Table table = output.Tables["_SummaryInformation"]; foreach (Row row in table.Rows) { if (9 == (int)row[0]) // PID_REVNUMBER { row[1] = modularizationGuid; } } } return(output); }
/// <summary> /// Updates database with signatures from external cabinets. /// </summary> /// <param name="databaseFile">Path to MSI database.</param> /// <param name="outputFile">Ouput for updated MSI database.</param> /// <param name="tidy">Clean up files.</param> /// <returns>True if database is updated.</returns> public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) { // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered bool foundUnsignedExternals = false; bool shouldCommit = false; FileAttributes attributes = File.GetAttributes(databaseFile); if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) { this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); return shouldCommit; } using (Database database = new Database(databaseFile, OpenDatabase.Transact)) { // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content int codepage = 1252; // list of certificates for this database (hash/identifier) Dictionary<string, string> certificates = new Dictionary<string, string>(); // Reset the in-memory tables for this new database Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); // If any digital signature records exist that are not of the media type, preserve them if (database.TableExists("MsiDigitalSignature")) { using (View digitalSignatureView = database.OpenExecuteView("SELECT `Table`, `SignObject`, `DigitalCertificate_`, `Hash` FROM `MsiDigitalSignature` WHERE `Table` <> 'Media'")) { while (true) { using (Record digitalSignatureRecord = digitalSignatureView.Fetch()) { if (null == digitalSignatureRecord) { break; } Row digitalSignatureRow = null; digitalSignatureRow = digitalSignatureTable.CreateRow(null); string table = digitalSignatureRecord.GetString(0); string signObject = digitalSignatureRecord.GetString(1); digitalSignatureRow[0] = table; digitalSignatureRow[1] = signObject; digitalSignatureRow[2] = digitalSignatureRecord.GetString(2); if (false == digitalSignatureRecord.IsNull(3)) { // Export to a file, because the MSI API's require us to provide a file path on disk string hashPath = Path.Combine(this.TempFilesLocation, "MsiDigitalSignature"); string hashFileName = string.Concat(table,".", signObject, ".bin"); Directory.CreateDirectory(hashPath); hashPath = Path.Combine(hashPath, hashFileName); using (FileStream fs = File.Create(hashPath)) { int bytesRead; byte[] buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalSignatureRecord.GetStream(3, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } digitalSignatureRow[3] = hashFileName; } } } } } // If any digital certificates exist, extract and preserve them if (database.TableExists("MsiDigitalCertificate")) { using (View digitalCertificateView = database.OpenExecuteView("SELECT * FROM `MsiDigitalCertificate`")) { while (true) { using (Record digitalCertificateRecord = digitalCertificateView.Fetch()) { if (null == digitalCertificateRecord) { break; } string certificateId = digitalCertificateRecord.GetString(1); // get the identifier of the certificate // Export to a file, because the MSI API's require us to provide a file path on disk string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); certPath = Path.Combine(certPath, string.Concat(certificateId, ".cer")); using (FileStream fs = File.Create(certPath)) { int bytesRead; byte[] buffer = new byte[1024 * 4]; while (0 != (bytesRead = digitalCertificateRecord.GetStream(2, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } // Add it to our "add to MsiDigitalCertificate" table dictionary Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateId; // Now set the file path on disk where this binary stream will be picked up at import time digitalCertificateRow[1] = string.Concat(certificateId, ".cer"); // Load the cert to get it's thumbprint X509Certificate cert = X509Certificate.CreateFromCertFile(certPath); X509Certificate2 cert2 = new X509Certificate2(cert); certificates.Add(cert2.Thumbprint, certificateId); } } } } using (View mediaView = database.OpenExecuteView("SELECT * FROM `Media`")) { while (true) { using (Record mediaRecord = mediaView.Fetch()) { if (null == mediaRecord) { break; } X509Certificate2 cert2 = null; Row digitalSignatureRow = null; string cabName = mediaRecord.GetString(4); // get the name of the cab // If there is no cabinet or it's an internal cab, skip it. if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) { continue; } string cabId = mediaRecord.GetString(1); // get the ID of the cab string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); // If the cabs aren't there, throw an error but continue to catch the other errors if (!File.Exists(cabPath)) { this.OnMessage(WixErrors.WixFileNotFound(cabPath)); continue; } try { // Get the certificate from the cab X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); cert2 = new X509Certificate2(signedFileCert); } catch (System.Security.Cryptography.CryptographicException e) { uint HResult = unchecked((uint)Marshal.GetHRForException(e)); // If the file has no cert, continue, but flag that we found at least one so we can later give a warning if (0x80092009 == HResult) // CRYPT_E_NO_MATCH { foundUnsignedExternals = true; continue; } // todo: exactly which HRESULT corresponds to this issue? // If it's one of these exact platforms, warn the user that it may be due to their OS. if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } else // otherwise, generic error { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } } // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added if (!certificates.ContainsKey(cert2.Thumbprint)) { // generate a stable identifier string certificateGeneratedId = Common.GenerateIdentifier("cer", true, cert2.Thumbprint); // Add it to our "add to MsiDigitalCertificate" table dictionary Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = certificateGeneratedId; // Export to a file, because the MSI API's require us to provide a file path on disk string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); certPath = Path.Combine(certPath, string.Concat(cert2.Thumbprint, ".cer")); File.Delete(certPath); using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) { writer.Write(cert2.RawData); writer.Close(); } // Now set the file path on disk where this binary stream will be picked up at import time digitalCertificateRow[1] = string.Concat(cert2.Thumbprint, ".cer"); certificates.Add(cert2.Thumbprint, certificateGeneratedId); } digitalSignatureRow = digitalSignatureTable.CreateRow(null); digitalSignatureRow[0] = "Media"; digitalSignatureRow[1] = cabId; digitalSignatureRow[2] = certificates[cert2.Thumbprint]; } } } if (digitalCertificateTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalCertificateTable, this.TempFilesLocation, true); shouldCommit = true; } if (digitalSignatureTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalSignatureTable, this.TempFilesLocation, true); shouldCommit = true; } // TODO: if we created the table(s), then we should add the _Validation records for them. certificates = null; // If we did find external cabs but none of them were signed, give a warning if (foundUnsignedExternals) { this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); } if (shouldCommit) { database.Commit(); } } return shouldCommit; }
private void SetMsiAssemblyName(Output output, Table assemblyNameTable, FileRow fileRow, string name, string value, IDictionary<string, string> infoCache, string modularizationGuid) { // check for null value (this can occur when grabbing the file version from an assembly without one) if (null == value || 0 == value.Length) { this.core.OnMessage(WixWarnings.NullMsiAssemblyNameValue(fileRow.SourceLineNumbers, fileRow.Component, name)); } else { Row assemblyNameRow = null; // override directly authored value foreach (Row row in assemblyNameTable.Rows) { if ((string)row[0] == fileRow.Component && (string)row[1] == name) { assemblyNameRow = row; break; } } // if the assembly will be GAC'd and the name in the file table doesn't match the name in the MsiAssemblyName table, error because the install will fail. if ("name" == name && FileAssemblyType.DotNetAssembly == fileRow.AssemblyType && String.IsNullOrEmpty(fileRow.AssemblyApplication) && !String.Equals(Path.GetFileNameWithoutExtension(fileRow.LongFileName), value, StringComparison.OrdinalIgnoreCase)) { this.core.OnMessage(WixErrors.GACAssemblyIdentityWarning(fileRow.SourceLineNumbers, Path.GetFileNameWithoutExtension(fileRow.LongFileName), value)); } if (null == assemblyNameRow) { assemblyNameRow = assemblyNameTable.CreateRow(fileRow.SourceLineNumbers); assemblyNameRow[0] = fileRow.Component; assemblyNameRow[1] = name; assemblyNameRow[2] = value; // put the MsiAssemblyName row in the same section as the related File row assemblyNameRow.SectionId = fileRow.SectionId; if (null == fileRow.AssemblyNameRows) { fileRow.AssemblyNameRows = new RowCollection(); } fileRow.AssemblyNameRows.Add(assemblyNameRow); } else { assemblyNameRow[2] = value; } if (infoCache != null) { string key = String.Format(CultureInfo.InvariantCulture, "assembly{0}.{1}", name, Demodularize(output, modularizationGuid, fileRow.File)).ToLower(CultureInfo.InvariantCulture); infoCache[key] = (string)assemblyNameRow[2]; } } }
/// <summary> /// Updates database with signatures from external cabinets. /// </summary> /// <param name="databaseFile">Path to MSI database.</param> /// <param name="outputFile">Ouput for updated MSI database.</param> /// <param name="tidy">Clean up files.</param> /// <returns>True if database is updated.</returns> public bool InscribeDatabase(string databaseFile, string outputFile, bool tidy) { // Keeps track of whether we've encountered at least one signed cab or not - we'll throw a warning if no signed cabs were encountered bool foundUnsignedExternals = false; bool shouldCommit = false; FileAttributes attributes = File.GetAttributes(databaseFile); if (FileAttributes.ReadOnly == (attributes & FileAttributes.ReadOnly)) { this.OnMessage(WixErrors.ReadOnlyOutputFile(databaseFile)); return(shouldCommit); } using (Database database = new Database(databaseFile, OpenDatabase.Transact)) { // Just use the English codepage, because the tables we're importing only have binary streams / MSI identifiers / other non-localizable content int codepage = 1252; // reset list of certificates seen for this database Dictionary <string, object> certificates = new Dictionary <string, object>(); // Reset the in-memory tables for this new database Table digitalSignatureTable = new Table(null, this.tableDefinitions["MsiDigitalSignature"]); Table digitalCertificateTable = new Table(null, this.tableDefinitions["MsiDigitalCertificate"]); using (View mediaView = database.OpenExecuteView("SELECT * FROM Media")) { while (true) { using (Record mediaRecord = mediaView.Fetch()) { if (null == mediaRecord) { break; } X509Certificate2 cert2 = null; Row digitalSignatureRow = null; string cabName = mediaRecord.GetString(4); // get the name of the cab // If there is no cabinet or it's an internal cab, skip it. if (String.IsNullOrEmpty(cabName) || cabName.StartsWith("#", StringComparison.Ordinal)) { continue; } string cabId = mediaRecord.GetString(1); // get the ID of the cab string cabPath = Path.Combine(Path.GetDirectoryName(databaseFile), cabName); // If the cabs aren't there, throw an error but continue to catch the other errors if (!File.Exists(cabPath)) { this.OnMessage(WixErrors.WixFileNotFound(cabPath)); continue; } try { // Get the certificate from the cab X509Certificate signedFileCert = X509Certificate.CreateFromSignedFile(cabPath); cert2 = new X509Certificate2(signedFileCert); } catch (System.Security.Cryptography.CryptographicException e) { uint HResult = unchecked ((uint)Marshal.GetHRForException(e)); // If the file has no cert, continue, but flag that we found at least one so we can later give a warning if (0x80092009 == HResult) // CRYPT_E_NO_MATCH { foundUnsignedExternals = true; continue; } // todo: exactly which HRESULT corresponds to this issue? // If it's one of these exact platforms, warn the user that it may be due to their OS. if ((5 == Environment.OSVersion.Version.Major && 2 == Environment.OSVersion.Version.Minor) || // W2K3 (5 == Environment.OSVersion.Version.Major && 1 == Environment.OSVersion.Version.Minor)) // XP { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFileDownlevelOS(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } else // otherwise, generic error { this.OnMessage(WixErrors.UnableToGetAuthenticodeCertOfFile(cabPath, String.Format(CultureInfo.InvariantCulture, "HRESULT: 0x{0:x8}", HResult))); } } // If we haven't added this cert to the MsiDigitalCertificate table, set it up to be added if (!certificates.ContainsKey(cert2.Thumbprint)) { // Add it to our "add to MsiDigitalCertificate" table dictionary Row digitalCertificateRow = digitalCertificateTable.CreateRow(null); digitalCertificateRow[0] = cert2.Thumbprint; // Export to a file, because the MSI API's require us to provide a file path on disk string certPath = Path.Combine(this.TempFilesLocation, "MsiDigitalCertificate"); Directory.CreateDirectory(certPath); certPath = Path.Combine(certPath, cert2.Thumbprint + ".cer"); File.Delete(certPath); using (BinaryWriter writer = new BinaryWriter(File.Open(certPath, FileMode.Create))) { writer.Write(cert2.RawData); writer.Close(); } // Now set the file path on disk where this binary stream will be picked up at import time digitalCertificateRow[1] = cert2.Thumbprint + ".cer"; certificates.Add(cert2.Thumbprint, certPath); } digitalSignatureRow = digitalSignatureTable.CreateRow(null); digitalSignatureRow[0] = "Media"; digitalSignatureRow[1] = cabId; digitalSignatureRow[2] = cert2.Thumbprint; } } } if (digitalSignatureTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalSignatureTable, this.TempFilesLocation, true); shouldCommit = true; } if (digitalCertificateTable.Rows.Count > 0) { database.ImportTable(codepage, (IMessageHandler)this, digitalCertificateTable, this.TempFilesLocation, true); shouldCommit = true; } certificates = null; // If we did find external cabs but none of them were signed, give a warning if (foundUnsignedExternals) { this.OnMessage(WixWarnings.ExternalCabsAreNotSigned(databaseFile)); } if (shouldCommit) { database.Commit(); } } return(shouldCommit); }
/// <summary> /// Create a deleted or modified row. /// </summary> /// <param name="table">The table containing the row.</param> /// <param name="primaryKeys">The primary keys of the row.</param> /// <param name="setRequiredFields">Option to set all required fields with placeholder values.</param> /// <returns>The new row.</returns> private Row CreateRow(Table table, string primaryKeys, bool setRequiredFields) { Row row = table.CreateRow(null); string[] primaryKeyParts = primaryKeys.Split('\t'); int primaryKeyPartIndex = 0; for (int i = 0; i < table.Definition.Columns.Count; i++) { ColumnDefinition columnDefinition = table.Definition.Columns[i]; if (columnDefinition.IsPrimaryKey) { if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) { row[i] = Convert.ToInt32(primaryKeyParts[primaryKeyPartIndex++], CultureInfo.InvariantCulture); } else { row[i] = primaryKeyParts[primaryKeyPartIndex++]; } } else if (setRequiredFields) { if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable) { row[i] = 1; } else if (ColumnType.Object == columnDefinition.Type) { if (null == this.emptyFile) { this.emptyFile = this.tempFiles.AddExtension("empty"); using (FileStream fileStream = File.Create(this.emptyFile)) { } } row[i] = this.emptyFile; } else { row[i] = "1"; } } } return row; }
/// <summary> /// Creates a Row from the XmlReader. /// </summary> /// <param name="reader">Reader to get data from.</param> /// <param name="table">Table for this row.</param> /// <returns>New row object.</returns> internal static Row Parse(XmlReader reader, Table table) { Debug.Assert("row" == reader.LocalName); bool empty = reader.IsEmptyElement; RowOperation operation = RowOperation.None; string sectionId = null; SourceLineNumberCollection sourceLineNumbers = null; while (reader.MoveToNextAttribute()) { switch (reader.LocalName) { case "op": switch (reader.Value) { case "add": operation = RowOperation.Add; break; case "delete": operation = RowOperation.Delete; break; case "modify": operation = RowOperation.Modify; break; default: throw new WixException(WixErrors.IllegalAttributeValue(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name, reader.Value, "Add", "Delete", "Modify")); } break; case "sectionId": sectionId = reader.Value; break; case "sourceLineNumber": sourceLineNumbers = new SourceLineNumberCollection(reader.Value); break; default: if (!reader.NamespaceURI.StartsWith("http://www.w3.org/", StringComparison.Ordinal)) { throw new WixException(WixErrors.UnexpectedAttribute(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name)); } break; } } Row row = table.CreateRow(sourceLineNumbers); row.Operation = operation; row.SectionId = sectionId; // loop through all the fields in a row if (!empty) { bool done = false; int field = 0; // loop through all the fields in a row while (!done && reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: switch (reader.LocalName) { case "field": if (row.Fields.Length <= field) { if (!reader.IsEmptyElement) { throw new WixException(WixErrors.UnexpectedColumnCount(SourceLineNumberCollection.FromUri(reader.BaseURI), table.Name)); } } else { row.fields[field].Parse(reader); } ++field; break; default: throw new WixException(WixErrors.UnexpectedElement(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name)); } break; case XmlNodeType.EndElement: done = true; break; } } if (!done) { throw new WixException(WixErrors.ExpectedEndElement(SourceLineNumberCollection.FromUri(reader.BaseURI), "row")); } } return row; }
/// <summary> /// Unbind an MSI database file. /// </summary> /// <param name="databaseFile">The database file.</param> /// <param name="database">The opened database.</param> /// <param name="outputType">The type of output to create.</param> /// <param name="exportBasePath">The path where files should be exported.</param> /// <param name="skipSummaryInfo">Option to skip unbinding the _SummaryInformation table.</param> /// <returns>The output representing the database.</returns> private Output UnbindDatabase(string databaseFile, Database database, OutputType outputType, string exportBasePath, bool skipSummaryInfo) { string modularizationGuid = null; Output output = new Output(SourceLineNumberCollection.FromFileName(databaseFile)); View validationView = null; // set the output type output.Type = outputType; // get the codepage database.Export("_ForceCodepage", this.TempFilesLocation, "_ForceCodepage.idt"); using (StreamReader sr = File.OpenText(Path.Combine(this.TempFilesLocation, "_ForceCodepage.idt"))) { string line; while (null != (line = sr.ReadLine())) { string[] data = line.Split('\t'); if (2 == data.Length) { output.Codepage = Convert.ToInt32(data[0], CultureInfo.InvariantCulture); } } } // get the summary information table if it exists; it won't if unbinding a transform if (!skipSummaryInfo) { using (SummaryInformation summaryInformation = new SummaryInformation(database)) { Table table = new Table(null, this.tableDefinitions["_SummaryInformation"]); for (int i = 1; 19 >= i; i++) { string value = summaryInformation.GetProperty(i); if (0 < value.Length) { Row row = table.CreateRow(output.SourceLineNumbers); row[0] = i; row[1] = value; } } output.Tables.Add(table); } } try { // open a view on the validation table if it exists if (database.TableExists("_Validation")) { validationView = database.OpenView("SELECT * FROM `_Validation` WHERE `Table` = ? AND `Column` = ?"); } // get the normal tables using (View tablesView = database.OpenExecuteView("SELECT * FROM _Tables")) { while (true) { using (Record tableRecord = tablesView.Fetch()) { if (null == tableRecord) { break; } string tableName = tableRecord.GetString(1); using (View tableView = database.OpenExecuteView(String.Format(CultureInfo.InvariantCulture, "SELECT * FROM `{0}`", tableName))) { TableDefinition tableDefinition = new TableDefinition(tableName, false, false); Hashtable tablePrimaryKeys = new Hashtable(); using (Record columnNameRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFONAMES), columnTypeRecord = tableView.GetColumnInfo(MsiInterop.MSICOLINFOTYPES)) { int columnCount = columnNameRecord.GetFieldCount(); // index the primary keys using (Record primaryKeysRecord = database.PrimaryKeys(tableName)) { int primaryKeysFieldCount = primaryKeysRecord.GetFieldCount(); for (int i = 1; i <= primaryKeysFieldCount; i++) { tablePrimaryKeys[primaryKeysRecord.GetString(i)] = null; } } for (int i = 1; i <= columnCount; i++) { string columnName = columnNameRecord.GetString(i); string idtType = columnTypeRecord.GetString(i); ColumnType columnType; int length; bool nullable; ColumnCategory columnCategory = ColumnCategory.Unknown; ColumnModularizeType columnModularizeType = ColumnModularizeType.None; bool primary = tablePrimaryKeys.Contains(columnName); bool minValueSet = false; int minValue = -1; bool maxValueSet = false; int maxValue = -1; string keyTable = null; bool keyColumnSet = false; int keyColumn = -1; string category = null; string set = null; string description = null; // get the column type, length, and whether its nullable switch (Char.ToLower(idtType[0], CultureInfo.InvariantCulture)) { case 'i': columnType = ColumnType.Number; break; case 'l': columnType = ColumnType.Localized; break; case 's': columnType = ColumnType.String; break; case 'v': columnType = ColumnType.Object; break; default: // TODO: error columnType = ColumnType.Unknown; break; } length = Convert.ToInt32(idtType.Substring(1), CultureInfo.InvariantCulture); nullable = Char.IsUpper(idtType[0]); // try to get validation information if (null != validationView) { using (Record validationRecord = new Record(2)) { validationRecord.SetString(1, tableName); validationRecord.SetString(2, columnName); validationView.Execute(validationRecord); } using (Record validationRecord = validationView.Fetch()) { if (null != validationRecord) { string validationNullable = validationRecord.GetString(3); minValueSet = !validationRecord.IsNull(4); minValue = (minValueSet ? validationRecord.GetInteger(4) : -1); maxValueSet = !validationRecord.IsNull(5); maxValue = (maxValueSet ? validationRecord.GetInteger(5) : -1); keyTable = (!validationRecord.IsNull(6) ? validationRecord.GetString(6) : null); keyColumnSet = !validationRecord.IsNull(7); keyColumn = (keyColumnSet ? validationRecord.GetInteger(7) : -1); category = (!validationRecord.IsNull(8) ? validationRecord.GetString(8) : null); set = (!validationRecord.IsNull(9) ? validationRecord.GetString(9) : null); description = (!validationRecord.IsNull(10) ? validationRecord.GetString(10) : null); // check the validation nullable value against the column definition if (null == validationNullable) { // TODO: warn for illegal validation nullable column } else if ((nullable && "Y" != validationNullable) || (!nullable && "N" != validationNullable)) { // TODO: warn for mismatch between column definition and validation nullable } // convert category to ColumnCategory if (null != category) { try { columnCategory = (ColumnCategory)Enum.Parse(typeof(ColumnCategory), category, true); } catch (ArgumentException) { columnCategory = ColumnCategory.Unknown; } } } else { // TODO: warn about no validation information } } } // guess the modularization type if ("Icon" == keyTable && 1 == keyColumn) { columnModularizeType = ColumnModularizeType.Icon; } else if ("Condition" == columnName) { columnModularizeType = ColumnModularizeType.Condition; } else if (ColumnCategory.Formatted == columnCategory || ColumnCategory.FormattedSDDLText == columnCategory) { columnModularizeType = ColumnModularizeType.Property; } else if (ColumnCategory.Identifier == columnCategory) { columnModularizeType = ColumnModularizeType.Column; } tableDefinition.Columns.Add(new ColumnDefinition(columnName, columnType, length, primary, nullable, columnModularizeType, (ColumnType.Localized == columnType), minValueSet, minValue, maxValueSet, maxValue, keyTable, keyColumnSet, keyColumn, columnCategory, set, description, true, true)); } } // use our table definitions if core properties are the same; this allows us to take advantage // of wix concepts like localizable columns which current code assumes if (this.tableDefinitions.Contains(tableName) && 0 == tableDefinition.CompareTo(this.tableDefinitions[tableName])) { tableDefinition = this.tableDefinitions[tableName]; } Table table = new Table(null, tableDefinition); while (true) { using (Record rowRecord = tableView.Fetch()) { if (null == rowRecord) { break; } int recordCount = rowRecord.GetFieldCount(); Row row = table.CreateRow(output.SourceLineNumbers); for (int i = 0; recordCount > i && row.Fields.Length > i; i++) { if (rowRecord.IsNull(i + 1)) { if (!row.Fields[i].Column.IsNullable) { // TODO: display an error for a null value in a non-nullable field OR // display a warning and put an empty string in the value to let the compiler handle it // (the second option is risky because the later code may make certain assumptions about // the contents of a row value) } } else { switch (row.Fields[i].Column.Type) { case ColumnType.Number: bool success = false; int intValue = rowRecord.GetInteger(i + 1); if (row.Fields[i].Column.IsLocalizable) { success = row.BestEffortSetField(i, Convert.ToString(intValue, CultureInfo.InvariantCulture)); } else { success = row.BestEffortSetField(i, intValue); } if (!success) { this.OnMessage(WixWarnings.BadColumnDataIgnored(row.SourceLineNumbers, Convert.ToString(intValue, CultureInfo.InvariantCulture), tableName, row.Fields[i].Column.Name)); } break; case ColumnType.Object: string sourceFile = "FILE NOT EXPORTED, USE THE dark.exe -x OPTION TO EXPORT BINARIES"; if (null != exportBasePath) { string relativeSourceFile = Path.Combine(tableName, row.GetPrimaryKey('.')); sourceFile = Path.Combine(exportBasePath, relativeSourceFile); // ensure the parent directory exists System.IO.Directory.CreateDirectory(Path.Combine(exportBasePath, tableName)); using (FileStream fs = System.IO.File.Create(sourceFile)) { int bytesRead; byte[] buffer = new byte[512]; while (0 != (bytesRead = rowRecord.GetStream(i + 1, buffer, buffer.Length))) { fs.Write(buffer, 0, bytesRead); } } } row[i] = sourceFile; break; default: string value = rowRecord.GetString(i + 1); switch (row.Fields[i].Column.Category) { case ColumnCategory.Guid: value = value.ToUpper(CultureInfo.InvariantCulture); break; } // de-modularize if (!this.suppressDemodularization && OutputType.Module == output.Type && ColumnModularizeType.None != row.Fields[i].Column.ModularizeType) { Regex modularization = new Regex(@"\.[0-9A-Fa-f]{8}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{4}_[0-9A-Fa-f]{12}"); if (null == modularizationGuid) { Match match = modularization.Match(value); if (match.Success) { modularizationGuid = String.Concat('{', match.Value.Substring(1).Replace('_', '-'), '}'); } } value = modularization.Replace(value, String.Empty); } // escape "$(" for the preprocessor value = value.Replace("$(", "$$("); // escape things that look like wix variables MatchCollection matches = Common.WixVariableRegex.Matches(value); for (int j = matches.Count - 1; 0 <= j; j--) { value = value.Insert(matches[j].Index, "!"); } row[i] = value; break; } } } } } output.Tables.Add(table); } } } } } finally { if (null != validationView) { validationView.Close(); } } // set the modularization guid as the PackageCode if (null != modularizationGuid) { Table table = output.Tables["_SummaryInformation"]; foreach (Row row in table.Rows) { if (9 == (int)row[0]) // PID_REVNUMBER { row[1] = modularizationGuid; } } } if (this.isAdminImage) { GenerateWixFileTable(databaseFile, output); GenerateSectionIds(output); } return output; }
/// <summary> /// Adds a row to the media table with cab name template filled in. /// </summary> /// <param name="mediaTable"></param> /// <param name="cabIndex"></param> /// <returns></returns> private MediaRow AddMediaRow(Table mediaTable, int cabIndex, string compressionLevel) { MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(null); currentMediaRow.DiskId = cabIndex; currentMediaRow.Cabinet = String.Format(this.cabinetNameTemplate, cabIndex); this.mediaRows.Add(currentMediaRow); this.cabinets.Add(currentMediaRow, new FileRowCollection()); Table wixMediaTable = this.output.EnsureTable(this.core.TableDefinitions["WixMedia"]); Row row = wixMediaTable.CreateRow(null); row[0] = cabIndex; row[1] = compressionLevel; return currentMediaRow; }
/// <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> /// Adds a row to the media table with cab name template filled in. /// </summary> /// <param name="mediaTable"></param> /// <param name="cabIndex"></param> /// <returns></returns> private MediaRow AddMediaRow(Table mediaTable, int cabIndex) { MediaRow currentMediaRow = (MediaRow)mediaTable.CreateRow(null); currentMediaRow.DiskId = cabIndex; mediaRows.Add(currentMediaRow); currentMediaRow.Cabinet = String.Format(this.cabinetNameTemplate, cabIndex); cabinets.Add(currentMediaRow, new FileRowCollection()); return currentMediaRow; }
/// <summary> /// Creates a Row from the XmlReader. /// </summary> /// <param name="reader">Reader to get data from.</param> /// <param name="table">Table for this row.</param> /// <returns>New row object.</returns> internal static Row Parse(XmlReader reader, Table table) { Debug.Assert("row" == reader.LocalName); bool empty = reader.IsEmptyElement; RowOperation operation = RowOperation.None; string sectionId = null; SourceLineNumberCollection sourceLineNumbers = null; while (reader.MoveToNextAttribute()) { switch (reader.LocalName) { case "op": switch (reader.Value) { case "add": operation = RowOperation.Add; break; case "delete": operation = RowOperation.Delete; break; case "modify": operation = RowOperation.Modify; break; default: throw new WixException(WixErrors.IllegalAttributeValue(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name, reader.Value, "Add", "Delete", "Modify")); } break; case "sectionId": sectionId = reader.Value; break; case "sourceLineNumber": sourceLineNumbers = new SourceLineNumberCollection(reader.Value); break; default: if (!reader.NamespaceURI.StartsWith("http://www.w3.org/", StringComparison.Ordinal)) { throw new WixException(WixErrors.UnexpectedAttribute(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name)); } break; } } Row row = table.CreateRow(sourceLineNumbers); row.Operation = operation; row.SectionId = sectionId; // loop through all the fields in a row if (!empty) { bool done = false; int field = 0; // loop through all the fields in a row while (!done && reader.Read()) { switch (reader.NodeType) { case XmlNodeType.Element: switch (reader.LocalName) { case "field": if (row.Fields.Length <= field) { if (!reader.IsEmptyElement) { throw new WixException(WixErrors.UnexpectedColumnCount(SourceLineNumberCollection.FromUri(reader.BaseURI), table.Name)); } } else { row.fields[field].Parse(reader); } ++field; break; default: throw new WixException(WixErrors.UnexpectedElement(SourceLineNumberCollection.FromUri(reader.BaseURI), "row", reader.Name)); } break; case XmlNodeType.EndElement: done = true; break; } } if (!done) { throw new WixException(WixErrors.ExpectedEndElement(SourceLineNumberCollection.FromUri(reader.BaseURI), "row")); } } return(row); }
/// <summary> /// Include transforms in a patch. /// </summary> /// <param name="transforms">List of transforms to attach.</param> public void AttachTransforms(ArrayList transforms) { int emptyTransform = 0; if (transforms == null || transforms.Count == 0) { throw new WixException(WixErrors.PatchWithoutTransforms()); } // Get the patch id from the WixPatchId table. string patchId = null; Table wixPatchIdTable = this.patch.Tables["WixPatchId"]; if (null != wixPatchIdTable && 0 < wixPatchIdTable.Rows.Count) { Row patchIdRow = wixPatchIdTable.Rows[0]; if (null != patchIdRow) { patchId = patchIdRow[0].ToString(); } } // enumerate patch.Media to map diskId to Media row Hashtable mediaRows = new Hashtable(); Table patchMediaTable = patch.Tables["Media"]; if (patchMediaTable != null) { foreach (MediaRow row in patchMediaTable.Rows) { int media = row.DiskId; mediaRows[media] = row; } } // enumerate patch.WixPatchBaseline to map baseline to diskId Hashtable baselineMedia = new Hashtable(); Table patchBaselineTable = patch.Tables["WixPatchBaseline"]; if (patchBaselineTable != null) { foreach (Row row in patchBaselineTable.Rows) { string baseline = (string)row[0]; int media = (int)row[1]; if (baselineMedia.Contains(baseline)) { throw new InvalidOperationException(String.Format("PatchBaseline '{0}' authored into multiple Media.", baseline)); } baselineMedia[baseline] = media; } } // enumerate transforms ArrayList productCodes = new ArrayList(); ArrayList transformNames = new ArrayList(); int transformCount = 0; foreach (PatchTransform mainTransform in transforms) { string baseline = null; int media = -1; if (baselineMedia.Contains(mainTransform.Baseline)) { int newMedia = (int)baselineMedia[mainTransform.Baseline]; if (media != -1 && media != newMedia) { throw new InvalidOperationException(String.Format("Transform authored into multiple Media '{0}' and '{1}'.", media, newMedia)); } baseline = mainTransform.Baseline; media = newMedia; } if (media == -1) { // transform's baseline not attached to any Media continue; } Table patchRefTable = patch.Tables["WixPatchRef"]; if (patchRefTable != null && patchRefTable.Rows.Count > 0) { if (!this.ReduceTransform(mainTransform.Transform, patchRefTable)) { // transform has none of the content authored into this patch emptyTransform++; continue; } } // ensure consistent File.Sequence within each Media MediaRow mediaRow = (MediaRow)mediaRows[media]; // TODO: should this be authored rather than inferring it from DiskId? mediaRow.LastSequence = mediaRow.DiskId; // ignore media table from transform. mainTransform.Transform.Tables.Remove("Media"); mainTransform.Transform.Tables.Remove("WixMedia"); mainTransform.Transform.Tables.Remove("MsiDigitalSignature"); string productCode = null; Output pairedTransform = this.BuildPairedTransform(patchId, mainTransform.Transform, mediaRow, ref productCode); productCodes.Add(productCode); // attach these transforms to the patch object // TODO: is this an acceptable way to auto-generate transform stream names? string transformName = baseline + "." + (++transformCount).ToString(); patch.SubStorages.Add(new SubStorage(transformName, mainTransform.Transform)); patch.SubStorages.Add(new SubStorage("#" + transformName, pairedTransform)); transformNames.Add(":" + transformName); transformNames.Add(":#" + transformName); } if (emptyTransform == transforms.Count) { throw new WixException(WixErrors.PatchWithoutValidTransforms()); } // populate MSP summary information Table patchSummaryInfo = patch.EnsureTable(this.tableDefinitions["_SummaryInformation"]); // remove any existing data for these fields for (int i = patchSummaryInfo.Rows.Count - 1; i >= 0; i--) { Row row = patchSummaryInfo.Rows[i]; switch ((SummaryInformation.Patch)row[0]) { case SummaryInformation.Patch.ProductCodes: case SummaryInformation.Patch.TransformNames: case SummaryInformation.Patch.PatchCode: case SummaryInformation.Patch.InstallerRequirement: patchSummaryInfo.Rows.RemoveAt(i); break; } } // Semicolon delimited list of the product codes that can accept the patch. Row templateRow = patchSummaryInfo.CreateRow(null); templateRow[0] = (int)SummaryInformation.Patch.ProductCodes; templateRow[1] = String.Join(";", (string[])productCodes.ToArray(typeof(string))); // Semicolon delimited list of transform substorage names in the order they are applied. Row savedbyRow = patchSummaryInfo.CreateRow(null); savedbyRow[0] = (int)SummaryInformation.Patch.TransformNames; savedbyRow[1] = String.Join(";", (string[])transformNames.ToArray(typeof(string))); // GUID patch code for the patch. Row revisionRow = patchSummaryInfo.CreateRow(null); revisionRow[0] = (int)SummaryInformation.Patch.PatchCode; revisionRow[1] = patchId; // Indicates the minimum Windows Installer version that is required to install the patch. Row wordsRow = patchSummaryInfo.CreateRow(null); wordsRow[0] = (int)SummaryInformation.Patch.InstallerRequirement; wordsRow[1] = ((int)SummaryInformation.InstallerRequirement.Version31).ToString(); Row security = patchSummaryInfo.CreateRow(null); security[0] = 19; //PID_SECURITY security[1] = "4"; Table msiPatchMetadataTable = patch.Tables["MsiPatchMetadata"]; if (null != msiPatchMetadataTable) { Hashtable metadataTable = new Hashtable(); foreach (Row row in msiPatchMetadataTable.Rows) { metadataTable.Add(row.Fields[1].Data.ToString(), row.Fields[2].Data.ToString()); } if (metadataTable.Contains("DisplayName")) { string comment = String.Concat("This patch contains the logic and data required to install ", metadataTable["DisplayName"]); Row title = patchSummaryInfo.CreateRow(null); title[0] = 2; //PID_TITLE title[1] = metadataTable["DisplayName"]; Row comments = patchSummaryInfo.CreateRow(null); comments[0] = 6; //PID_COMMENTS comments[1] = comment; } if (metadataTable.Contains("CodePage")) { Row codePage = patchSummaryInfo.CreateRow(null); codePage[0] = 1; //PID_CODEPAGE codePage[1] = metadataTable["CodePage"]; } if (metadataTable.Contains("Description")) { Row subject = patchSummaryInfo.CreateRow(null); subject[0] = 3; //PID_SUBJECT subject[1] = metadataTable["Description"]; } if (metadataTable.Contains("ManufacturerName")) { Row author = patchSummaryInfo.CreateRow(null); author[0] = 4; //PID_AUTHOR author[1] = metadataTable["ManufacturerName"]; } } }