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
;
22 use installer
::control
;
23 use installer
::converter
;
24 use installer
::exiter
;
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
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
;
102 if ( $^O
=~ /linux/i ) {
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);
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");
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
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);
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);
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");
187 $infoline = "Success: Executed $systemcall successfully!\n";
188 push( @installer::globals
::logfileinfo
, $infoline);
193 #################################################################################
195 #################################################################################
199 my ($fullpcpfilename, $mspfilename, $localmspdir) = @_;
201 my $msimsp = "msimsp.exe"; # Has to be in the path
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);
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");
253 $infoline = "Success: Executed $systemcall successfully!\n";
254 push( @installer::globals
::logfileinfo
, $infoline);
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 ####################################################################
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
; }
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*$/ )
330 ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$mspfilename/;
331 $found_patchoutputpath = 1;
334 if ( ${$filecontent}[$i] =~ /^\s*PatchGUID\t(.*?)\s*$/ )
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);
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);
376 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
379 my $newline = "T1\t$olddatabase\t\tU1\t1\t0x00000922\t1\n";
380 push(@newcontent, $newline);
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);
404 for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
406 # Syntax: Upgraded MsiPath PatchMsiPath SymbolPaths Family
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*$/ )
427 #Adding sequence line, saving PatchFamily
428 my $newline = "$upgraded\t$msipath\t$patchmsipath\t$symbolpaths\t$family\n";
429 push(@newcontent, $newline);
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);
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
460 my $family = "22334455";
461 my $mediasrcpropname = "MediaSrcPropName";
462 my $mediadiskid = "2";
463 my $filesequencestart = get_filesequencestart
();
465 my $volumelabel = "";
467 if ( $#{$filecontent} >= 3 )
469 my $line = ${$filecontent}[3];
470 if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
473 $mediasrcpropname = $2;
480 #Adding sequence line
481 my $newline = "$family\t$mediasrcpropname\t$mediadiskid\t$filesequencestart\t$diskprompt\t$volumelabel\n";
482 push(@newcontent, $newline);
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; }
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;
524 #################################################################################
525 # Checking, if this is the correct database.
526 #################################################################################
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}) )
549 if ( ! $not_included )
551 foreach my $onelanguage ( keys %{$langstringhash} )
553 if ( ! exists($langlisthash->{$onelanguage}) )
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) = @_;
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*$/ )
585 my $localpatchid = $2;
587 if ( correct_langs
($langs, $languagestringref) )
589 $patchid = $localpatchid;
595 installer
::exiter
::exit_program
("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_patchid_from_list");
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);
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
;
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*$/ )
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);
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);
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";
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$/ )
843 #Adding sequence line, saving PatchFamily
844 my $newline = "$patchfamily\t$target\t$patchsequence\t$supersede\n";
845 push(@newcontent, $newline);
848 installer
::files
::save_file
($filename, \
@newcontent);
851 ####################################################################
852 # Setting supersede, "0" for Hotfixes, "1" for ServicePack
853 ####################################################################
857 my ( $allvariables ) = @_;
859 my $supersede = 0; # if not defined, this is a Hotfix
861 if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $supersede = 1; }
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*$/ )
886 $patchsequence = $major . "\." . $minor . "\." . $micro . "\." . $patch;
889 return $patchsequence;
892 ####################################################################
893 # Editing all tables from pcp file, that need to be edited
894 ####################################################################
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 #################################################################################
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 )
936 if ((( $pro eq "pro" ) && ( $installer::globals
::pro
)) || (( $pro eq "nonpro" ) && ( ! $installer::globals
::pro
))) { $pro_is_good = 1; }
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}) )
953 if ( ! $not_included )
955 foreach my $onelanguage ( keys %{$langstringhash} )
957 if ( ! exists($langlisthash->{$onelanguage}) )
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) = @_;
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*$/ )
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) )
1005 installer
::exiter
::exit_program
("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_requiredpatchfile_from_list");
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 ) = @_;
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;
1032 if ( open( OUT
, ">", $filename ) )
1034 print OUT
@localfile;
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
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*$/ )
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); }
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");
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;
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
1174 check_and_save_tables
($tablelist, $localmspdir);
1176 # Setting the name of the new msp file
1177 my $mspfilename = set_mspfilename
($allvariables, $mspdir, $languagesarrayref);
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);
1186 installer
::logger
::include_timestamp_into_logfile
("\nPerformance Info: Starting msimsp.exe");
1187 my $msimsplogfile = execute_msimsp
($fullpcpfilename, $mspfilename, $localmspdir);
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);
1211 $infoline = "ERROR: Could not execute \"$systemcall\"!\n";
1212 push( @installer::globals
::logfileinfo
, $infoline);
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;
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);
1259 installer
::logger
::include_timestamp_into_logfile
("\nPerformance Info: msp creation done");