public void Execute() { var transformFlags = 0; var targetOutput = new WindowsInstallerData(null); var updatedOutput = new WindowsInstallerData(null); // TODO: handle added columns // to generate a localized transform, both the target and updated // databases need to have the same code page. the only reason to // set different code pages is to support localized primary key // columns, but that would only support deleting rows. if this // becomes necessary, define a PreviousCodepage property on the // Output class and persist this throughout transform generation. targetOutput.Codepage = this.Transform.Codepage; updatedOutput.Codepage = this.Transform.Codepage; // remove certain Property rows which will be populated from summary information values string targetUpgradeCode = null; string updatedUpgradeCode = null; if (this.Transform.TryGetTable("Property", out var propertyTable)) { for (int i = propertyTable.Rows.Count - 1; i >= 0; i--) { Row row = propertyTable.Rows[i]; if ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]) { propertyTable.Rows.RemoveAt(i); if ("UpgradeCode" == (string)row[0]) { updatedUpgradeCode = (string)row[1]; } } } } var targetSummaryInfo = targetOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); var updatedSummaryInfo = updatedOutput.EnsureTable(this.TableDefinitions["_SummaryInformation"]); var targetPropertyTable = targetOutput.EnsureTable(this.TableDefinitions["Property"]); var updatedPropertyTable = updatedOutput.EnsureTable(this.TableDefinitions["Property"]); // process special summary information values foreach (var row in this.Transform.Tables["_SummaryInformation"].Rows) { var summaryId = row.FieldAsInteger(0); var summaryData = row.FieldAsString(1); if ((int)SummaryInformation.Transform.CodePage == summaryId) { // convert from a web name if provided var codePage = summaryData; if (null == codePage) { codePage = "0"; } else { codePage = Common.GetValidCodePage(codePage).ToString(CultureInfo.InvariantCulture); } var previousCodePage = row.Fields[1].PreviousData; if (null == previousCodePage) { previousCodePage = "0"; } else { previousCodePage = Common.GetValidCodePage(previousCodePage).ToString(CultureInfo.InvariantCulture); } var targetCodePageRow = targetSummaryInfo.CreateRow(null); targetCodePageRow[0] = 1; // PID_CODEPAGE targetCodePageRow[1] = previousCodePage; var updatedCodePageRow = updatedSummaryInfo.CreateRow(null); updatedCodePageRow[0] = 1; // PID_CODEPAGE updatedCodePageRow[1] = codePage; } else if ((int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId || (int)SummaryInformation.Transform.UpdatedPlatformAndLanguage == summaryId) { // the target language var propertyData = summaryData.Split(';'); var lang = 2 == propertyData.Length ? propertyData[1] : "0"; var tempSummaryInfo = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetSummaryInfo : updatedSummaryInfo; var tempPropertyTable = (int)SummaryInformation.Transform.TargetPlatformAndLanguage == summaryId ? targetPropertyTable : updatedPropertyTable; var productLanguageRow = tempPropertyTable.CreateRow(null); productLanguageRow[0] = "ProductLanguage"; productLanguageRow[1] = lang; // set the platform;language on the MSI to be generated var templateRow = tempSummaryInfo.CreateRow(null); templateRow[0] = 7; // PID_TEMPLATE templateRow[1] = summaryData; } else if ((int)SummaryInformation.Transform.ProductCodes == summaryId) { var propertyData = summaryData.Split(';'); var targetProductCodeRow = targetPropertyTable.CreateRow(null); targetProductCodeRow[0] = "ProductCode"; targetProductCodeRow[1] = propertyData[0].Substring(0, 38); var targetProductVersionRow = targetPropertyTable.CreateRow(null); targetProductVersionRow[0] = "ProductVersion"; targetProductVersionRow[1] = propertyData[0].Substring(38); var updatedProductCodeRow = updatedPropertyTable.CreateRow(null); updatedProductCodeRow[0] = "ProductCode"; updatedProductCodeRow[1] = propertyData[1].Substring(0, 38); var updatedProductVersionRow = updatedPropertyTable.CreateRow(null); updatedProductVersionRow[0] = "ProductVersion"; updatedProductVersionRow[1] = propertyData[1].Substring(38); // UpgradeCode is optional and may not exists in the target // or upgraded databases, so do not include a null-valued // UpgradeCode property. targetUpgradeCode = propertyData[2]; if (!String.IsNullOrEmpty(targetUpgradeCode)) { var targetUpgradeCodeRow = targetPropertyTable.CreateRow(null); targetUpgradeCodeRow[0] = "UpgradeCode"; targetUpgradeCodeRow[1] = targetUpgradeCode; // If the target UpgradeCode is specified, an updated // UpgradeCode is required. if (String.IsNullOrEmpty(updatedUpgradeCode)) { updatedUpgradeCode = targetUpgradeCode; } } if (!String.IsNullOrEmpty(updatedUpgradeCode)) { var updatedUpgradeCodeRow = updatedPropertyTable.CreateRow(null); updatedUpgradeCodeRow[0] = "UpgradeCode"; updatedUpgradeCodeRow[1] = updatedUpgradeCode; } } else if ((int)SummaryInformation.Transform.ValidationFlags == summaryId) { transformFlags = Convert.ToInt32(summaryData, CultureInfo.InvariantCulture); } else if ((int)SummaryInformation.Transform.Reserved11 == summaryId) { // PID_LASTPRINTED should be null for transforms row.Operation = RowOperation.None; } else { // add everything else as is var targetRow = targetSummaryInfo.CreateRow(null); targetRow[0] = row[0]; targetRow[1] = row[1]; var updatedRow = updatedSummaryInfo.CreateRow(null); updatedRow[0] = row[0]; updatedRow[1] = row[1]; } } // Validate that both databases have an UpgradeCode if the // authoring transform will validate the UpgradeCode; otherwise, // MsiCreateTransformSummaryinfo() will fail with 1620. if (((int)TransformFlags.ValidateUpgradeCode & transformFlags) != 0 && (String.IsNullOrEmpty(targetUpgradeCode) || String.IsNullOrEmpty(updatedUpgradeCode))) { this.Messaging.Write(ErrorMessages.BothUpgradeCodesRequired()); } string emptyFile = null; foreach (var table in this.Transform.Tables) { // Ignore unreal tables when building transforms except the _Stream table. // These tables are ignored when generating the database so there is no reason // to process them here. if (table.Definition.Unreal && "_Streams" != table.Name) { continue; } // process table operations switch (table.Operation) { case TableOperation.Add: updatedOutput.EnsureTable(table.Definition); break; case TableOperation.Drop: targetOutput.EnsureTable(table.Definition); continue; default: targetOutput.EnsureTable(table.Definition); updatedOutput.EnsureTable(table.Definition); break; } // process row operations foreach (var row in table.Rows) { switch (row.Operation) { case RowOperation.Add: var updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(row); continue; case RowOperation.Delete: var targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(row); // fill-in non-primary key values foreach (var field in row.Fields) { if (!field.Column.PrimaryKey) { if (ColumnType.Number == field.Column.Type && !field.Column.IsLocalizable) { field.Data = field.Column.MinValue; } else if (ColumnType.Object == field.Column.Type) { if (null == emptyFile) { emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } field.Data = emptyFile; } else { field.Data = "0"; } } } continue; } // Assure that the file table's sequence is populated if ("File" == table.Name) { foreach (var fileRow in table.Rows) { if (null == fileRow[7]) { if (RowOperation.Add == fileRow.Operation) { this.Messaging.Write(ErrorMessages.InvalidAddedFileRowWithoutSequence(fileRow.SourceLineNumbers, (string)fileRow[0])); break; } // Set to 1 to prevent invalid IDT file from being generated fileRow[7] = 1; } } } // process modified and unmodified rows var modifiedRow = false; var targetRow = table.Definition.CreateRow(null); var updatedRow = row; for (var i = 0; i < row.Fields.Length; i++) { var updatedField = row.Fields[i]; if (updatedField.Modified) { // set a different value in the target row to ensure this value will be modified during transform generation if (ColumnType.Number == updatedField.Column.Type && !updatedField.Column.IsLocalizable) { var data = updatedField.AsNullableInteger(); targetRow[i] = (data == 1) ? 2 : 1; } else if (ColumnType.Object == updatedField.Column.Type) { if (null == emptyFile) { emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } targetRow[i] = emptyFile; } else { var data = updatedField.AsString(); targetRow[i] = (data == "0") ? "1" : "0"; } modifiedRow = true; } else if (ColumnType.Object == updatedField.Column.Type) { var objectField = (ObjectField)updatedField; // create an empty file for comparing against if (null == objectField.PreviousData) { if (null == emptyFile) { emptyFile = Path.Combine(this.IntermediateFolder, "empty"); } targetRow[i] = emptyFile; modifiedRow = true; } else if (!this.FileSystemManager.CompareFiles(objectField.PreviousData, (string)objectField.Data)) { targetRow[i] = objectField.PreviousData; modifiedRow = true; } } else // unmodified { if (null != updatedField.Data) { targetRow[i] = updatedField.Data; } } } // modified rows and certain special rows go in the target and updated msi databases if (modifiedRow || ("Property" == table.Name && ("ProductCode" == (string)row[0] || "ProductLanguage" == (string)row[0] || "ProductVersion" == (string)row[0] || "UpgradeCode" == (string)row[0]))) { var targetTable = targetOutput.EnsureTable(table.Definition); targetTable.Rows.Add(targetRow); var updatedTable = updatedOutput.EnsureTable(table.Definition); updatedTable.Rows.Add(updatedRow); } } } //foreach (BinderExtension extension in this.Extensions) //{ // extension.PostBind(this.Context); //} // Any errors encountered up to this point can cause errors during generation. if (this.Messaging.EncounteredError) { return; } var transformFileName = Path.GetFileNameWithoutExtension(this.OutputPath); var targetDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_target.msi")); var updatedDatabaseFile = Path.Combine(this.IntermediateFolder, String.Concat(transformFileName, "_updated.msi")); try { if (!String.IsNullOrEmpty(emptyFile)) { using (var fileStream = File.Create(emptyFile)) { } } this.GenerateDatabase(targetOutput, targetDatabaseFile, keepAddedColumns: false); this.GenerateDatabase(updatedOutput, updatedDatabaseFile, keepAddedColumns: true); // make sure the directory exists Directory.CreateDirectory(Path.GetDirectoryName(this.OutputPath)); // create the transform file using (var targetDatabase = new Database(targetDatabaseFile, OpenDatabase.ReadOnly)) using (var updatedDatabase = new Database(updatedDatabaseFile, OpenDatabase.ReadOnly)) { if (updatedDatabase.GenerateTransform(targetDatabase, this.OutputPath)) { updatedDatabase.CreateTransformSummaryInfo(targetDatabase, this.OutputPath, (TransformErrorConditions)(transformFlags & 0xFFFF), (TransformValidations)((transformFlags >> 16) & 0xFFFF)); } else { this.Messaging.Write(ErrorMessages.NoDifferencesInTransform(this.Transform.SourceLineNumbers)); } } } finally { if (!String.IsNullOrEmpty(emptyFile)) { File.Delete(emptyFile); } } }