Esempio n. 1
0
        public void FillFromPayloadRow(Output output, Row payloadRow)
        {
            SourceLineNumberCollection sourceLineNumbers = payloadRow.SourceLineNumbers;

            this[0] = payloadRow[0];
            this[1] = payloadRow[1];
            this[2] = (string)payloadRow[2] ?? String.Empty;
            this[3] = payloadRow[3];
            this[4] = payloadRow[4];
            this[5] = payloadRow[5] ?? String.Empty;
            this[6] = payloadRow[6];

            // payload files sourced from a cabinet (think WixExtension with embedded binary wixlib) are considered "non-content files".
            ObjectField field = (ObjectField)payloadRow.Fields[2];

            this.ContentFile = String.IsNullOrEmpty(field.CabinetFileId);

            ResolvePayloadInfo(this);

            return;
        }
Esempio n. 2
0
        /// <summary>
        /// Saves an output to a path on disk.
        /// </summary>
        /// <param name="path">Path to save output file to on disk.</param>
        /// <param name="binderExtension">If provided, the binder extension is used to bind files into the output.</param>
        /// <param name="wixVariableResolver">The Wix variable resolver.</param>
        /// <param name="tempFilesLocation">Location for temporary files.</param>
        public void Save(string path, BinderExtension binderExtension, WixVariableResolver wixVariableResolver, string tempFilesLocation)
        {
            FileMode fileMode = FileMode.Create;

            // Check if there was a cab on the wixout when it was created
            if (null != this.cabPath)
            {
                // There was already a cab on the wixout when it was loaded. Reuse that one.
                File.Copy(this.cabPath, path, true);
                if (null != this.tempFileCollection)
                {
                    this.tempFileCollection.Delete();
                }
                fileMode = FileMode.Append;
            }
            else
            {
                Hashtable        cabinets = new Hashtable();
                StringCollection fileIds  = new StringCollection();
                StringCollection files    = new StringCollection();
                int index = 0;

                // resolve paths to files and create the output cabinet file
                foreach (Section section in this.sections)
                {
                    foreach (Table table in section.Tables)
                    {
                        foreach (Row row in table.Rows)
                        {
                            foreach (Field field in row.Fields)
                            {
                                ObjectField objectField = field as ObjectField;

                                if (null != objectField && null != objectField.Data)
                                {
                                    string file      = null;
                                    bool   isDefault = true;

                                    // resolve localization and wix variables
                                    objectField.Data = wixVariableResolver.ResolveVariables(null, row.SourceLineNumbers, (string)objectField.Data, false, ref isDefault);

                                    // do not save the output if errors were found while resolving object paths
                                    if (wixVariableResolver.EncounteredError)
                                    {
                                        return;
                                    }

                                    // file is compressed in a cabinet (and not modified above)
                                    if (null != objectField.CabinetFileId && isDefault)
                                    {
                                        // index cabinets that have not been previously encountered
                                        if (!cabinets.ContainsKey(objectField.BaseUri))
                                        {
                                            Uri    baseUri = new Uri(objectField.BaseUri);
                                            string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(baseUri.LocalPath);
                                            string extractedDirectoryName        = String.Format(CultureInfo.InvariantCulture, "cab_{0}_{1}", cabinets.Count, localFileNameWithoutExtension);

                                            // index the cabinet file's base URI (source location) and extracted directory
                                            cabinets.Add(objectField.BaseUri, Path.Combine(tempFilesLocation, extractedDirectoryName));
                                        }

                                        // set the path to the file once its extracted from the cabinet
                                        file = Path.Combine((string)cabinets[objectField.BaseUri], objectField.CabinetFileId);
                                    }
                                    else if (null != binderExtension)
                                    {
                                        file = binderExtension.ResolveFile((string)objectField.Data);
                                    }

                                    // add the file to the list of files to go in the cabinet
                                    if (null != file)
                                    {
                                        string cabinetFileId = (index++).ToString(CultureInfo.InvariantCulture);

                                        objectField.CabinetFileId = cabinetFileId;
                                        fileIds.Add(cabinetFileId);

                                        files.Add(file);
                                    }
                                }
                            }
                        }
                    }
                }

                // extract files that come from cabinet files
                if (0 < cabinets.Count)
                {
                    // ensure the temporary directory exists
                    Directory.CreateDirectory(tempFilesLocation);

                    foreach (DictionaryEntry cabinet in cabinets)
                    {
                        Uri    baseUri = new Uri((string)cabinet.Key);
                        string localPath;

                        if ("embeddedresource" == baseUri.Scheme)
                        {
                            int    bytesRead;
                            byte[] buffer = new byte[512];

                            string   originalLocalPath = Path.GetFullPath(baseUri.LocalPath.Substring(1));
                            string   resourceName      = baseUri.Fragment.Substring(1);
                            Assembly assembly          = Assembly.LoadFile(originalLocalPath);

                            localPath = String.Concat(cabinet.Value, ".cab");

                            using (FileStream fs = File.OpenWrite(localPath))
                            {
                                using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
                                {
                                    while (0 < (bytesRead = resourceStream.Read(buffer, 0, buffer.Length)))
                                    {
                                        fs.Write(buffer, 0, bytesRead);
                                    }
                                }
                            }
                        }
                        else // normal file
                        {
                            localPath = baseUri.LocalPath;
                        }

                        // extract the cabinet's files into a temporary directory
                        Directory.CreateDirectory((string)cabinet.Value);

                        using (WixExtractCab extractCab = new WixExtractCab())
                        {
                            extractCab.Extract(localPath, (string)cabinet.Value);
                        }
                    }
                }

                // create the cabinet file
                if (0 < fileIds.Count)
                {
                    try
                    {
                        using (WixCreateCab cab = new WixCreateCab(Path.GetFileName(path), Path.GetDirectoryName(path), 0, 0, CompressionLevel.Mszip))
                        {
                            for (int i = 0; i < fileIds.Count; i++)
                            {
                                cab.AddFile(files[i], fileIds[i]);
                            }
                        }
                    }
                    catch (FileNotFoundException e)
                    {
                        throw new WixException(WixErrors.FileNotFound(null, e.FileName));
                    }

                    // append the output xml to the end of the newly created cabinet file
                    fileMode = FileMode.Append;
                }
            }

            // Assure the location to output the xml exists
            Directory.CreateDirectory(Path.GetDirectoryName(path));

            // save the xml
            using (FileStream fs = new FileStream(path, fileMode))
            {
                XmlWriter writer = null;

                try
                {
                    writer = new XmlTextWriter(fs, System.Text.Encoding.UTF8);

                    writer.WriteStartDocument();
                    this.Persist(writer);
                    writer.WriteEndDocument();
                }
                finally
                {
                    if (null != writer)
                    {
                        writer.Close();
                    }
                }
            }
        }
