// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. using System; using System.IO; using System.Collections; using System.Collections.Generic; using WixToolset.Dtf.WindowsInstaller; using WixToolset.Dtf.WindowsInstaller.Package; namespace WixToolset.Dtf.Tools.DDiff { public class MsiDiffEngine : IDiffEngine { public MsiDiffEngine() { } protected bool IsMsiDatabase(string file) { // TODO: use something smarter? switch(Path.GetExtension(file).ToLower()) { case ".msi": return true; case ".msm": return true; case ".pcp": return true; default : return false; } } protected bool IsMspPatch(string file) { // TODO: use something smarter? switch(Path.GetExtension(file).ToLower()) { case ".msp": return true; default : return false; } } public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) { if(diffInput1 != null && File.Exists(diffInput1) && diffInput2 != null && File.Exists(diffInput2) && (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2))) { return .70f; } else if(diffInput1 != null && File.Exists(diffInput1) && diffInput2 != null && File.Exists(diffInput2) && (IsMspPatch(diffInput1) || IsMspPatch(diffInput2))) { return .60f; } else { return 0; } } public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) { bool difference = false; Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly); Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly); if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; db1.Close(); db2.Close(); return difference; } protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) { bool difference = false; SummaryInfo summInfo1 = db1.SummaryInfo; SummaryInfo summInfo2 = db2.SummaryInfo; if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; } if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; } if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; } if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; } if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; } if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; } if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; } if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; } if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; } if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; } if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; } if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; } if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; } if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; } if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; } if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; } if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; } summInfo1.Close(); summInfo2.Close(); return difference; } protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) { bool difference = false; string tempFile = Path.GetTempFileName(); if(db2.GenerateTransform(db1, tempFile)) { difference = true; Database db = db1; db.ViewTransform(tempFile); string row, column, change; using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " + "FROM `_TransformView` ORDER BY `Table`, `Row`")) { view.Execute(); foreach (Record rec in view) using (rec) { column = String.Format("{0} {1}", rec[1], rec[2]); change = ""; if (rec.IsNull(3)) { row = "<DDL>"; if (!rec.IsNull(4)) { change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4)); } } else { row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]"; if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE") { column = String.Format("{0}.{1}", rec[1], rec[2]); change = "{" + rec[5] + "}->{" + rec[4] + "}"; } } diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change); } } } File.Delete(tempFile); return difference; } private string DecodeColDef(int colDef) { const int icdLong = 0x0000; const int icdShort = 0x0400; const int icdObject = 0x0800; const int icdString = 0x0C00; const int icdTypeMask = 0x0F00; const int icdNullable = 0x1000; const int icdPrimaryKey = 0x2000; string def = ""; switch(colDef & (icdTypeMask)) { case icdLong : def = "LONG"; break; case icdShort : def = "SHORT"; break; case icdObject: def = "OBJECT"; break; case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break; } if((colDef & icdNullable) != 0) { def = def + " NOT NULL"; } if((colDef & icdPrimaryKey) != 0) { def = def + " PRIMARY KEY"; } return def; } protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) { bool difference = false; IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); string[] streams1 = new string[streams1List.Count]; string[] streams2 = new string[streams2List.Count]; streams1List.CopyTo(streams1, 0); streams2List.CopyTo(streams2, 0); IComparer caseInsComp = CaseInsensitiveComparer.Default; Array.Sort(streams1, caseInsComp); Array.Sort(streams2, caseInsComp); for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; ) { int comp; if (i1 == streams1.Length) { comp = 1; } else if (i2 == streams2.Length) { comp = -1; } else { comp = caseInsComp.Compare(streams1[i1], streams2[i2]); } if(comp < 0) { diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]); i1++; difference = true; } else if(comp > 0) { diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]); i2++; difference = true; } else { if(streams1[i1] != ("" + ((char)5) + "SummaryInformation")) { string tempFile1 = Path.GetTempFileName(); string tempFile2 = Path.GetTempFileName(); using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1]))) { view.Execute(); using (Record rec = view.Fetch()) { rec.GetStream(1, tempFile1); } } using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2]))) { view.Execute(); using (Record rec = view.Fetch()) { rec.GetStream(1, tempFile2); } } IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options); StringWriter sw = new StringWriter(); if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory)) { diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]); diffOutput.Write(sw.ToString()); difference = true; } File.Delete(tempFile1); File.Delete(tempFile2); } i1++; i2++; } } return difference; } public virtual IDiffEngine Clone() { return new MsiDiffEngine(); } } }