Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

268 lines
7.5 KiB

  1. package MsiComp;
  2. use strict;
  3. use Carp;
  4. use FileHandle;
  5. use File::Temp;
  6. use Win32::OLE qw(in);
  7. use BinComp;
  8. use CabComp;
  9. sub new {
  10. my $class = shift;
  11. my $instance = {
  12. DIFF => '',
  13. ERR => ''
  14. };
  15. return bless $instance;
  16. }
  17. sub GetLastError {
  18. my $self = shift;
  19. if (!ref $self) {croak "Invalid object reference"}
  20. return $self->{ERR};
  21. }
  22. sub GetLastDiff {
  23. my $self = shift;
  24. if (!ref $self) {croak "Invalid object reference"}
  25. return $self->{DIFF};
  26. }
  27. # let's just pretend I am a macro
  28. sub CheckCOMErrorWrapper(&;$) {
  29. my $code = shift;
  30. my $rerror = shift;
  31. if (ref $code ne 'CODE') {confess "Invalid parameter to CheckCOMErrorWrapper"}
  32. &$code;
  33. if ( Win32::OLE->LastError() ) {
  34. print "COM error: ".Win32::OLE->LastError(). "\n";
  35. $$rerror = Win32::OLE->LastError() if $rerror;
  36. return;
  37. }
  38. return 1;
  39. }
  40. sub OpenMsiDb {
  41. my $self = shift;
  42. my $msi_file = shift;
  43. if (!ref $self) {confess "Invalid object reference"}
  44. my $msi_object;
  45. return if !CheckCOMErrorWrapper {$msi_object = Win32::OLE->new("WindowsInstaller.Installer")} \$self->{ERR};
  46. my $msi_db;
  47. return if !CheckCOMErrorWrapper {$msi_db = $msi_object->OpenDatabase( $msi_file, 0 )} \$self->{ERR};
  48. return $msi_db;
  49. }
  50. sub StoreMsiStreamData {
  51. my $self = shift;
  52. my $msi_db = shift;
  53. my $stream_name = shift;
  54. my $scratch_dir = shift;
  55. my ($view, $file, $size);
  56. return if !CheckCOMErrorWrapper {$view = $msi_db->OpenView( "SELECT Data FROM _Streams WHERE Name = '$stream_name'" )} \$self->{ERR};
  57. return if !CheckCOMErrorWrapper {$view->Execute()} \$self->{ERR};
  58. return if !CheckCOMErrorWrapper {$file = $view->Fetch()} \$self->{ERR};
  59. if (!$file) {
  60. $self->{ERR} = "Cannot find stream '$stream_name' in DB";
  61. return;
  62. }
  63. return if !CheckCOMErrorWrapper {$size = $file->DataSize(1)} \$self->{ERR};
  64. my ($fh, $fname) = File::Temp::tempfile( DIR => $scratch_dir?$scratch_dir:$ENV{TEMP} );
  65. if ( !$fh ) {
  66. $self->{ERR} = "Unable to create temporary file: $!";
  67. return;
  68. }
  69. binmode $fh;
  70. my ($i, $data);
  71. for ( $i = 0; $i < $size; $i+=2048 ) {
  72. return if !CheckCOMErrorWrapper {$data = $file->ReadStream(1, 2048, 2)} \$self->{ERR};
  73. $fh->write($data, 2048);
  74. }
  75. my $excess = $size%2048;
  76. if ($excess) {
  77. return if !CheckCOMErrorWrapper {$data = $file->ReadStream(1, $excess, 2)} \$self->{ERR};
  78. $fh->write($data, $excess);
  79. }
  80. close $fh;
  81. return $fname;
  82. }
  83. sub CompareMsiBinaries {
  84. my $self = shift;
  85. if (!ref $self) {confess "Invlid object reference"}
  86. if (@_ != 6) {confess "Invalid number of parameters to CompareMsiBinaries"}
  87. my $msi_a = shift;
  88. my $msi_b = shift;
  89. my $file_a = shift;
  90. my $file_b = shift;
  91. my $scratch_dir = shift;
  92. my $rfsame = shift;
  93. my $binary_a = $self->StoreMsiStreamData($msi_a, $file_a, $scratch_dir);
  94. return if !$binary_a;
  95. my $binary_b = $self->StoreMsiStreamData($msi_b, $file_b, $scratch_dir);
  96. return if !$binary_b;
  97. my $bin_comp = new BinComp;
  98. $$rfsame = $bin_comp->compare( $binary_a, $binary_b );
  99. if ( !defined $$rfsame ) {
  100. $self->{ERR} = $bin_comp->GetLastError();
  101. return;
  102. }
  103. elsif ( !$$rfsame ) {
  104. $self->{DIFF} = $bin_comp->GetLastDiff();
  105. }
  106. return 1;
  107. }
  108. sub CompareMsiCabs {
  109. my $self = shift;
  110. if (!ref $self) {confess "Invlid object reference"}
  111. if (@_ != 6) {confess "Invalid number of parameters to CompareMsiCabs"}
  112. my $msi_a = shift;
  113. my $msi_b = shift;
  114. my $file_a = shift;
  115. my $file_b = shift;
  116. my $scratch_dir = shift;
  117. my $rfsame = shift;
  118. my $cab_a = $self->StoreMsiStreamData($msi_a, $file_a, $scratch_dir);
  119. return if !$cab_a;
  120. my $cab_b = $self->StoreMsiStreamData($msi_b, $file_b, $scratch_dir);
  121. return if !$cab_b;
  122. my $cab_comp = new CabComp;
  123. $$rfsame = $cab_comp->compare( $cab_a, $cab_b );
  124. if ( !defined $$rfsame ) {
  125. $self->{ERR} = $cab_comp->GetLastError();
  126. return;
  127. }
  128. elsif ( !$$rfsame ) {
  129. $self->{DIFF} = $cab_comp->GetLastDiff();
  130. }
  131. return 1;
  132. }
  133. #
  134. # 0 - different
  135. # 1 - same
  136. # undefined - error
  137. #
  138. sub compare {
  139. my $self = shift;
  140. my $base = shift;
  141. my $upd = shift;
  142. if (!ref $self) {croak "Invalid object reference"}
  143. if (!$base||!$upd) {croak "Invalid function call -- missing required parameters"}
  144. if ( ! -e $base ) {
  145. $self->{ERR} = "Invalid file: $base";
  146. return;
  147. }
  148. if ( ! -e $upd ) {
  149. $self->{ERR} = "Invalid file: $upd";
  150. return;
  151. }
  152. return 0;
  153. my $base_db = $self->OpenMsiDb( $base );
  154. return if !$base_db;
  155. my $upd_db = $self->OpenMsiDb( $upd );
  156. return if !$upd_db;
  157. my $scratch_dir = File::Temp::tempdir( DIR => $ENV{TEMP}, CLEANUP => 1 );
  158. if ( !$scratch_dir ) {
  159. $self->{ERR} = "Unable to create temporary directory: $!";
  160. return;
  161. }
  162. my ($fh, $tempfile) = File::Temp::tempfile( DIR => $scratch_dir );
  163. if ( !$tempfile ) {
  164. $self->{ERR} = "Unable to create temporary file: $!";
  165. return;
  166. }
  167. close $fh;
  168. my $fDiff;
  169. return if !CheckCOMErrorWrapper {$fDiff = $base_db->GenerateTransform( $upd_db, $tempfile )} \$self->{ERR};
  170. if ( $fDiff ) {
  171. # Don't actually apply the transform, but generate
  172. # a temporary table that we can query for what updates
  173. # are needed to make the DB's equal
  174. return if !CheckCOMErrorWrapper {$upd_db->ApplyTransform( $tempfile, 0x0100 )} \$self->{ERR};
  175. my $view;
  176. return if !CheckCOMErrorWrapper {$view = $upd_db->OpenView( 'SELECT * FROM _TransformView' )} \$self->{ERR};
  177. return if !CheckCOMErrorWrapper {$view->Execute()} \$self->{ERR};
  178. my $record;
  179. return if !CheckCOMErrorWrapper {$record = $view->Fetch()} \$self->{ERR};
  180. my $fignore;
  181. my ($table, $column, $row, $data, $current);
  182. while ( $record ) {
  183. undef $fignore;
  184. return if !CheckCOMErrorWrapper {$table = $record->StringData(1)} \$self->{ERR};
  185. return if !CheckCOMErrorWrapper {$column = $record->StringData(2)} \$self->{ERR};
  186. return if !CheckCOMErrorWrapper {$row = $record->StringData(3)} \$self->{ERR};
  187. return if !CheckCOMErrorWrapper {$data = $record->StringData(4)} \$self->{ERR};
  188. return if !CheckCOMErrorWrapper {$current = $record->StringData(5)} \$self->{ERR};
  189. if ( $table eq 'File' && $column eq 'Version' ) {
  190. $fignore = 1;
  191. }
  192. elsif ( $table eq 'Product' && $column eq 'Version' ) {
  193. $fignore = 1;
  194. }
  195. elsif ( $table eq 'Property' && $column eq 'Value' and $row eq 'ProductVersion' ) {
  196. $fignore = 1;
  197. }
  198. elsif ( $table eq 'Binary' and $column eq 'Data' ) {
  199. return if !$self->CompareMsiBinaries( $base_db, $upd_db, $current, $data, $scratch_dir, \$fignore );
  200. return 0 if ( !$fignore );
  201. }
  202. elsif ( $table eq 'Cabs' and $column eq 'Data' ) {
  203. return if !$self->CompareMsiCabs( $base_db, $upd_db, $current, $data, $scratch_dir, \$fignore );
  204. return 0 if ( !$fignore );
  205. }
  206. if ( !$fignore ) {
  207. $self->{DIFF} = "$table/$column ($row): $current vs $data";
  208. return 0;
  209. }
  210. return if !CheckCOMErrorWrapper {$record = $view->Fetch()} \$self->{ERR};
  211. }
  212. }
  213. # Compare equal
  214. return 1;
  215. }
  216. 1;