1#
2# This file is part of the LibreOffice project.
3#
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/.
7#
8# This file incorporates work covered by the following license notice:
9#
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 .
17#
18
19package installer::windows::msp;
20
21use File::Copy;
22use installer::control;
23use installer::converter;
24use installer::exiter;
25use installer::files;
26use installer::globals;
27use installer::logger;
28use installer::pathanalyzer;
29use installer::systemactions;
30use installer::windows::admin;
31use installer::windows::idtglobal;
32use installer::windows::update;
33
34#################################################################################
35# Making all required administrative installations
36#################################################################################
37
38sub install_installation_sets
39{
40    my ($installationdir) = @_;
41
42    # Finding the msi database in the new installation set, that is located in $installationdir
43
44    my $msifiles = installer::systemactions::find_file_with_file_extension("msi", $installationdir);
45
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"); }
48
49    my $newinstallsetdatabasepath = $installationdir . $installer::globals::separator . ${$msifiles}[0];
50    my $oldinstallsetdatabasepath = $installer::globals::updatedatabasepath;
51
52    # Creating temp directory again
53    installer::systemactions::create_directory_structure($installer::globals::temppath);
54
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); }
59
60    my $oldinstallpath = $installpath . $installer::globals::separator . "old";
61    my $newinstallpath = $installpath . $installer::globals::separator . "new";
62
63    if ( ! -d $oldinstallpath) { installer::systemactions::create_directory($oldinstallpath); }
64    if ( ! -d $newinstallpath) { installer::systemactions::create_directory($newinstallpath); }
65
66    my $olddatabase = installer::windows::admin::make_admin_install($oldinstallsetdatabasepath, $oldinstallpath);
67    my $newdatabase = installer::windows::admin::make_admin_install($newinstallsetdatabasepath, $newinstallpath);
68
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;
74    }
75
76    return ($olddatabase, $newdatabase);
77}
78
79#################################################################################
80# Extracting all tables from a pcp file
81#################################################################################
82
83sub extract_all_tables_from_pcpfile
84{
85    my ($fullpcpfilepath, $workdir) = @_;
86
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
92
93    my $localfullpcpfile = $fullpcpfilepath;
94    my $localworkdir = $workdir;
95
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 = "\\";
101    }
102    if ( $^O =~ /linux/i ) {
103        $extraslash = "\\";
104    }
105
106    # Export of all tables by using "*"
107
108    $systemcall = $msidb . " -d " . $localfullpcpfile . " -f " . $localworkdir . " -e " . $extraslash . "*";
109    $returnvalue = system($systemcall);
110
111    $infoline = "Systemcall: $systemcall\n";
112    push( @installer::globals::logfileinfo, $infoline);
113
114    if ($returnvalue)
115    {
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");
119    }
120    else
121    {
122        $infoline = "Success: Executed $systemcall successfully!\n";
123        push( @installer::globals::logfileinfo, $infoline);
124    }
125}
126
127#################################################################################
128# Include tables into a pcp file
129#################################################################################
130
131sub include_tables_into_pcpfile
132{
133    my ($fullpcpfilepath, $workdir, $tables) = @_;
134
135    my $msidb = "msidb.exe";    # Has to be in the path
136    my $infoline = "";
137    my $systemcall = "";
138    my $returnvalue = "";
139
140    # Make all table 8+3 conform
141    my $alltables = installer::converter::convert_stringlist_into_array(\$tables, " ");
142
143    for ( my $i = 0; $i <= $#{$alltables}; $i++ )
144    {
145        my $tablename = ${$alltables}[$i];
146        $tablename =~ s/\s*$//;
147        my $namelength = length($tablename);
148        if ( $namelength > 8 )
149        {
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);
155        }
156    }
157
158    # Import of tables
159
160    my $localworkdir = $workdir;
161    my $localfullpcpfilepath = $fullpcpfilepath;
162
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;
167    }
168
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)
171    {
172        $systemcall = $msidb . " -d " . $localfullpcpfilepath . " -f " . $localworkdir . " -i " . $table;
173
174        $returnvalue = system($systemcall);
175
176        $infoline = "Systemcall: $systemcall\n";
177        push( @installer::globals::logfileinfo, $infoline);
178
179        if ($returnvalue)
180        {
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");
184        }
185        else
186        {
187            $infoline = "Success: Executed $systemcall successfully!\n";
188            push( @installer::globals::logfileinfo, $infoline);
189        }
190    }
191}
192
193#################################################################################
194# Calling msimsp.exe
195#################################################################################
196
197sub execute_msimsp
198{
199    my ($fullpcpfilename, $mspfilename, $localmspdir) = @_;
200
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";
206
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);
210
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); }
215
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
217
218    if ( -f $logfilename ) { unlink $logfilename; }
219
220    my $localfullpcpfilename = $fullpcpfilename;
221    my $localmspfilename = $mspfilename;
222    my $locallogfilename = $logfilename;
223    my $localmsimsptemppath = $msimsptemppath;
224
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;
229
230        $localmspfilename =~ s/\\/\\\\/g; # path already contains backslash
231
232        $localmsimsptemppath = qx{cygpath -w "$localmsimsptemppath"};
233        $localmsimsptemppath =~ s/\\/\\\\/g;
234        $localmsimsptemppath =~ s/\s*$//g;
235    }
236
237    $systemcall = $msimsp . " -s " . $localfullpcpfilename . " -p " . $localmspfilename . " -l " . $locallogfilename . " -f " . $localmsimsptemppath;
238    installer::logger::print_message( "... $systemcall ...\n" );
239
240    $returnvalue = system($systemcall);
241
242    $infoline = "Systemcall: $systemcall\n";
243    push( @installer::globals::logfileinfo, $infoline);
244
245    if ($returnvalue)
246    {
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");
250    }
251    else
252    {
253        $infoline = "Success: Executed $systemcall successfully!\n";
254        push( @installer::globals::logfileinfo, $infoline);
255    }
256
257    return $logfilename;
258}
259
260####################################################################
261# Checking existence and saving all tables, that need to be edited
262####################################################################
263
264sub check_and_save_tables
265{
266    my ($tablelist, $workdir) = @_;
267
268    my $tables = installer::converter::convert_stringlist_into_array(\$tablelist, " ");
269
270    for ( my $i = 0; $i <= $#{$tables}; $i++ )
271    {
272        my $filename = ${$tables}[$i];
273        $filename =~ s/\s*$//;
274        my $fullfilename = $workdir . $installer::globals::separator . $filename . ".idt";
275
276        if ( ! -f $fullfilename ) { installer::exiter::exit_program("ERROR: Required idt file could not be found: \"$fullfilename\"!", "check_and_save_tables"); }
277
278        my $savfilename = $fullfilename . ".sav";
279        installer::systemactions::copy_one_file($fullfilename, $savfilename);
280    }
281}
282
283####################################################################
284# Setting the name of the msp database
285####################################################################
286
287sub set_mspfilename
288{
289    my ($allvariables, $mspdir, $languagesarrayref) = @_;
290
291    my $databasename = $allvariables->{'PRODUCTNAME'} . "-" . $allvariables->{'PRODUCTVERSION'} . "-" . $allvariables->{'WINDOWSPATCHLEVEL'} . ".msp";
292
293    my $fullmspname = $mspdir . $installer::globals::separator . $databasename;
294
295    if ( $^O =~ /cygwin/i ) { $fullmspname =~ s/\//\\/g; }
296
297    return $fullmspname;
298}
299
300####################################################################
301# Editing table Properties
302####################################################################
303
304sub change_properties_table
305{
306    my ($localmspdir, $mspfilename) = @_;
307
308    my $infoline = "Changing content of table \"Properties\"\n";
309    push( @installer::globals::logfileinfo, $infoline);
310
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"); }
313
314    my $filecontent = installer::files::read_file($filename);
315
316
317    my $guidref = installer::windows::msiglobal::get_guid_list(1, 1);
318    ${$guidref}[0] =~ s/\s*$//;     # removing ending spaces
319    my $patchcode = "\{" . ${$guidref}[0] . "\}";
320
321    # Setting "PatchOutputPath"
322    my $found_patchoutputpath = 0;
323    my $found_patchguid = 0;
324
325    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
326    {
327        if ( ${$filecontent}[$i] =~ /^\s*PatchOutputPath\t(.*?)\s*$/ )
328        {
329            my $oldvalue = $1;
330            ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$mspfilename/;
331            $found_patchoutputpath = 1;
332        }
333
334        if ( ${$filecontent}[$i] =~ /^\s*PatchGUID\t(.*?)\s*$/ )
335        {
336            my $oldvalue = $1;
337            ${$filecontent}[$i] =~ s/\Q$oldvalue\E/$patchcode/;
338            $found_patchguid = 1;
339        }
340    }
341
342    if ( ! $found_patchoutputpath )
343    {
344        my $newline = "PatchOutputPath\t$mspfilename\n";
345        push(@{$filecontent}, $newline);
346    }
347
348    if ( ! $found_patchguid )
349    {
350        my $newline = "PatchGUID\t$patchcode\n";
351        push(@{$filecontent}, $newline);
352    }
353
354    # saving file
355    installer::files::save_file($filename, $filecontent);
356}
357
358####################################################################
359# Editing table TargetImages
360####################################################################
361
362sub change_targetimages_table
363{
364    my ($localmspdir, $olddatabase) = @_;
365
366    my $infoline = "Changing content of table \"TargetImages\"\n";
367    push( @installer::globals::logfileinfo, $infoline);
368
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"); }
371
372    my $filecontent = installer::files::read_file($filename);
373    my @newcontent = ();
374
375    # Copying the header
376    for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
377
378    #Adding all targets
379    my $newline = "T1\t$olddatabase\t\tU1\t1\t0x00000922\t1\n";
380    push(@newcontent, $newline);
381
382    # saving file
383    installer::files::save_file($filename, \@newcontent);
384}
385
386####################################################################
387# Editing table UpgradedImages
388####################################################################
389
390sub change_upgradedimages_table
391{
392    my ($localmspdir, $newdatabase) = @_;
393
394    my $infoline = "Changing content of table \"UpgradedImages\"\n";
395    push( @installer::globals::logfileinfo, $infoline);
396
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"); }
399
400    my $filecontent = installer::files::read_file($filename);
401    my @newcontent = ();
402
403    # Copying the header
404    for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
405
406    # Syntax: Upgraded MsiPath PatchMsiPath SymbolPaths Family
407
408    # default values
409    my $upgraded = "U1";
410    my $msipath = $newdatabase;
411    my $patchmsipath = "";
412    my $symbolpaths = "";
413    my $family = "22334455";
414
415    if ( $#{$filecontent} >= 3 )
416    {
417        my $line = ${$filecontent}[3];
418        if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
419        {
420            $upgraded = $1;
421            $patchmsipath = $3;
422            $symbolpaths = $4;
423            $family = $5;
424        }
425    }
426
427    #Adding sequence line, saving PatchFamily
428    my $newline = "$upgraded\t$msipath\t$patchmsipath\t$symbolpaths\t$family\n";
429    push(@newcontent, $newline);
430
431    # saving file
432    installer::files::save_file($filename, \@newcontent);
433}
434
435####################################################################
436# Editing table ImageFamilies
437####################################################################
438
439sub change_imagefamilies_table
440{
441    my ($localmspdir) = @_;
442
443    my $infoline = "Changing content of table \"ImageFamilies\"\n";
444    push( @installer::globals::logfileinfo, $infoline);
445
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"); }
448
449    my $filecontent = installer::files::read_file($filename);
450    my @newcontent = ();
451
452    # Copying the header
453    for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
454
455    # Syntax: Family MediaSrcPropName MediaDiskId FileSequenceStart DiskPrompt VolumeLabel
456    # "FileSequenceStart has to be set
457
458    # Default values:
459
460    my $family = "22334455";
461    my $mediasrcpropname = "MediaSrcPropName";
462    my $mediadiskid = "2";
463    my $filesequencestart = get_filesequencestart();
464    my $diskprompt = "";
465    my $volumelabel = "";
466
467    if ( $#{$filecontent} >= 3 )
468    {
469        my $line = ${$filecontent}[3];
470        if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
471        {
472            $family = $1;
473            $mediasrcpropname = $2;
474            $mediadiskid = $3;
475            $diskprompt = $5;
476            $volumelabel = $6;
477        }
478    }
479
480    #Adding sequence line
481    my $newline = "$family\t$mediasrcpropname\t$mediadiskid\t$filesequencestart\t$diskprompt\t$volumelabel\n";
482    push(@newcontent, $newline);
483
484    # saving file
485    installer::files::save_file($filename, \@newcontent);
486}
487
488####################################################################
489# Setting start sequence for patch
490####################################################################
491
492sub get_filesequencestart
493{
494    my $sequence = 1000;  # default
495
496    if ( $installer::globals::updatelastsequence ) { $sequence = $installer::globals::updatelastsequence + 500; }
497
498    return $sequence;
499}
500
501####################################################################
502# Setting time value into pcp file
503# Format mm/dd/yyyy hh:mm
504####################################################################
505
506sub get_patchtime_value
507{
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];
514
515    $month++; # zero based month
516    if ( $minute  < 10 ) { $minute = "0" . $minute; }
517    if ( $hour  < 10 ) { $hour = "0" . $hour; }
518
519    my $timestring = $month . "/" . $day . "/" . $year . " " . $hour . ":" . $minute;
520
521    return $timestring;
522}
523
524#################################################################################
525# Checking, if this is the correct database.
526#################################################################################
527
528sub correct_langs
529{
530    my ($langs, $languagestringref) = @_;
531
532    my $correct_langs = 0;
533
534    # Comparing $langs with $languagestringref
535
536    my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
537    my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
538
539    my $not_included = 0;
540    foreach my $onelang ( keys %{$langlisthash} )
541    {
542        if ( ! exists($langstringhash->{$onelang}) )
543        {
544            $not_included = 1;
545            last;
546        }
547    }
548
549    if ( ! $not_included )
550    {
551        foreach my $onelanguage ( keys %{$langstringhash} )
552        {
553            if ( ! exists($langlisthash->{$onelanguage}) )
554            {
555                $not_included = 1;
556                last;
557            }
558        }
559
560        if ( ! $not_included ) { $correct_langs = 1; }
561    }
562
563    return $correct_langs;
564}
565
566#################################################################################
567# Searching for the path to the reference database for this special product.
568#################################################################################
569
570sub get_patchid_from_list
571{
572    my ($filecontent, $languagestringref, $filename) = @_;
573
574    my $patchid = "";
575
576    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
577    {
578        my $line = ${$filecontent}[$i];
579        if ( $line =~ /^\s*$/ ) { next; } # empty line
580        if ( $line =~ /^\s*\#/ ) { next; } # comment line
581
582        if ( $line =~ /^\s*(.+?)\s*=\s*(.+?)\s*$/ )
583        {
584            my $langs = $1;
585            my $localpatchid = $2;
586
587            if ( correct_langs($langs, $languagestringref) )
588            {
589                $patchid = $localpatchid;
590                last;
591            }
592        }
593        else
594        {
595            installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_patchid_from_list");
596        }
597    }
598
599    return $patchid;
600}
601
602####################################################################
603# Editing table PatchMetadata
604####################################################################
605
606sub change_patchmetadata_table
607{
608    my ($localmspdir, $allvariables, $languagestringref) = @_;
609
610    my $infoline = "Changing content of table \"PatchMetadata\"\n";
611    push( @installer::globals::logfileinfo, $infoline);
612
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"); }
615
616    my $filecontent = installer::files::read_file($filename);
617    my @newcontent = ();
618
619    # Syntax: Company Property Value
620    # Interesting properties: "Classification" and "CreationTimeUTC"
621
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;
629
630    my $defaultcompany = "";
631
632    my $classificationstring = "Classification";
633    my $classificationvalue = "Hotfix";
634    if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $classificationvalue = "ServicePack"; }
635
636    my $allowremovalstring = "AllowRemoval";
637    my $allowremovalvalue = "1";
638    if (( exists($allvariables->{'MSPALLOWREMOVAL'}) ) && ( $allvariables->{'MSPALLOWREMOVAL'} == 0 )) { $allowremovalvalue = 0; }
639
640    my $timestring = "CreationTimeUTC";
641    # Syntax: 8/8/2008 11:55
642    my $timevalue = get_patchtime_value();
643
644    my $targetproductnamestring = "TargetProductName";
645    my $targetproductnamevalue = $allvariables->{'PRODUCTNAME'};
646    if ( $allvariables->{'PROPERTYTABLEPRODUCTNAME'} ) { $targetproductnamevalue = $allvariables->{'PROPERTYTABLEPRODUCTNAME'}; }
647
648    my $manufacturerstring = "ManufacturerName";
649    my $manufacturervalue = $ENV{'OOO_VENDOR'};
650    if ( $installer::globals::longmanufacturer ) { $manufacturervalue = $installer::globals::longmanufacturer; }
651
652    my $displaynamestring = "DisplayName";
653    my $descriptionstring = "Description";
654    my $displaynamevalue = "";
655    my $descriptionvalue = "";
656
657    my $base = $allvariables->{'PRODUCTNAME'} . " " . $allvariables->{'PRODUCTVERSION'};
658    if ( $installer::globals::languagepack || $installer::globals::helppack ) { $base = $targetproductnamevalue; }
659
660    my $windowspatchlevel = 0;
661    if ( $allvariables->{'WINDOWSPATCHLEVEL'} ) { $windowspatchlevel = $allvariables->{'WINDOWSPATCHLEVEL'}; }
662
663    my $displayaddon = "";
664    if ( $allvariables->{'PATCHDISPLAYADDON'} ) { $displayaddon = $allvariables->{'PATCHDISPLAYADDON'}; }
665
666    my $patchsequence = get_patchsequence($allvariables);
667
668    if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 ))
669    {
670        $displaynamevalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid;
671        $descriptionvalue = $base . " ServicePack " . $windowspatchlevel . " " . $patchsequence . " Build: " . $installer::globals::buildid;
672    }
673    else
674    {
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;
683    }
684
685    if ( $allvariables->{'MSPPATCHNAMELIST'} )
686    {
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);
691
692        # Get name and path of reference database
693        my $patchid = get_patchid_from_list($filecontent, $languagestringref, $patchnamelistfile);
694
695        if ( $patchid eq "" ) { installer::exiter::exit_program("ERROR: Could not find file patchid in file \"$patchnamelistfile\" for language(s) \"$$languagestringref\".", "change_patchmetadata_table"); }
696
697        # Setting language specific patch id
698    }
699
700    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
701    {
702        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
703        {
704            my $company = $1;
705            my $property = $2;
706            my $value = $3;
707
708            if ( $property eq $classificationstring )
709            {
710                ${$filecontent}[$i] = "$company\t$property\t$classificationvalue\n";
711                $classification_set = 1;
712            }
713
714            if ( $property eq $allowremovalstring )
715            {
716                ${$filecontent}[$i] = "$company\t$property\t$allowremovalvalue\n";
717                $allowremoval_set = 1;
718            }
719
720            if ( $property eq $timestring )
721            {
722                ${$filecontent}[$i] = "$company\t$property\t$timevalue\n";
723                $creationtime_set = 1;
724            }
725
726            if ( $property eq $targetproductnamestring )
727            {
728                ${$filecontent}[$i] = "$company\t$property\t$targetproductnamevalue\n";
729                $targetproductname_set = 1;
730            }
731
732            if ( $property eq $manufacturerstring )
733            {
734                ${$filecontent}[$i] = "$company\t$property\t$manufacturervalue\n";
735                $manufacturer_set = 1;
736            }
737
738            if ( $property eq $displaynamestring )
739            {
740                ${$filecontent}[$i] = "$company\t$property\t$displaynamevalue\n";
741                $displayname_set = 1;
742            }
743
744            if ( $property eq $descriptionstring )
745            {
746                ${$filecontent}[$i] = "$company\t$property\t$descriptionvalue\n";
747                $description_set = 1;
748            }
749        }
750
751        push(@newcontent, ${$filecontent}[$i]);
752    }
753
754    if ( ! $classification_set )
755    {
756        my $line = "$defaultcompany\t$classificationstring\t$classificationvalue\n";
757        push(@newcontent, $line);
758    }
759
760    if ( ! $allowremoval_set )
761    {
762        my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n";
763        push(@newcontent, $line);
764    }
765
766    if ( ! $allowremoval_set )
767    {
768        my $line = "$defaultcompany\t$classificationstring\t$allowremovalvalue\n";
769        push(@newcontent, $line);
770    }
771
772    if ( ! $creationtime_set )
773    {
774        my $line = "$defaultcompany\t$timestring\t$timevalue\n";
775        push(@newcontent, $line);
776    }
777
778    if ( ! $targetproductname_set )
779    {
780        my $line = "$defaultcompany\t$targetproductnamestring\t$targetproductnamevalue\n";
781        push(@newcontent, $line);
782    }
783
784    if ( ! $manufacturer_set )
785    {
786        my $line = "$defaultcompany\t$manufacturerstring\t$manufacturervalue\n";
787        push(@newcontent, $line);
788    }
789
790    if ( ! $displayname_set )
791    {
792        my $line = "$defaultcompany\t$displaynamestring\t$displaynamevalue\n";
793        push(@newcontent, $line);
794    }
795
796    if ( ! $description_set )
797    {
798        my $line = "$defaultcompany\t$descriptionstring\t$descriptionvalue\n";
799        push(@newcontent, $line);
800    }
801
802    # saving file
803    installer::files::save_file($filename, \@newcontent);
804}
805
806####################################################################
807# Editing table PatchSequence
808####################################################################
809
810sub change_patchsequence_table
811{
812    my ($localmspdir, $allvariables) = @_;
813
814    my $infoline = "Changing content of table \"PatchSequence\"\n";
815    push( @installer::globals::logfileinfo, $infoline);
816
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"); }
819
820    my $filecontent = installer::files::read_file($filename);
821    my @newcontent = ();
822
823    # Copying the header
824    for ( my $i = 0; $i <= $#{$filecontent}; $i++ ) { if ( $i < 3 ) { push(@newcontent, ${$filecontent}[$i]); } }
825
826    # Syntax: PatchFamily Target Sequence Supersede
827
828    my $patchfamily = "SO";
829    my $target = "";
830    my $patchsequence = get_patchsequence($allvariables);
831    my $supersede = get_supersede($allvariables);
832
833    if ( $#{$filecontent} >= 3 )
834    {
835        my $line = ${$filecontent}[3];
836        if ( $line =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\s$/ )
837        {
838            $patchfamily = $1;
839            $target = $2;
840        }
841    }
842
843    #Adding sequence line, saving PatchFamily
844    my $newline = "$patchfamily\t$target\t$patchsequence\t$supersede\n";
845    push(@newcontent, $newline);
846
847    # saving file
848    installer::files::save_file($filename, \@newcontent);
849}
850
851####################################################################
852# Setting supersede, "0" for Hotfixes, "1" for ServicePack
853####################################################################
854
855sub get_supersede
856{
857    my ( $allvariables ) = @_;
858
859    my $supersede = 0;  # if not defined, this is a Hotfix
860
861    if (( $allvariables->{'SERVICEPACK'} ) && ( $allvariables->{'SERVICEPACK'} == 1 )) { $supersede = 1; }
862
863    return $supersede;
864}
865
866####################################################################
867# Setting the sequence of the patch
868####################################################################
869
870sub get_patchsequence
871{
872    my ( $allvariables ) = @_;
873
874    my $patchsequence = "1.0";
875
876    if ( ! $allvariables->{'PACKAGEVERSION'} ) { installer::exiter::exit_program("ERROR: PACKAGEVERSION must be set for msp patch creation!", "get_patchsequence"); }
877
878    my $packageversion = $allvariables->{'PACKAGEVERSION'};
879
880    if ( $packageversion =~ /^\s*(\d+)\.(\d+)\.(\d+)\.(\d+)\s*$/ )
881    {
882        my $major = $1;
883        my $minor = $2;
884        my $micro = $3;
885        my $patch = $4;
886        $patchsequence = $major . "\." . $minor . "\." . $micro . "\." . $patch;
887    }
888
889    return $patchsequence;
890}
891
892####################################################################
893# Editing all tables from pcp file, that need to be edited
894####################################################################
895
896sub edit_tables
897{
898    my ($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref) = @_;
899
900    # table list contains:  my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence";
901
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);
908}
909
910#################################################################################
911# Checking, if this is the correct database.
912#################################################################################
913
914sub correct_patch
915{
916    my ($product, $pro, $langs, $languagestringref) = @_;
917
918    my $correct_patch = 0;
919
920    # Comparing $product with $installer::globals::product and
921    # $pro with $installer::globals::pro and
922    # $langs with $languagestringref
923
924    my $product_is_good = 0;
925
926    my $localproduct = $installer::globals::product;
927    if ( $installer::globals::languagepack ) { $localproduct = $localproduct . "LanguagePack"; }
928    elsif ( $installer::globals::helppack ) { $localproduct = $localproduct . "HelpPack"; }
929
930    if ( $product eq $localproduct ) { $product_is_good = 1; }
931
932    if ( $product_is_good )
933    {
934        my $pro_is_good = 0;
935
936        if ((( $pro eq "pro" ) && ( $installer::globals::pro )) || (( $pro eq "nonpro" ) && ( ! $installer::globals::pro ))) { $pro_is_good = 1; }
937
938        if ( $pro_is_good )
939        {
940            my $langlisthash = installer::converter::convert_stringlist_into_hash(\$langs, ",");
941            my $langstringhash = installer::converter::convert_stringlist_into_hash($languagestringref, "_");
942
943            my $not_included = 0;
944            foreach my $onelang ( keys %{$langlisthash} )
945            {
946                if ( ! exists($langstringhash->{$onelang}) )
947                {
948                    $not_included = 1;
949                    last;
950                }
951            }
952
953            if ( ! $not_included )
954            {
955                foreach my $onelanguage ( keys %{$langstringhash} )
956                {
957                    if ( ! exists($langlisthash->{$onelanguage}) )
958                    {
959                        $not_included = 1;
960                        last;
961                    }
962                }
963
964                if ( ! $not_included ) { $correct_patch = 1; }
965            }
966        }
967    }
968
969    return $correct_patch;
970}
971
972#################################################################################
973# Searching for the path to the required patch for this special product.
974#################################################################################
975
976sub get_requiredpatchfile_from_list
977{
978    my ($filecontent, $languagestringref, $filename) = @_;
979
980    my $patchpath = "";
981
982    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
983    {
984        my $line = ${$filecontent}[$i];
985        if ( $line =~ /^\s*$/ ) { next; } # empty line
986        if ( $line =~ /^\s*\#/ ) { next; } # comment line
987
988        if ( $line =~ /^\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*\t+\s*(.+?)\s*$/ )
989        {
990            my $product = $1;
991            my $pro = $2;
992            my $langs = $3;
993            my $path = $4;
994
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"); }
996
997            if ( correct_patch($product, $pro, $langs, $languagestringref) )
998            {
999                $patchpath = $path;
1000                last;
1001            }
1002        }
1003        else
1004        {
1005            installer::exiter::exit_program("ERROR: Wrong syntax in file: $filename! Line: \"$line\"", "get_requiredpatchfile_from_list");
1006        }
1007    }
1008
1009    return $patchpath;
1010}
1011
1012##################################################################
1013# Converting unicode file to ascii
1014# to be more precise: uft-16 little endian to ascii
1015##################################################################
1016
1017sub convert_unicode_to_ascii
1018{
1019    my ( $filename ) = @_;
1020
1021    my @localfile = ();
1022
1023    my $savfilename = $filename . "_before.unicode";
1024    installer::systemactions::copy_one_file($filename, $savfilename);
1025
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;
1029    }
1030    close( IN );
1031
1032    if ( open( OUT, ">", $filename ) )
1033    {
1034        print OUT @localfile;
1035        close(OUT);
1036    }
1037}
1038
1039####################################################################
1040# Analyzing the log file created by msimsp.exe to find all
1041# files included into the patch.
1042####################################################################
1043
1044sub analyze_msimsp_logfile
1045{
1046    my ($logfile, $filesarray) = @_;
1047
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);
1051
1052    # Creating hash from $filesarray: unique file name -> destination of file
1053    my %filehash = ();
1054    my %destinationcollector = ();
1055
1056    for ( my $i = 0; $i <= $#{$filesarray}; $i++ )
1057    {
1058        my $onefile = ${$filesarray}[$i];
1059
1060        # Only collecting files with "uniquename" and "destination"
1061        if (( exists($onefile->{'uniquename'}) ) && ( exists($onefile->{'uniquename'}) ))
1062        {
1063            my $uniquefilename = $onefile->{'uniquename'};
1064            my $destpath = $onefile->{'destination'};
1065            $filehash{$uniquefilename} = $destpath;
1066        }
1067    }
1068
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
1073
1074    for ( my $i = 0; $i <= $#{$logfilecontent}; $i++ )
1075    {
1076        if ( ${$logfilecontent}[$i] =~ /Key\:\s*(.*?) is modified\s*$/ )
1077        {
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"); }
1081        }
1082    }
1083
1084    foreach my $onedest ( sort keys %destinationcollector ) { push(@installer::globals::patchfilecollector, "$onedest\n"); }
1085
1086}
1087
1088####################################################################
1089# Creating msp patch files for Windows
1090####################################################################
1091
1092sub create_msp_patch
1093{
1094    my ($installationdir, $includepatharrayref, $allvariables, $languagestringref, $languagesarrayref, $filesarray) = @_;
1095
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" );
1100
1101    $installer::globals::creating_windows_installer_patch = 1;
1102
1103    my @needed_files = ("msimsp.exe");  # only required for patch creation process
1104    installer::control::check_needed_files_in_path(\@needed_files);
1105
1106    installer::logger::include_header_into_logfile("Creating msp installation sets:");
1107
1108    my $firstdir = $installationdir;
1109    installer::pathanalyzer::get_path_from_fullqualifiedname(\$firstdir);
1110
1111    my $lastdir = $installationdir;
1112    installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$lastdir);
1113
1114    if ( $lastdir =~ /\./ ) { $lastdir =~ s/\./_msp_inprogress\./ }
1115    else { $lastdir = $lastdir . "_msp_inprogress"; }
1116
1117    # Removing existing directory "_native_packed_inprogress" and "_native_packed_witherror" and "_native_packed"
1118
1119    my $mspdir = $firstdir . $lastdir;
1120    if ( -d $mspdir ) { installer::systemactions::remove_complete_directory($mspdir); }
1121
1122    my $olddir = $mspdir;
1123    $olddir =~ s/_inprogress/_witherror/;
1124    if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); }
1125
1126    $olddir = $mspdir;
1127    $olddir =~ s/_inprogress//;
1128    if ( -d $olddir ) { installer::systemactions::remove_complete_directory($olddir); }
1129
1130    # Creating the new directory for new installation set
1131    installer::systemactions::create_directory($mspdir);
1132
1133    $installer::globals::saveinstalldir = $mspdir;
1134
1135    installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting product installation");
1136
1137    # Installing both installation sets
1138    installer::logger::print_message( "... installing products ...\n" );
1139    my ($olddatabase, $newdatabase) = install_installation_sets($installationdir);
1140
1141    installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting pcp file creation");
1142
1143    # Create pcp file
1144    installer::logger::print_message( "... creating pcp file ...\n" );
1145
1146    my $localmspdir = installer::systemactions::create_directories("msp", $languagestringref);
1147
1148    if ( ! $allvariables->{'PCPFILENAME'} ) { installer::exiter::exit_program("ERROR: Property \"PCPFILENAME\" has to be defined.", "create_msp_patch"); }
1149    my $pcpfilename = $allvariables->{'PCPFILENAME'};
1150
1151    if ( $installer::globals::languagepack ) { $pcpfilename =~ s/.pcp\s*$/languagepack.pcp/; }
1152    elsif ( $installer::globals::helppack ) { $pcpfilename =~ s/.pcp\s*$/helppack.pcp/; }
1153
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;
1158
1159    # Copying pcp file
1160    my $fullpcpfilename = $localmspdir . $installer::globals::separator . $pcpfilename;
1161    installer::systemactions::copy_one_file($fullpcpfilenamesource, $fullpcpfilename);
1162
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, ...
1166
1167    # Unpacking tables from pcp file
1168    extract_all_tables_from_pcpfile($fullpcpfilename, $localmspdir);
1169
1170    # Tables, that need to be edited
1171    my $tablelist = "Properties TargetImages UpgradedImages ImageFamilies PatchMetadata PatchSequence"; # required tables
1172
1173    # Saving all tables
1174    check_and_save_tables($tablelist, $localmspdir);
1175
1176    # Setting the name of the new msp file
1177    my $mspfilename = set_mspfilename($allvariables, $mspdir, $languagesarrayref);
1178
1179    # Editing tables
1180    edit_tables($tablelist, $localmspdir, $olddatabase, $newdatabase, $mspfilename, $allvariables, $languagestringref);
1181
1182    # Adding edited tables into pcp file
1183    include_tables_into_pcpfile($fullpcpfilename, $localmspdir, $tablelist);
1184
1185    # Start msimsp.exe
1186    installer::logger::include_timestamp_into_logfile("\nPerformance Info: Starting msimsp.exe");
1187    my $msimsplogfile = execute_msimsp($fullpcpfilename, $mspfilename, $localmspdir);
1188
1189    # Sign .msp file
1190    if ( defined($ENV{'WINDOWS_BUILD_SIGNING'}) && ($ENV{'WINDOWS_BUILD_SIGNING'} eq 'TRUE') )
1191    {
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" );
1201
1202        my $returnvalue = system($systemcall);
1203
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);
1208
1209        if ($returnvalue)
1210        {
1211            $infoline = "ERROR: Could not execute \"$systemcall\"!\n";
1212            push( @installer::globals::logfileinfo, $infoline);
1213        }
1214        else
1215        {
1216            $infoline = "Success: Executed \"$systemcall\" successfully!\n";
1217            push( @installer::globals::logfileinfo, $infoline);
1218        }
1219    }
1220
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" );
1224
1225    my $oldinstallationsetpath = $installer::globals::updatedatabasepath;
1226
1227    if ( $^O =~ /cygwin/i ) { $oldinstallationsetpath =~ s/\\/\//g; }
1228
1229    installer::pathanalyzer::get_path_from_fullqualifiedname(\$oldinstallationsetpath);
1230    installer::systemactions::copy_complete_directory($oldinstallationsetpath, $mspdir);
1231
1232    # Copying additional patches into the installation set, if required
1233    if (( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ) && ( $allvariables->{'ADDITIONALREQUIREDPATCHES'} ne "" ) && ( ! $installer::globals::languagepack ) && ( ! $installer::globals::helppack ))
1234    {
1235        my $filename = $allvariables->{'ADDITIONALREQUIREDPATCHES'};
1236
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;
1240
1241        # Reading list file
1242        my $listfile = installer::files::read_file($fullfilename);
1243
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"); }
1247
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);
1252    }
1253
1254    # Find all files included into the patch
1255    # Analyzing the msimsp log file $msimsplogfile
1256    analyze_msimsp_logfile($msimsplogfile, $filesarray);
1257
1258    # Done
1259    installer::logger::include_timestamp_into_logfile("\nPerformance Info: msp creation done");
1260
1261    return $mspdir;
1262}
1263
12641;
1265