Esempio n. 3
0
        /// <summary>
        /// Saves a library to a path on disk.
        /// </summary>
        /// <param name="path">Path to save library file to on disk.</param>
        /// <param name="binderExtension">If provided, the binder extension is used to bind files into the library.</param>
        /// <param name="wixVariableResolver">The Wix variable resolver.</param>
        public void Save(string path, BinderExtension binderExtension, WixVariableResolver wixVariableResolver)
        {
            FileMode         fileMode = FileMode.Create;
            StringCollection fileIds  = new StringCollection();
            StringCollection files    = new StringCollection();
            int index = 0;

            // resolve paths to files and create the library cabinet file
            foreach (Section section in this.sections)
            {
                foreach (Table table in section.Tables)
                {
                    foreach (Row row in table.Rows)
                    {
                        foreach (Field field in row.Fields)
                        {
                            ObjectField objectField = field as ObjectField;

                            if (null != objectField)
                            {
                                if (null != binderExtension && null != objectField.Data)
                                {
                                    string cabinetFileId = (index++).ToString(CultureInfo.InvariantCulture);

                                    objectField.CabinetFileId = cabinetFileId;
                                    fileIds.Add(cabinetFileId);

                                    // resolve wix variables
                                    string resolvedValue = wixVariableResolver.ResolveVariables(null, row.SourceLineNumbers, (string)objectField.Data, false);

                                    files.Add(binderExtension.ResolveFile(resolvedValue));
                                }
                                else // clear out a previous cabinet file id value
                                {
                                    objectField.CabinetFileId = null;
                                }
                            }
                        }
                    }
                }
            }

            // do not save the library if errors were found while resolving object paths
            if (wixVariableResolver.EncounteredError)
            {
                return;
            }

            // create the cabinet file
            if (0 < fileIds.Count)
            {
                try
                {
                    using (WixCreateCab cab = new WixCreateCab(Path.GetFileName(path), Path.GetDirectoryName(path), 0, 0, CompressionLevel.Mszip))
                    {
                        for (int i = 0; i < fileIds.Count; i++)
                        {
                            cab.AddFile(files[i], fileIds[i]);
                        }
                    }
                }
                catch (FileNotFoundException e)
                {
                    throw new WixException(WixErrors.FileNotFound(null, e.FileName));
                }

                // append the library xml to the end of the newly created cabinet file
                fileMode = FileMode.Append;
            }

            // save the xml
            using (FileStream fs = new FileStream(path, fileMode))
            {
                XmlWriter writer = null;

                try
                {
                    writer = new XmlTextWriter(fs, System.Text.Encoding.UTF8);

                    writer.WriteStartDocument();
                    this.Persist(writer);
                    writer.WriteEndDocument();
                }
                finally
                {
                    if (null != writer)
                    {
                        writer.Close();
                    }
                }
            }
        }
