Branch libreoffice-5-0-4
[LibreOffice.git] / solenv / bin / modules / installer / windows / msp.pm
blob1bbeea8d20cb1f3bb8ffb25d9d2fa710a85b3c74
2 # This file is part of the LibreOffice project.
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 # This file incorporates work covered by the following license notice:
10 # Licensed to the Apache Software Foundation (ASF) under one or more
11 # contributor license agreements. See the NOTICE file distributed
12 # with this work for additional information regarding copyright
13 # ownership. The ASF licenses this file to you under the Apache
14 # License, Version 2.0 (the "License"); you may not use this file
15 # except in compliance with the License. You may obtain a copy of
16 # the License at http://www.apache.org/licenses/LICENSE-2.0 .
19 package installer::windows::msp;
21 use File::Copy;
22 use installer::control;
23 use installer::converter;
24 use installer::exiter;
25 use installer::files;
26 use installer::globals;
27 use installer::logger;
28 use installer::pathanalyzer;
29 use installer::systemactions;
30 use installer::windows::admin;
31 use installer::windows::idtglobal;
32 use installer::windows::update;
34 #################################################################################
35 # Making all required administrative installations
36 #################################################################################
38 sub install_installation_sets
40 my ($installationdir) = @_;
42 # Finding the msi database in the new installation set, that is located in $installationdir
44 my $msifiles = installer::systemactions::find_file_with_file_extension("msi", $installationdir);
46 if ( $#{$msifiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find msi database in directory $installationdir", "create_msp_patch"); }
47 if ( $#{$msifiles} > 0 ) { installer::exiter::exit_program("ERROR: Did find more than one msi database in directory $installationdir", "create_msp_patch"); }
49 my $newinstallsetdatabasepath = $installationdir . $installer::globals::separator . ${$msifiles}[0];
50 my $oldinstallsetdatabasepath = $installer::globals::updatedatabasepath;
52 # Creating temp directory again
53 installer::systemactions::create_directory_structure($installer::globals::temppath);
55 # Creating old installation directory
56 my $dirname = "admin";
57 my $installpath = $installer::globals::temppath . $installer::globals::separator . $dirname;
58 if ( ! -d $installpath) { installer::systemactions::create_directory($installpath); }
60 my $oldinstallpath = $installpath . $installer::globals::separator . "old";
61 my $newinstallpath = $installpath . $installer::globals::separator . "new";
63 if ( ! -d $oldinstallpath) { installer::systemactions::create_directory($oldinstallpath); }
64 if ( ! -d $newinstallpath) { installer::systemactions::create_directory($newinstallpath); }
66 my $olddatabase = installer::windows::admin::make_admin_install($oldinstallsetdatabasepath, $oldinstallpath);
67 my $newdatabase = installer::windows::admin::make_admin_install($newinstallsetdatabasepath, $newinstallpath);
69 if ( $^O =~ /cygwin/i ) {
70 $olddatabase = qx{cygpath -w "$olddatabase"};
71 $olddatabase =~ s/\s*$//g;
72 $newdatabase = qx{cygpath -w "$newdatabase"};
73 $newdatabase =~ s/\s*$//g;
76 return ($olddatabase, $newdatabase);
79 #################################################################################
80 # Extracting all tables from a pcp file
81 #################################################################################
83 sub extract_all_tables_from_pcpfile
85 my ($fullpcpfilepath, $workdir) = @_;
87 my $msidb = "msidb.exe"; # Has to be in the path
88 my $infoline = "";
89 my $systemcall = "";
90 my $returnvalue = "";
91 my $extraslash = ""; # Has to be set for non-ActiveState perl
93 my $localfullpcpfile = $fullpcpfilepath;
94 my $localworkdir = $workdir;
96 if ( $^O =~ /cygwin/i ) {
97 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
98 $localfullpcpfile =~ s/\//\\\\/g;
99 $localworkdir =~ s/\//\\\\/g;
100 $extraslash = "\\";
102 if ( $^O =~ /linux/i ) {
103 $extraslash = "\\";
106 # Export of all tables by using "*"
108 $systemcall = $msidb . " -d " . $localfullpcpfile . " -f " . $localworkdir . " -e " . $extraslash . "*";
109 $returnvalue = system($systemcall);
111 $infoline = "Systemcall: $systemcall\n";
112 push( @installer::globals::logfileinfo, $infoline);
114 if ($returnvalue)
116 $infoline = "ERROR: Could not execute $systemcall !\n";
117 push( @installer::globals::logfileinfo, $infoline);
118 installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $fullpcpfilepath !", "extract_all_tables_from_msidatabase");
120 else
122 $infoline = "Success: Executed $systemcall successfully!\n";
123 push( @installer::globals::logfileinfo, $infoline);
127 #################################################################################
128 # Include tables into a pcp file
129 #################################################################################
131 sub include_tables_into_pcpfile
133 my ($fullpcpfilepath, $workdir, $tables) = @_;
135 my $msidb = "msidb.exe"; # Has to be in the path
136 my $infoline = "";
137 my $systemcall = "";
138 my $returnvalue = "";
140 # Make all table 8+3 conform
141 my $alltables = installer::converter::convert_stringlist_into_array(\$tables, " ");
143 for ( my $i = 0; $i <= $#{$alltables}; $i++ )
145 my $tablename = ${$alltables}[$i];
146 $tablename =~ s/\s*$//;
147 my $namelength = length($tablename);
148 if ( $namelength > 8 )
150 my $newtablename = substr($tablename, 0, 8); # name, offset, length
151 my $oldfile = $workdir . $installer::globals::separator . $tablename . ".idt";
152 my $newfile = $workdir . $installer::globals::separator . $newtablename . ".idt";
153 if ( -f $newfile ) { unlink $newfile; }
154 installer::systemactions::copy_one_file($oldfile, $newfile);
158 # Import of tables
160 my $localworkdir = $workdir;
161 my $localfullpcpfilepath = $fullpcpfilepath;
163 if ( $^O =~ /cygwin/i ) {
164 # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
165 $localfullpcpfilepath =~ s/\//\\\\/g;
166 $localworkdir =~ s/\//\\\\/g;
169 my @tables = split(' ', $tables); # I found that msidb from Windows SDK 7.1 did not accept more than one table.
170 foreach my $table (@tables)
172 $systemcall = $msidb . " -d " . $localfullpcpfilepath . " -f " . $localworkdir . " -i " . $table;
174 $returnvalue = system($systemcall);
176 $infoline = "Systemcall: $systemcall\n";
177 push( @installer::globals::logfileinfo, $infoline);
179 if ($returnvalue)
181 $infoline = "ERROR: Could not execute $systemcall !\n";
182 push( @installer::globals::logfileinfo, $infoline);
183 installer::exiter::exit_program("ERROR: Could not include tables into pcp file: $fullpcpfilepath !", "include_tables_into_pcpfile");
185 else
187 $infoline = "Success: Executed $systemcall successfully!\n";
188 push( @installer::globals::logfileinfo, $infoline);
193 #################################################################################
194 # Calling msimsp.exe
195 #################################################################################
197 sub execute_msimsp
199 my ($fullpcpfilename, $mspfilename, $localmspdir) = @_;
201 my $msimsp = "msimsp.exe"; # Has to be in the path
202 my $infoline = "";
203 my $systemcall = "";
204 my $returnvalue = "";
205 my $logfilename = $localmspdir . $installer::globals::separator . "msimsp.log";
207 # Using a specific temp for each msimsp.exe process
208 # Creating temp directory again (should already have happened)
209 installer::systemactions::create_directory_structure($installer::globals::temppath);
211 # Creating old installation directory
212 my $dirname = "msimsptemp";
213 my $msimsptemppath = $installer::globals::temppath . $installer::globals::separator . $dirname;
214 if ( ! -d $msimsptemppath) { installer::systemactions::create_directory($msimsptemppath); }
216 # r:\msvc9p\PlatformSDK\v6.1\bin\msimsp.exe -s c:\patch\hotfix_qfe1.pcp -p c:\patch\patch_ooo3_m2_m3.msp -l c:\patch\patch_ooo3_m2_m3.log
218 if ( -f $logfilename ) { unlink $logfilename; }
220 my $localfullpcpfilename = $fullpcpfilename;
221 my $localmspfilename = $mspfilename;
222 my $locallogfilename = $logfilename;
223 my $localmsimsptemppath = $msimsptemppath;
225 if ( $^O =~ /cygwin/i ) {
226 # msimsp.exe really wants backslashes. (And double escaping because system() expands the string.)
227 $localfullpcpfilename =~ s/\//\\\\/g;
228 $locallogfilename =~ s/\//\\\\/g;
230 $localmspfilename =~ s/\\/\\\\/g; # path already contains backslash
232 $localmsimsptemppath = qx{cygpath -w "$localmsimsptemppath"};
233 $localmsimsptemppath =~ s/\\/\\\\/g;
234 $localmsimsptemppath =~ s/\s*$//g;
237 $systemcall = $msimsp . " -s " . $localfullpcpfilename . " -p " . $localmspfilename . " -l " . $locallogfilename . " -f " . $localmsimsptemppath;
238 installer::logger::print_message( "... $systemcall ...\n" );
240 $returnvalue = system($systemcall);
242 $infoline = "Systemcall: $systemcall\n";
243 push( @installer::globals::logfileinfo, $infoline);
245 if ($returnvalue)
247 $infoline = "ERROR: Could not execute $systemcall !\n";
248 push( @installer::globals::logfileinfo, $infoline);
249 installer::exiter::exit_program("ERROR: Could not execute $systemcall !", "execute_msimsp");
251 else
253 $infoline = "Success: Executed $systemcall successfully!\n";
254 push( @installer::globals::logfileinfo, $infoline);
257 return $logfilename;
260 ####################################################################
261 # Checking existence and saving all tables, that need to be edited
262 ####################################################################
264 sub check_and_save_tables
266 my ($tablelist, $workdir) = @_;
268 my $tables = installer::converter::convert_stringlist_into_array(\$tablelist, " ");
270 for ( my $i = 0; $i <= $#{$tables}; $i++ )
272 my $filename = ${$tables}[$i];
273 $filename =~ s/\s*$//;
274 my $fullfilename = $workdir . $installer::globals::separator . $filename . ".idt";
276 if ( ! -f $fullfilename ) { installer::exiter::exit_program("ERROR: Required idt file could not be found: \"$fullfilename\"!", "check_and_save_tables"); }
278 my $savfilename = $fullfilename . ".sav";
279 installer::systemactions::copy_one_file($fullfilename, $savfilename);
283 ####################################################################
284 # Setting the name of the msp database
285 ####################################################################
287 sub set_mspfilename
289 my ($allvariables, $mspdir, $languagesarrayref) = @_;
291 my $databasename = $allvariables->{'PRODUCTNAME'} . "-" . $allvariables->{'PRODUCTVERSION'} . "-" . $allvariables->{'WINDOWSPATCHLEVEL'} . ".msp";
293 my $fullmspname = $mspdir . $installer::globals::separator . $databasename;
295 if ( $^O =~ /cygwin/i ) { $fullmspname =~ s/\//\\/g; }
297 return $fullmspname;
300 ####################################################################
301 # Editing table Properties
302 ####################################################################
304 sub change_properties_table
306 my ($localmspdir, $mspfilename) = @_;
308 my $infoline = "Changing content of table \"Properties\"\n";
309 push( @installer::globals::logfileinfo, $infoline);
311 my $filename = $localmspdir . $installer::globals::separator . "Properties.idt";
312 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_properties_table"); }
314 my $filecontent = installer::files::read_file($filename);
317 my $guidref = installer::windows::msiglobal::get_guid_list(1, 1);
318 ${$guidref}[0] =~ s/\s*$//; # removing ending spaces
319 my $patchcode = "\{" . ${$guidref}[0] . "\}";
321 # Setting "PatchOutputPath"
322 my $found_patchoutputpath = 0;
323 my $found_patchguid = 0;
325 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
327 if ( ${$filecontent}[$i] =~ /^\s*PatchOutputPath\t(.*?)\s*$/ )
329 my $oldvalue = $1;
330 ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$mspfilename/;
331 $found_patchoutputpath = 1;
334 if ( ${$filecontent}[$i] =~ /^\s*PatchGUID\t(.*?)\s*$/ )
336 my $oldvalue = $1;
337 ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$patchcode/;
338 $found_patchguid = 1;
342 if ( ! $found_patchoutputpath )
344 my $newline = "PatchOutputPath\t$mspfilename\n";
345 push(@{$filecontent}, $newline);
348 if ( ! $found_patchguid )
350 my $newline = "PatchGUID\t$patchcode\n";
351 push(@{$filecontent}, $newline);
354 # saving file
355 installer::files::save_file($filename, $filecontent);
358 ####################################################################
359 # Editing table TargetImages
360 ####################################################################
362 sub change_targetimages_table
364 my ($localmspdir, $olddatabase) = @_;
366 my $infoline = "Changing content of table \"TargetImages\"\n";
367 push( @installer::globals::logfileinfo, $infoline);
369 my $filename = $localmspdir . $installer::globals::separator . "TargetImages.idt";
370 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_targetimages_table"); }
372 my $filecontent = installer::files::read_file($filename);
373 my @newcontent = ();
375 # Copying the header
376 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
378 #Adding all targets
379 my $newline = "T1\t$olddatabase\t\tU1\t1\t0x00000922\t1\n";
380 push(@newcontent, $newline);
382 # saving file
383 installer::files::save_file($filename, \@newcontent);
386 ####################################################################
387 # Editing table UpgradedImages
388 ####################################################################
390 sub change_upgradedimages_table
392 my ($localmspdir, $newdatabase) = @_;
394 my $infoline = "Changing content of table \"UpgradedImages\"\n";
395 push( @installer::globals::logfileinfo, $infoline);
397 my $filename = $localmspdir . $installer::globals::separator . "UpgradedImages.idt";
398 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_upgradedimages_table"); }
400 my $filecontent = installer::files::read_file($filename);
401 my @newcontent = ();
403 # Copying the header
404 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
406 # Syntax: Upgraded MsiPath PatchMsiPath SymbolPaths Family
408 # default values
409 my $upgraded = "U1";
410 my $msipath = $newdatabase;
411 my $patchmsipath = "";
412 my $symbolpaths = "";
413 my $family = "22334455";
415 if ( $#{$filecontent} >= 3 )
417 my $line = ${$filecontent}[3];
418 if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
420 $upgraded = $1;
421 $patchmsipath = $3;
422 $symbolpaths = $4;
423 $family = $5;
427 #Adding sequence line, saving PatchFamily
428 my $newline = "$upgraded\t$msipath\t$patchmsipath\t$symbolpaths\t$family\n";
429 push(@newcontent, $newline);
431 # saving file
432 installer::files::save_file($filename, \@newcontent);
435 ####################################################################
436 # Editing table ImageFamilies
437 ####################################################################
439 sub change_imagefamilies_table
441 my ($localmspdir) = @_;
443 my $infoline = "Changing content of table \"ImageFamilies\"\n";
444 push( @installer::globals::logfileinfo, $infoline);
446 my $filename = $localmspdir . $installer::globals::separator . "ImageFamilies.idt";
447 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_imagefamilies_table"); }
449 my $filecontent = installer::files::read_file($filename);
450 my @newcontent = ();
452 # Copying the header
453 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
455 # Syntax: Family MediaSrcPropName MediaDiskId FileSequenceStart DiskPrompt VolumeLabel
456 # "FileSequenceStart has to be set
458 # Default values:
460 my $family = "22334455";
461 my $mediasrcpropname = "MediaSrcPropName";
462 my $mediadiskid = "2";
463 my $filesequencestart = get_filesequencestart();
464 my $diskprompt = "";
465 my $volumelabel = "";
467 if ( $#{$filecontent} >= 3 )
469 my $line = ${$filecontent}[3];
470 if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
472 $family = $1;
473 $mediasrcpropname = $2;
474 $mediadiskid = $3;
475 $diskprompt = $5;
476 $volumelabel = $6;
480 #Adding sequence line
481 my $newline = "$family\t$mediasrcpropname\t$mediadiskid\t$filesequencestart\t$diskprompt\t$volumelabel\n";
482 push(@newcontent, $newline);
484 # saving file
485 installer::files::save_file($filename, \@newcontent);
488 ####################################################################
489 # Setting start sequence for patch
490 ####################################################################
492 sub get_filesequencestart
494 my $sequence = 1000; # default
496 if ( $installer::globals::updatelastsequence ) { $sequence = $installer::globals::updatelastsequence + 500; }
498 return $sequence;
501 ####################################################################
502 # Setting time value into pcp file
503 # Format mm/dd/yyyy hh:mm
504 ####################################################################
506 sub get_patchtime_value
508 # Syntax: 8/8/2008 11:55
509 my $minute = (localtime())[1];
510 my $hour = (localtime())[2];
511 my $day = (localtime())[3];
512 my $month = (localtime())[4];
513 my $year = 1900 + (localtime())[5];
515 $month++; # zero based month
516 if ( $minute < 10 ) { $minute = "0" . $minute; }
517 if ( $hour < 10 ) { $hour = "0" . $hour; }
519 my $timestring = $month . "/" . $day . "/" . $year . " " . $hour . ":" . $minute;
521 return $timestring;
524 #################################################################################
525 # Checking, if this is the correct database.
526 #################################################################################
528 sub correct_langs
530 my ($langs, $languagestringref) = @_;
532 my $correct_langs = 0;
534 # Comparing $langs with $languagestringref
536 my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
537 my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
539 my $not_included = 0;
540 foreach my $onelang ( keys %{$langlisthash} )
542 if ( ! exists($langstringhash->{$onelang}) )
544 $not_included = 1;
545 last;
549 if ( ! $not_included )
551 foreach my $onelanguage ( keys %{$langstringhash} )
553 if ( ! exists($langlisthash->{$onelanguage}) )
555 $not_included = 1;
556 last;
560 if ( ! $not_included ) { $correct_langs = 1; }
563 return $correct_langs;
566 #################################################################################
567 # Searching for the path to the reference database for this special product.
568 #################################################################################
570 sub get_patchid_from_list
572 my ($filecontent, $languagestringref, $filename) = @_;
574 my $patchid = "";
576 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
578 my $line = ${$filecontent}[$i];
579 if ( $line =~ /^\s*$/ ) { next; } # empty line
580 if ( $line =~ /^\s*\#/ ) { next; } # comment line
582 if ( $line =~ /^\s*(.+?)\s*=\s*(.+?)\s*$/ )
584 my $langs = $1;
585 my $localpatchid = $2;
587 if ( correct_langs($langs, $languagestringref) )
589 $patchid = $localpatchid;
590 last;
593 else
595 installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_patchid_from_list");
599 return $patchid;
602 ####################################################################
603 # Editing table PatchMetadata
604 ####################################################################
606 sub change_patchmetadata_table
608 my ($localmspdir, $allvariables, $languagestringref) = @_;
610 my $infoline = "Changing content of table \"PatchMetadata\"\n";
611 push( @installer::globals::logfileinfo, $infoline);
613 my $filename = $localmspdir . $installer::globals::separator . "PatchMetadata.idt";
614 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_patchmetadata_table"); }
616 my $filecontent = installer::files::read_file($filename);
617 my @newcontent = ();
619 # Syntax: Company Property Value
620 # Interesting properties: "Classification" and "CreationTimeUTC"
622 my $classification_set = 0;
623 my $creationtime_set = 0;
624 my $targetproductname_set = 0;
625 my $manufacturer_set = 0;
626 my $displayname_set = 0;
627 my $description_set = 0;
628 my $allowremoval_set = 0;
630 my $defaultcompany = "";
632 my $classificationstring = "Classification";
633 my $classificationvalue = "Hotfix";
634 if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $classificationvalue = "ServicePack"; }
636 my $allowremovalstring = "AllowRemoval";
637 my $allowremovalvalue = "1";
638 if (( exists($allvariables->{'MSPALLOWREMOVAL'}) ) && ( $allvariables->{'MSPALLOWREMOVAL'} == 0 )) { $allowremovalvalue = 0; }
640 my $timestring = "CreationTimeUTC";
641 # Syntax: 8/8/2008 11:55
642 my $timevalue = get_patchtime_value();
644 my $targetproductnamestring = "TargetProductName";
645 my $targetproductnamevalue = $allvariables->{'PRODUCTNAME'};
646 if ( $allvariables->{'PROPERTYTABLEPRODUCTNAME'} ) { $targetproductnamevalue = $allvariables->{'PROPERTYTABLEPRODUCTNAME'}; }
648 my $manufacturerstring = "ManufacturerName";
649 my $manufacturervalue = $ENV{'OOO_VENDOR'};
650 if ( $installer::globals::longmanufacturer ) { $manufacturervalue = $installer::globals::longmanufacturer; }
652 my $displaynamestring = "DisplayName";
653 my $descriptionstring = "Description";
654 my $displaynamevalue = "";
655 my $descriptionvalue = "";
657 my $base = $allvariables->{'PRODUCTNAME'} . " " . $allvariables->{'PRODUCTVERSION'};
658 if ( $installer::globals::languagepack || $installer::globals::helppack ) { $base = $targetproductnamevalue; }
660 my $windowspatchlevel = 0;
661 if ( $allvariables->{'WINDOWSPATCHLEVEL'} ) { $windowspatchlevel = $allvariables->{'WINDOWSPATCHLEVEL'}; }
663 my $displayaddon = "";
664 if ( $allvariables->{'PATCHDISPLAYADDON'} ) { $displayaddon = $allvariables->{'PATCHDISPLAYADDON'}; }
666 my $patchsequence = get_patchsequence($allvariables);
668 if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 ))
670 $displaynamevalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid;
671 $descriptionvalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid;
673 else
675 $displaynamevalue = $base . " Hotfix " . $displayaddon . " " . $patchsequence . " Build: " . $installer::globals::buildid;
676 $descriptionvalue = $base . " Hotfix " . $displayaddon . " " . $patchsequence . " Build: " . $installer::globals::buildid;
677 $displaynamevalue =~ s/ / /g;
678 $descriptionvalue =~ s/ / /g;
679 $displaynamevalue =~ s/ / /g;
680 $descriptionvalue =~ s/ / /g;
681 $displaynamevalue =~ s/ / /g;
682 $descriptionvalue =~ s/ / /g;
685 if ( $allvariables->{'MSPPATCHNAMELIST'} )
687 my $patchnamelistfile = $allvariables->{'MSPPATCHNAMELIST'};
688 $patchnamelistfile = $installer::globals::idttemplatepath . $installer::globals::separator . $patchnamelistfile;
689 if ( ! -f $patchnamelistfile ) { installer::exiter::exit_program("ERROR: Could not find file \"$patchnamelistfile\".", "change_patchmetadata_table"); }
690 my $filecontent = installer::files::read_file($patchnamelistfile);
692 # Get name and path of reference database
693 my $patchid = get_patchid_from_list($filecontent, $languagestringref, $patchnamelistfile);
695 if ( $patchid eq "" ) { installer::exiter::exit_program("ERROR: Could not find file patchid in file \"$patchnamelistfile\" for language(s) \"$$languagestringref\".", "change_patchmetadata_table"); }
697 # Setting language specific patch id
700 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
702 if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
704 my $company = $1;
705 my $property = $2;
706 my $value = $3;
708 if ( $property eq $classificationstring )
710 ${$filecontent}[$i] = "$company\t$property\t$classificationvalue\n";
711 $classification_set = 1;
714 if ( $property eq $allowremovalstring )
716 ${$filecontent}[$i] = "$company\t$property\t$allowremovalvalue\n";
717 $allowremoval_set = 1;
720 if ( $property eq $timestring )
722 ${$filecontent}[$i] = "$company\t$property\t$timevalue\n";
723 $creationtime_set = 1;
726 if ( $property eq $targetproductnamestring )
728 ${$filecontent}[$i] = "$company\t$property\t$targetproductnamevalue\n";
729 $targetproductname_set = 1;
732 if ( $property eq $manufacturerstring )
734 ${$filecontent}[$i] = "$company\t$property\t$manufacturervalue\n";
735 $manufacturer_set = 1;
738 if ( $property eq $displaynamestring )
740 ${$filecontent}[$i] = "$company\t$property\t$displaynamevalue\n";
741 $displayname_set = 1;
744 if ( $property eq $descriptionstring )
746 ${$filecontent}[$i] = "$company\t$property\t$descriptionvalue\n";
747 $description_set = 1;
751 push(@newcontent, ${$filecontent}[$i]);
754 if ( ! $classification_set )
756 my $line = "$defaultcompany\t$classificationstring\t$classificationvalue\n";
757 push(@newcontent, $line);
760 if ( ! $allowremoval_set )
762 my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n";
763 push(@newcontent, $line);
766 if ( ! $allowremoval_set )
768 my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n";
769 push(@newcontent, $line);
772 if ( ! $creationtime_set )
774 my $line = "$defaultcompany\t$timestring\t$timevalue\n";
775 push(@newcontent, $line);
778 if ( ! $targetproductname_set )
780 my $line = "$defaultcompany\t$targetproductnamestring\t$targetproductnamevalue\n";
781 push(@newcontent, $line);
784 if ( ! $manufacturer_set )
786 my $line = "$defaultcompany\t$manufacturerstring\t$manufacturervalue\n";
787 push(@newcontent, $line);
790 if ( ! $displayname_set )
792 my $line = "$defaultcompany\t$displaynamestring\t$displaynamevalue\n";
793 push(@newcontent, $line);
796 if ( ! $description_set )
798 my $line = "$defaultcompany\t$descriptionstring\t$descriptionvalue\n";
799 push(@newcontent, $line);
802 # saving file
803 installer::files::save_file($filename, \@newcontent);
806 ####################################################################
807 # Editing table PatchSequence
808 ####################################################################
810 sub change_patchsequence_table
812 my ($localmspdir, $allvariables) = @_;
814 my $infoline = "Changing content of table \"PatchSequence\"\n";
815 push( @installer::globals::logfileinfo, $infoline);
817 my $filename = $localmspdir . $installer::globals::separator . "PatchSequence.idt";
818 if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" !", "change_patchsequence_table"); }
820 my $filecontent = installer::files::read_file($filename);
821 my @newcontent = ();
823 # Copying the header
824 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
826 # Syntax: PatchFamily Target Sequence Supersede
828 my $patchfamily = "SO";
829 my $target = "";
830 my $patchsequence = get_patchsequence($allvariables);
831 my $supersede = get_supersede($allvariables);
833 if ( $#{$filecontent} >= 3 )
835 my $line = ${$filecontent}[3];
836 if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\s$/ )
838 $patchfamily = $1;
839 $target = $2;
843 #Adding sequence line, saving PatchFamily
844 my $newline = "$patchfamily\t$target\t$patchsequence\t$supersede\n";
845 push(@newcontent, $newline);
847 # saving file
848 installer::files::save_file($filename, \@newcontent);
851 ####################################################################
852 # Setting supersede, "0" for Hotfixes, "1" for ServicePack
853 ####################################################################
855 sub get_supersede
857 my ( $allvariables ) = @_;
859 my $supersede = 0; # if not defined, this is a Hotfix
861 if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $supersede = 1; }
863 return $supersede;
866 ####################################################################
867 # Setting the sequence of the patch
868 ####################################################################
870 sub get_patchsequence
872 my ( $allvariables ) = @_;
874 my $patchsequence = "1.0";
876 if ( ! $allvariables->{'PACKAGEVERSION'} ) { installer::exiter::exit_program("ERROR: PACKAGEVERSION must be set for msp patch creation!", "get_patchsequence"); }
878 my $packageversion = $allvariables->{'PACKAGEVERSION'};
880 if ( $packageversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\.(\d+)\s*$/ )
882 my $major = $1;
883 my $minor = $2;
884 my $micro = $3;
885 my $patch = $4;
886 $patchsequence = $major . "\." . $minor . "\." . $micro . "\." . $patch;
889 return $patchsequence;
892 ####################################################################
893 # Editing all tables from pcp file, that need to be edited
894 ####################################################################
896 sub edit_tables
898 my ($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref) = @_;
900 # table list contains: my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence";
902 change_properties_table($localmspdir, $mspfilename);
903 change_targetimages_table($localmspdir, $olddatabase);
904 change_upgradedimages_table($localmspdir, $newdatabase);
905 change_imagefamilies_table($localmspdir);
906 change_patchmetadata_table($localmspdir, $allvariables, $languagestringref);
907 change_patchsequence_table($localmspdir, $allvariables);
910 #################################################################################
911 # Checking, if this is the correct database.
912 #################################################################################
914 sub correct_patch
916 my ($product, $pro, $langs, $languagestringref) = @_;
918 my $correct_patch = 0;
920 # Comparing $product with $installer::globals::product and
921 # $pro with $installer::globals::pro and
922 # $langs with $languagestringref
924 my $product_is_good = 0;
926 my $localproduct = $installer::globals::product;
927 if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; }
928 elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; }
930 if ( $product eq $localproduct ) { $product_is_good = 1; }
932 if ( $product_is_good )
934 my $pro_is_good = 0;
936 if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; }
938 if ( $pro_is_good )
940 my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
941 my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
943 my $not_included = 0;
944 foreach my $onelang ( keys %{$langlisthash} )
946 if ( ! exists($langstringhash->{$onelang}) )
948 $not_included = 1;
949 last;
953 if ( ! $not_included )
955 foreach my $onelanguage ( keys %{$langstringhash} )
957 if ( ! exists($langlisthash->{$onelanguage}) )
959 $not_included = 1;
960 last;
964 if ( ! $not_included ) { $correct_patch = 1; }
969 return $correct_patch;
972 #################################################################################
973 # Searching for the path to the required patch for this special product.
974 #################################################################################
976 sub get_requiredpatchfile_from_list
978 my ($filecontent, $languagestringref, $filename) = @_;
980 my $patchpath = "";
982 for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
984 my $line = ${$filecontent}[$i];
985 if ( $line =~ /^\s*$/ ) { next; } # empty line
986 if ( $line =~ /^\s*\#/ ) { next; } # comment line
988 if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ )
990 my $product = $1;
991 my $pro = $2;
992 my $langs = $3;
993 my $path = $4;
995 if (( $pro ne "pro" ) && ( $pro ne "nonpro" )) { installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename. Only \"pro\" or \"nonpro\" allowed in column 1! Line: \"$line\"", "get_databasename_from_list"); }
997 if ( correct_patch($product, $pro, $langs, $languagestringref) )
999 $patchpath = $path;
1000 last;
1003 else
1005 installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_requiredpatchfile_from_list");
1009 return $patchpath;
1012 ##################################################################
1013 # Converting unicode file to ascii
1014 # to be more precise: uft-16 little endian to ascii
1015 ##################################################################
1017 sub convert_unicode_to_ascii
1019 my ( $filename ) = @_;
1021 my @localfile = ();
1023 my $savfilename = $filename . "_before.unicode";
1024 installer::systemactions::copy_one_file($filename, $savfilename);
1026 open( IN, "<:encoding(UTF16-LE)", $filename ) || installer::exiter::exit_program("ERROR: Cannot open file $filename for reading", "convert_unicode_to_ascii");
1027 while ( $line = <IN> ) {
1028 push @localfile, $line;
1030 close( IN );
1032 if ( open( OUT, ">", $filename ) )
1034 print OUT @localfile;
1035 close(OUT);
1039 ####################################################################
1040 # Analyzing the log file created by msimsp.exe to find all
1041 # files included into the patch.
1042 ####################################################################
1044 sub analyze_msimsp_logfile
1046 my ($logfile, $filesarray) = @_;
1048 # Reading log file after converting from utf-16 (LE) to ascii
1049 convert_unicode_to_ascii($logfile);
1050 my $logfilecontent = installer::files::read_file($logfile);
1052 # Creating hash from $filesarray: unique file name -> destination of file
1053 my %filehash = ();
1054 my %destinationcollector = ();
1056 for ( my $i = 0; $i <= $#{$filesarray}; $i++ )
1058 my $onefile = ${$filesarray}[$i];
1060 # Only collecting files with "uniquename" and "destination"
1061 if (( exists($onefile->{'uniquename'}) ) && ( exists($onefile->{'uniquename'}) ))
1063 my $uniquefilename = $onefile->{'uniquename'};
1064 my $destpath = $onefile->{'destination'};
1065 $filehash{$uniquefilename} = $destpath;
1069 # Analyzing log file of msimsp.exe, finding all changed files
1070 # and searching all destinations of unique file names.
1071 # Content in log file: "INFO File Key: <file key> is modified"
1072 # Collecting content in @installer::globals::patchfilecollector
1074 for ( my $i = 0; $i <= $#{$logfilecontent}; $i++ )
1076 if ( ${$logfilecontent}[$i] =~ /Key\:\s*(.*?) is modified\s*$/ )
1078 my $filekey = $1;
1079 if ( exists($filehash{$filekey}) ) { $destinationcollector{$filehash{$filekey}} = 1; }
1080 else { installer::exiter::exit_program("ERROR: Could not find file key \"$filekey\" in file collector.", "analyze_msimsp_logfile"); }
1084 foreach my $onedest ( sort keys %destinationcollector ) { push(@installer::globals::patchfilecollector, "$onedest\n"); }
1088 ####################################################################
1089 # Creating msp patch files for Windows
1090 ####################################################################
1092 sub create_msp_patch
1094 my ($installationdir, $includepatharrayref, $allvariables, $languagestringref, $languagesarrayref, $filesarray) = @_;
1096 my $force = 1; # print this message even in 'quiet' mode
1097 installer::logger::print_message( "\n******************************************\n" );
1098 installer::logger::print_message( "... creating msp installation set ...\n", $force );
1099 installer::logger::print_message( "******************************************\n" );
1101 $installer::globals::creating_windows_installer_patch = 1;
1103 my @needed_files = ("msimsp.exe"); # only required for patch creation process
1104 installer::control::check_needed_files_in_path(\@needed_files);
1106 installer::logger::include_header_into_logfile("Creating msp installation sets:");
1108 my $firstdir = $installationdir;
1109 installer::pathanalyzer::get_path_from_fullqualifiedname(\$firstdir);
1111 my $lastdir = $installationdir;
1112 installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$lastdir);
1114 if ( $lastdir =~ /\./ ) { $lastdir =~ s/\./_msp_inprogress\./ }
1115 else { $lastdir = $lastdir . "_msp_inprogress"; }
1117 # Removing existing directory "_native_packed_inprogress" and "_native_packed_witherror" and "_native_packed"
1119 my $mspdir = $firstdir . $lastdir;
1120 if ( -d $mspdir ) { installer::systemactions::remove_complete_directory($mspdir); }
1122 my $olddir = $mspdir;
1123 $olddir =~ s/_inprogress/_witherror/;
1124 if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); }
1126 $olddir = $mspdir;
1127 $olddir =~ s/_inprogress//;
1128 if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); }
1130 # Creating the new directory for new installation set
1131 installer::systemactions::create_directory($mspdir);
1133 $installer::globals::saveinstalldir = $mspdir;
1135 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting product installation");
1137 # Installing both installation sets
1138 installer::logger::print_message( "... installing products ...\n" );
1139 my ($olddatabase, $newdatabase) = install_installation_sets($installationdir);
1141 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting pcp file creation");
1143 # Create pcp file
1144 installer::logger::print_message( "... creating pcp file ...\n" );
1146 my $localmspdir = installer::systemactions::create_directories("msp", $languagestringref);
1148 if ( ! $allvariables->{'PCPFILENAME'} ) { installer::exiter::exit_program("ERROR: Property \"PCPFILENAME\" has to be defined.", "create_msp_patch"); }
1149 my $pcpfilename = $allvariables->{'PCPFILENAME'};
1151 if ( $installer::globals::languagepack ) { $pcpfilename =~ s/.pcp\s*$/languagepack.pcp/; }
1152 elsif ( $installer::globals::helppack ) { $pcpfilename =~ s/.pcp\s*$/helppack.pcp/; }
1154 # Searching the pcp file in the include paths
1155 my $fullpcpfilenameref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$pcpfilename, $includepatharrayref, 1);
1156 if ( $$fullpcpfilenameref eq "" ) { installer::exiter::exit_program("ERROR: pcp file not found: $pcpfilename !", "create_msp_patch"); }
1157 my $fullpcpfilenamesource = $$fullpcpfilenameref;
1159 # Copying pcp file
1160 my $fullpcpfilename = $localmspdir . $installer::globals::separator . $pcpfilename;
1161 installer::systemactions::copy_one_file($fullpcpfilenamesource, $fullpcpfilename);
1163 # a. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
1164 # b. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
1165 # c. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
1167 # Unpacking tables from pcp file
1168 extract_all_tables_from_pcpfile($fullpcpfilename, $localmspdir);
1170 # Tables, that need to be edited
1171 my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence"; # required tables
1173 # Saving all tables
1174 check_and_save_tables($tablelist, $localmspdir);
1176 # Setting the name of the new msp file
1177 my $mspfilename = set_mspfilename($allvariables, $mspdir, $languagesarrayref);
1179 # Editing tables
1180 edit_tables($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref);
1182 # Adding edited tables into pcp file
1183 include_tables_into_pcpfile($fullpcpfilename, $localmspdir, $tablelist);
1185 # Start msimsp.exe
1186 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting msimsp.exe");
1187 my $msimsplogfile = execute_msimsp($fullpcpfilename, $mspfilename, $localmspdir);
1189 # Sign .msp file
1190 if ( defined($ENV{'WINDOWS_BUILD_SIGNING'}) && ($ENV{'WINDOWS_BUILD_SIGNING'} eq 'TRUE') )
1192 my $localmspfilename = $mspfilename;
1193 $localmspfilename =~ s/\\/\\\\/g;
1194 my $systemcall = "signtool.exe sign ";
1195 if ( defined($ENV{'PFXFILE'}) ) { $systemcall .= "-f $ENV{'PFXFILE'} "; }
1196 if ( defined($ENV{'PFXPASSWORD'}) ) { $systemcall .= "-p $ENV{'PFXPASSWORD'} "; }
1197 if ( defined($ENV{'TIMESTAMPURL'}) ) { $systemcall .= "-t $ENV{'TIMESTAMPURL'} "; } else { $systemcall .= "-t http://timestamp.globalsign.com/scripts/timestamp.dll "; }
1198 $systemcall .= "-d \"" . $allvariables->{'PRODUCTNAME'} . " " . $allvariables->{'PRODUCTVERSION'} . " Patch " . $allvariables->{'WINDOWSPATCHLEVEL'} . "\" ";
1199 $systemcall .= $localmspfilename;
1200 installer::logger::print_message( "... code signing and timestamping with signtool.exe ...\n" );
1202 my $returnvalue = system($systemcall);
1204 # do not print password to log
1205 if ( defined($ENV{'PFXPASSWORD'}) ) { $systemcall =~ s/$ENV{'PFXPASSWORD'}/********/; }
1206 my $infoline = "Systemcall: $systemcall\n";
1207 push( @installer::globals::logfileinfo, $infoline);
1209 if ($returnvalue)
1211 $infoline = "ERROR: Could not execute \"$systemcall\"!\n";
1212 push( @installer::globals::logfileinfo, $infoline);
1214 else
1216 $infoline = "Success: Executed \"$systemcall\" successfully!\n";
1217 push( @installer::globals::logfileinfo, $infoline);
1221 # Copy final installation set next to msp file
1222 installer::logger::include_timestamp_into_logfile("\nPerformance Info: Copying installation set");
1223 installer::logger::print_message( "... copying installation set ...\n" );
1225 my $oldinstallationsetpath = $installer::globals::updatedatabasepath;
1227 if ( $^O =~ /cygwin/i ) { $oldinstallationsetpath =~ s/\\/\//g; }
1229 installer::pathanalyzer::get_path_from_fullqualifiedname(\$oldinstallationsetpath);
1230 installer::systemactions::copy_complete_directory($oldinstallationsetpath, $mspdir);
1232 # Copying additional patches into the installation set, if required
1233 if (( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ) && ( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ne "" ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack ))
1235 my $filename = $allvariables->{'ADDITIONALREQUIREDPATCHES'};
1237 my $fullfilenameref = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$filename, $includepatharrayref, 1);
1238 if ( $$fullfilenameref eq "" ) { installer::exiter::exit_program("ERROR: Could not find file with required patches, although it is defined: $filename !", "create_msp_patch"); }
1239 my $fullfilename = $$fullfilenameref;
1241 # Reading list file
1242 my $listfile = installer::files::read_file($fullfilename);
1244 # Get name and path of reference database
1245 my $requiredpatchfile = get_requiredpatchfile_from_list($listfile, $languagestringref, $fullfilename);
1246 if ( $requiredpatchfile eq "" ) { installer::exiter::exit_program("ERROR: Could not find path to required patch in file $fullfilename for language(s) $$languagestringref!", "create_msp_patch"); }
1248 # Copying patch file
1249 installer::systemactions::copy_one_file($requiredpatchfile, $mspdir);
1250 # my $infoline = "Copy $requiredpatchfile to $mspdir\n";
1251 # push( @installer::globals::logfileinfo, $infoline);
1254 # Find all files included into the patch
1255 # Analyzing the msimsp log file $msimsplogfile
1256 analyze_msimsp_logfile($msimsplogfile, $filesarray);
1258 # Done
1259 installer::logger::include_timestamp_into_logfile("\nPerformance Info: msp creation done");
1261 return $mspdir;