Esempio n. 4
0
        /// <summary>
        /// Creates a transform by diffing two outputs.
        /// </summary>
        /// <param name="targetOutput">The target output.</param>
        /// <param name="updatedOutput">The updated output.</param>
        /// <returns>The transform.</returns>
        public Output Diff(Output targetOutput, Output updatedOutput)
        {
            Output transform = new Output(null);

            transform.Type     = OutputType.Transform;
            transform.Codepage = updatedOutput.Codepage;

            string targetProductCode     = null;
            string targetProductVersion  = null;
            string targetUpgradeCode     = null;
            string updatedProductCode    = null;
            string updatedProductVersion = null;

            // compare the codepages
            if (targetOutput.Codepage != updatedOutput.Codepage)
            {
                this.OnMessage(WixErrors.OutputCodepageMismatch(targetOutput.SourceLineNumbers, targetOutput.Codepage, updatedOutput.Codepage));
                if (null != updatedOutput.SourceLineNumbers)
                {
                    this.OnMessage(WixErrors.OutputCodepageMismatch2(updatedOutput.SourceLineNumbers));
                }
            }

            // compare the output types
            if (targetOutput.Type != updatedOutput.Type)
            {
                throw new WixException(WixErrors.OutputTypeMismatch(targetOutput.SourceLineNumbers, targetOutput.Type.ToString(), updatedOutput.Type.ToString()));
            }

            // compare the contents of the tables
            foreach (Table targetTable in targetOutput.Tables)
            {
                Table updatedTable = updatedOutput.Tables[targetTable.Name];

                // dropped tables
                if (null == updatedTable)
                {
                    Table droppedTable = transform.Tables.EnsureTable(null, targetTable.Definition);
                    droppedTable.Operation = TableOperation.Drop;
                }
                else // possibly modified tables
                {
                    SortedList updatedPrimaryKeys = new SortedList();
                    SortedList targetPrimaryKeys  = new SortedList();

                    // TODO compare the table definitions - they must be identical for the type, size, primary keys, etc...
                    if (targetTable.Definition.Columns.Count != updatedTable.Definition.Columns.Count)
                    {
                        throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Different numbers of columns for {0}.", targetTable.Name));
                    }

                    // index the target rows
                    foreach (Row row in targetTable.Rows)
                    {
                        string primaryKey = row.GetPrimaryKey('/');

                        if (null != primaryKey)
                        {
                            targetPrimaryKeys.Add(primaryKey, row);
                        }
                        else // use the string representation of the row as its primary key (it may not be unique)
                        {
                            // this is provided for compatibility with unreal tables with no primary key
                            // all real tables must specify at least one column as the primary key
                            targetPrimaryKeys[row.ToString()] = row;
                        }

                        if ("Property" == targetTable.Name)
                        {
                            if ("ProductCode" == (string)row[0])
                            {
                                targetProductCode = (string)row[1];
                            }
                            else if ("ProductVersion" == (string)row[0])
                            {
                                targetProductVersion = (string)row[1];
                            }
                            else if ("UpgradeCode" == (string)row[0])
                            {
                                targetUpgradeCode = (string)row[1];
                            }
                        }
                    }

                    // index the updated rows
                    foreach (Row row in updatedTable.Rows)
                    {
                        string primaryKey = row.GetPrimaryKey('/');

                        if (null != primaryKey)
                        {
                            updatedPrimaryKeys.Add(primaryKey, row);
                        }
                        else // use the string representation of the row as its primary key (it may not be unique)
                        {
                            // this is provided for compatibility with unreal tables with no primary key
                            // all real tables must specify at least one column as the primary key
                            updatedPrimaryKeys[row.ToString()] = row;
                        }

                        if ("Property" == targetTable.Name)
                        {
                            if ("ProductCode" == (string)row[0])
                            {
                                updatedProductCode = (string)row[1];
                            }
                            else if ("ProductVersion" == (string)row[0])
                            {
                                updatedProductVersion = (string)row[1];
                            }
                        }
                    }

                    // diff the target and updated rows
                    foreach (DictionaryEntry targetPrimaryKeyEntry in targetPrimaryKeys)
                    {
                        string targetPrimaryKey = (string)targetPrimaryKeyEntry.Key;
                        Row    targetRow        = (Row)targetPrimaryKeyEntry.Value;
                        Row    updatedRow       = (Row)updatedPrimaryKeys[targetPrimaryKey];

                        if (null == updatedRow) // deleted row
                        {
                            Table modifiedTable = transform.EnsureTable(targetTable.Definition);
                            targetRow.Operation = RowOperation.Delete;
                            targetRow.SectionId = targetRow.SectionId + sectionDelimiter;
                            modifiedTable.Rows.Add(targetRow);
                        }
                        else // possibly modified
                        {
                            updatedRow.Operation = RowOperation.None;
                            if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
                            {
                                Table table = transform.EnsureTable(updatedTable.Definition);
                                updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
                                table.Rows.Add(updatedRow);
                            }
                            else
                            {
                                bool keepRow = false;

                                if (this.preserveUnchangedRows)
                                {
                                    keepRow = true;
                                }

                                for (int i = 0; i < updatedRow.Fields.Length; i++)
                                {
                                    ColumnDefinition columnDefinition = updatedRow.Fields[i].Column;

                                    if (!columnDefinition.IsPrimaryKey)
                                    {
                                        bool modified = false;

                                        if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
                                        {
                                            if (null == targetRow[i] ^ null == updatedRow[i])
                                            {
                                                modified = true;
                                            }
                                            else if (null != targetRow[i] && null != updatedRow[i])
                                            {
                                                modified = ((int)targetRow[i] != (int)updatedRow[i]);
                                            }
                                        }
                                        else if (ColumnType.Object == columnDefinition.Type)
                                        {
                                            ObjectField targetObjectField  = (ObjectField)targetRow.Fields[i];
                                            ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i];

                                            if (null != targetObjectField.CabinetFileId)
                                            {
                                                // TODO: handle this
                                            }

                                            if ((string)targetObjectField.Data != (string)updatedObjectField.Data)
                                            {
                                                updatedObjectField.PreviousData = (string)targetObjectField.Data;
                                            }

                                            // keep rows containing object fields so the files can be compared in the binder
                                            keepRow = !this.suppressKeepingSpecialRows;
                                        }
                                        else
                                        {
                                            modified = ((string)targetRow[i] != (string)updatedRow[i]);
                                        }

                                        if (modified)
                                        {
                                            updatedRow.Fields[i].Modified = true;
                                            updatedRow.Operation          = RowOperation.Modify;
                                            keepRow = true;
                                        }
                                    }
                                }

                                if (keepRow)
                                {
                                    Table modifiedTable = transform.EnsureTable(updatedTable.Definition);
                                    updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
                                    modifiedTable.Rows.Add(updatedRow);
                                }
                            }
                        }
                    }

                    // find the inserted rows
                    foreach (DictionaryEntry updatedPrimaryKeyEntry in updatedPrimaryKeys)
                    {
                        string updatedPrimaryKey = (string)updatedPrimaryKeyEntry.Key;

                        if (!targetPrimaryKeys.Contains(updatedPrimaryKey))
                        {
                            Row updatedRow = (Row)updatedPrimaryKeyEntry.Value;

                            Table modifiedTable = transform.EnsureTable(updatedTable.Definition);
                            updatedRow.Operation = RowOperation.Add;
                            updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
                            modifiedTable.Rows.Add(updatedRow);
                        }
                    }
                }
            }

            // added tables
            foreach (Table updatedTable in updatedOutput.Tables)
            {
                if (null == targetOutput.Tables[updatedTable.Name])
                {
                    Table addedTable = transform.Tables.EnsureTable(null, updatedTable.Definition);
                    addedTable.Operation = TableOperation.Add;

                    foreach (Row updatedRow in updatedTable.Rows)
                    {
                        updatedRow.Operation = RowOperation.Add;
                        updatedRow.SectionId = sectionDelimiter + updatedRow.SectionId;
                        addedTable.Rows.Add(updatedRow);
                    }
                }
            }

            // create the PID_REVNUMBER summary information property
            if (!this.suppressKeepingSpecialRows)
            {
                foreach (Row row in transform.Tables["_SummaryInformation"].Rows)
                {
                    if (9 == (int)row[0])
                    {
                        row[1] = String.Concat(targetProductCode, targetProductVersion, ';', updatedProductCode, updatedProductVersion, ';', targetUpgradeCode);
                    }
                }
            }

            return(transform);
        }
Esempio n. 5
0
        /// <summary>
        /// Resolves paths to files.
        /// </summary>
        /// <param name="sectionTables">TableCollection of tables to process</param>
        /// <param name="binderFileManager">If provided, the binder file manager is used to bind files into the output.</param>
        /// <param name="wixVariableResolver">The Wix variable resolver.</param>
        /// <param name="tempFilesLocation">Location for temporary files.</param>
        /// <param name="cabinets">Hash of source cabinets.</param>
        /// <param name="fileIds">Collection of CabinetFileIds.</param>
        /// <param name="files">Collection of file paths from compressed files.</param>
        /// <param name="index">CabinetFileId generator.</param>
        public static void ResolveSectionFiles(TableCollection sectionTables, BinderFileManager binderFileManager, WixVariableResolver wixVariableResolver, string tempFilesLocation, Hashtable cabinets, StringCollection fileIds, StringCollection files, ref int index)
        {
            foreach (Table table in sectionTables)
            {
                foreach (Row row in table.Rows)
                {
                    foreach (Field field in row.Fields)
                    {
                        ObjectField objectField = field as ObjectField;

                        if (null != objectField && null != objectField.Data)
                        {
                            string file              = null;
                            string previousFile      = null;
                            bool   isDefault         = true;
                            bool   isPreviousDefault = true;

                            // resolve localization and wix variables if there is a file manager that would use the value
                            // if it was different, otherwise we just don't care so skip the whole variable resolution thing.
                            if (null != wixVariableResolver && null != binderFileManager)
                            {
                                objectField.Data = wixVariableResolver.ResolveVariables(row.SourceLineNumbers, (string)objectField.Data, false, ref isDefault);

                                if (null != objectField.PreviousData)
                                {
                                    objectField.PreviousData = wixVariableResolver.ResolveVariables(row.SourceLineNumbers, objectField.PreviousData, false, ref isPreviousDefault);
                                }

                                // do not save the output if errors were found while resolving object paths
                                if (wixVariableResolver.EncounteredError)
                                {
                                    return;
                                }
                            }

                            // file is compressed in a cabinet (and not modified above)
                            if (null != objectField.CabinetFileId && isDefault)
                            {
                                // index cabinets that have not been previously encountered
                                if (!cabinets.ContainsKey(objectField.BaseUri))
                                {
                                    Uri    baseUri = new Uri(objectField.BaseUri);
                                    string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(baseUri.LocalPath);
                                    string extractedDirectoryName        = String.Format(CultureInfo.InvariantCulture, "cab_{0}_{1}", cabinets.Count, localFileNameWithoutExtension);

                                    // index the cabinet file's base URI (source location) and extracted directory
                                    cabinets.Add(objectField.BaseUri, Path.Combine(tempFilesLocation, extractedDirectoryName));
                                }

                                // set the path to the file once its extracted from the cabinet
                                file = Path.Combine((string)cabinets[objectField.BaseUri], objectField.CabinetFileId);
                            }
                            else if (null != binderFileManager)
                            {
                                file = binderFileManager.ResolveFile((string)objectField.Data, table.Name, row.SourceLineNumbers, BindStage.Normal);
                            }

                            // add the file to the list of files to go in the cabinet
                            if (null != file)
                            {
                                string cabinetFileId = (index++).ToString(CultureInfo.InvariantCulture);

                                objectField.CabinetFileId = cabinetFileId;
                                fileIds.Add(cabinetFileId);

                                files.Add(file);
                            }

                            // previous file is compressed in a cabinet (and not modified above)
                            if (null != objectField.PreviousCabinetFileId && isPreviousDefault)
                            {
                                // index cabinets that have not been previously encountered
                                if (!cabinets.ContainsKey(objectField.PreviousBaseUri))
                                {
                                    Uri    baseUri = new Uri(objectField.PreviousBaseUri);
                                    string localFileNameWithoutExtension = Path.GetFileNameWithoutExtension(baseUri.LocalPath);
                                    string extractedDirectoryName        = String.Format(CultureInfo.InvariantCulture, "cab_{0}_{1}", cabinets.Count, localFileNameWithoutExtension);

                                    // index the cabinet file's base URI (source location) and extracted directory
                                    cabinets.Add(objectField.PreviousBaseUri, Path.Combine(tempFilesLocation, extractedDirectoryName));
                                }

                                // set the path to the file once its extracted from the cabinet
                                previousFile = Path.Combine((string)cabinets[objectField.PreviousBaseUri], objectField.PreviousCabinetFileId);
                            }
                            else if (null != objectField.PreviousData && null != binderFileManager)
                            {
                                previousFile = binderFileManager.ResolveFile((string)objectField.PreviousData, table.Name, row.SourceLineNumbers, BindStage.Normal);
                            }

                            // add the file to the list of files to go in the cabinet
                            if (null != previousFile)
                            {
                                string cabinetFileId = (index++).ToString(CultureInfo.InvariantCulture);

                                objectField.PreviousCabinetFileId = cabinetFileId;
                                fileIds.Add(cabinetFileId);

                                files.Add(previousFile);
                            }
                        }
                    }
                }
            }
        }
Esempio n. 6
0
        private Row CompareRows(Table targetTable, Row targetRow, Row updatedRow, out RowOperation operation, out bool keepRow)
        {
            Row comparedRow = null;

            keepRow   = false;
            operation = RowOperation.None;

            if (null == targetRow ^ null == updatedRow)
            {
                if (null == targetRow)
                {
                    operation   = updatedRow.Operation = RowOperation.Add;
                    comparedRow = updatedRow;
                }
                else if (null == updatedRow)
                {
                    operation           = targetRow.Operation = RowOperation.Delete;
                    targetRow.SectionId = targetRow.SectionId + sectionDelimiter;
                    comparedRow         = targetRow;
                    keepRow             = true;
                }
            }
            else // possibly modified
            {
                updatedRow.Operation = RowOperation.None;
                if (!this.suppressKeepingSpecialRows && "_SummaryInformation" == targetTable.Name)
                {
                    // ignore rows that shouldn't be in a transform
                    if (Enum.IsDefined(typeof(SummaryInformation.Transform), (int)updatedRow[0]))
                    {
                        updatedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
                        comparedRow          = updatedRow;
                        keepRow   = true;
                        operation = RowOperation.Modify;
                    }
                }
                else
                {
                    if (this.preserveUnchangedRows)
                    {
                        keepRow = true;
                    }

                    for (int i = 0; i < updatedRow.Fields.Length; i++)
                    {
                        ColumnDefinition columnDefinition = updatedRow.Fields[i].Column;

                        if (!columnDefinition.IsPrimaryKey)
                        {
                            bool modified = false;

                            if (i >= targetRow.Fields.Length)
                            {
                                columnDefinition.Added = true;
                                modified = true;
                            }
                            else if (ColumnType.Number == columnDefinition.Type && !columnDefinition.IsLocalizable)
                            {
                                if (null == targetRow[i] ^ null == updatedRow[i])
                                {
                                    modified = true;
                                }
                                else if (null != targetRow[i] && null != updatedRow[i])
                                {
                                    modified = ((int)targetRow[i] != (int)updatedRow[i]);
                                }
                            }
                            else if (ColumnType.Preserved == columnDefinition.Type)
                            {
                                updatedRow.Fields[i].PreviousData = (string)targetRow.Fields[i].Data;

                                // keep rows containing preserved fields so the historical data is available to the binder
                                keepRow = !this.suppressKeepingSpecialRows;
                            }
                            else if (ColumnType.Object == columnDefinition.Type)
                            {
                                ObjectField targetObjectField  = (ObjectField)targetRow.Fields[i];
                                ObjectField updatedObjectField = (ObjectField)updatedRow.Fields[i];

                                updatedObjectField.PreviousCabinetFileId = targetObjectField.CabinetFileId;
                                updatedObjectField.PreviousBaseUri       = targetObjectField.BaseUri;

                                // always keep a copy of the previous data even if they are identical
                                // This makes diff.wixmst clean and easier to control patch logic
                                updatedObjectField.PreviousData = (string)targetObjectField.Data;

                                // always remember the unresolved data for target build
                                updatedObjectField.UnresolvedPreviousData = (string)targetObjectField.UnresolvedData;

                                // keep rows containing object fields so the files can be compared in the binder
                                keepRow = !this.suppressKeepingSpecialRows;
                            }
                            else
                            {
                                modified = ((string)targetRow[i] != (string)updatedRow[i]);
                            }

                            if (modified)
                            {
                                if (null != updatedRow.Fields[i].PreviousData)
                                {
                                    updatedRow.Fields[i].PreviousData = targetRow.Fields[i].Data.ToString();
                                }

                                updatedRow.Fields[i].Modified = true;
                                operation = updatedRow.Operation = RowOperation.Modify;
                                keepRow   = true;
                            }
                        }
                    }

                    if (keepRow)
                    {
                        comparedRow           = updatedRow;
                        comparedRow.SectionId = targetRow.SectionId + sectionDelimiter + updatedRow.SectionId;
                    }
                }
            }

            return(comparedRow);
        }