xref: /AOO41X/main/setup_native/scripts/admin.pl (revision 7e90fac2499926267c39e1b60f243e5765a5bf84)
1#**************************************************************
2#
3#  Licensed to the Apache Software Foundation (ASF) under one
4#  or more contributor license agreements.  See the NOTICE file
5#  distributed with this work for additional information
6#  regarding copyright ownership.  The ASF licenses this file
7#  to you under the Apache License, Version 2.0 (the
8#  "License"); you may not use this file except in compliance
9#  with the License.  You may obtain a copy of the License at
10#
11#    http://www.apache.org/licenses/LICENSE-2.0
12#
13#  Unless required by applicable law or agreed to in writing,
14#  software distributed under the License is distributed on an
15#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16#  KIND, either express or implied.  See the License for the
17#  specific language governing permissions and limitations
18#  under the License.
19#
20#**************************************************************
21
22
23
24use Cwd;
25use File::Copy;
26
27#################################################################################
28# Global settings
29#################################################################################
30
31BEGIN
32{
33    $prog = "msi installer";
34    $targetdir = "";
35    $databasepath = "";
36    $starttime = "";
37    $globaltempdirname = "ooopackaging";
38    $savetemppath = "";
39    $msiinfo_available = 0;
40    $path_displayed = 0;
41    $localmsidbpath = "";
42
43    $plat = $^O;
44
45    if ( $plat =~ /cygwin/i )
46    {
47        $separator = "/";
48        $pathseparator = "\:";
49    }
50    else
51    {
52        $separator = "\\";
53        $pathseparator = "\;";
54    }
55}
56
57#################################################################################
58# Program information
59#################################################################################
60
61sub usage
62{
63    print <<Ende;
64----------------------------------------------------------------------
65This program installs a Windows Installer installation set
66without using msiexec.exe. The installation is comparable
67with an administrative installation using the Windows Installer
68service.
69Required parameter:
70-d Path to installation set or msi database
71-t Target directory
72---------------------------------------------------------------------
73Ende
74    exit(-1);
75}
76
77#################################################################################
78# Collecting parameter
79#################################################################################
80
81sub getparameter
82{
83    if (( $#ARGV < 3 ) || ( $#ARGV > 3 )) { usage(); }
84
85    while ( $#ARGV >= 0 )
86    {
87        my $param = shift(@ARGV);
88
89        if ($param eq "-t") { $targetdir = shift(@ARGV); }
90        elsif ($param eq "-d") { $databasepath = shift(@ARGV); }
91        else
92        {
93            print "\n**********************************************\n";
94            print "Error: Unknows parameter: $param";
95            print "\n**********************************************\n";
96            usage();
97            exit(-1);
98        }
99    }
100}
101
102#################################################################################
103# Checking content of parameter
104#################################################################################
105
106sub controlparameter
107{
108    if ( $targetdir eq "" )
109    {
110        print "\n******************************************************\n";
111        print "Error: Target directory not defined (parameter -t)!";
112        print "\n******************************************************\n";
113        usage();
114        exit(-1);
115    }
116
117    if ( $databasepath eq "" )
118    {
119        print "\n******************************************************\n";
120        print "Error: Path to msi database not defined (parameter -d)!";
121        print "\n******************************************************\n";
122        usage();
123        exit(-1);
124    }
125
126    if ( -d $databasepath )
127    {
128        $databasepath =~ s/\\\s*$//;
129        $databasepath =~ s/\/\s*$//;
130
131        my $msifiles = find_file_with_file_extension("msi", $databasepath);
132
133        if ( $#{$msifiles} < 0 ) { exit_program("ERROR: Did not find msi database in directory $installationdir"); }
134        if ( $#{$msifiles} > 0 ) { exit_program("ERROR: Did find more than one msi database in directory $installationdir"); }
135
136        $databasepath = $databasepath . $separator . ${$msifiles}[0];
137    }
138
139    if ( ! -f $databasepath ) { exit_program("ERROR: Did not find msi database in directory $databasepath."); }
140
141    if ( ! -d $targetdir ) { create_directories($targetdir); }
142}
143
144#############################################################################
145# The program msidb.exe can be located next to the Perl program. Then it is
146# not neccessary to find it in the PATH variable.
147#############################################################################
148
149sub check_local_msidb
150{
151    my $msidbname = "msidb.exe";
152    my $perlprogramm = $0;
153    my $path = $perlprogramm;
154
155    get_path_from_fullqualifiedname(\$path);
156
157    $path =~ s/\\\s*$//;
158    $path =~ s/\/\s*$//;
159
160    my $msidbpath = "";
161    if ( $path =~ /^\s*$/ ) { $msidbpath = $msidbname; }
162    else { $msidbpath = $path . $separator . $msidbname; }
163
164    if ( -f $msidbpath )
165    {
166        $localmsidbpath = $msidbpath;
167        print "Using $msidbpath (next to \"admin.pl\")\n";
168    }
169}
170
171#############################################################################
172# Converting a string list with separator $listseparator
173# into an array
174#############################################################################
175
176sub convert_stringlist_into_array
177{
178    my ( $includestringref, $listseparator ) = @_;
179
180    my @newarray = ();
181    my $first;
182    my $last = ${$includestringref};
183
184    while ( $last =~ /^\s*(.+?)\Q$listseparator\E(.+)\s*$/) # "$" for minimal matching
185    {
186        $first = $1;
187        $last = $2;
188        # Problem with two directly following listseparators. For example a path with two ";;" directly behind each other
189        $first =~ s/^$listseparator//;
190        push(@newarray, "$first\n");
191    }
192
193    push(@newarray, "$last\n");
194
195    return \@newarray;
196}
197
198#########################################################
199# Checking the local system
200# Checking existence of needed files in include path
201#########################################################
202
203sub check_system_path
204{
205    my $onefile;
206    my $error = 0;
207    my $pathvariable = $ENV{'PATH'};
208    my $local_pathseparator = $pathseparator;
209
210    if( $^O =~ /cygwin/i )
211    {   # When using cygwin's perl the PATH variable is POSIX style and ...
212        $pathvariable = qx{cygpath -mp "$pathvariable"} ;
213        # has to be converted to DOS style for further use.
214        $local_pathseparator = ';';
215    }
216    my $patharrayref = convert_stringlist_into_array(\$pathvariable, $local_pathseparator);
217
218    my @needed_files_in_path = ("expand.exe");
219    if ( $localmsidbpath eq "" ) { push(@needed_files_in_path, "msidb.exe"); } # not found locally -> search in path
220    my @optional_files_in_path = ("msiinfo.exe");
221
222    print("\nChecking required files:\n");
223
224    foreach $onefile ( @needed_files_in_path )
225    {
226        print("...... searching $onefile ...");
227
228        my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref);
229
230        if ( $$fileref eq "" )
231        {
232            $error = 1;
233            print( "$onefile not found\n" );
234        }
235        else
236        {
237            print( "\tFound: $$fileref\n" );
238        }
239    }
240
241    if ( $error ) { exit_program("ERROR: Could not find all needed files in path (using setsolar should help)!"); }
242
243    print("\nChecking optional files:\n");
244
245    foreach $onefile ( @optional_files_in_path )
246    {
247        print("...... searching $onefile ...");
248
249        my $fileref = get_sourcepath_from_filename_and_includepath(\$onefile, $patharrayref);
250
251        if ( $$fileref eq "" )
252        {
253            print( "$onefile not found\n" );
254            if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 0; }
255        }
256        else
257        {
258            print( "\tFound: $$fileref\n" );
259            if ( $onefile eq "msiinfo.exe" ) { $msiinfo_available = 1; }
260        }
261    }
262
263}
264
265##########################################################################
266# Searching a file in a list of pathes
267##########################################################################
268
269sub get_sourcepath_from_filename_and_includepath
270{
271    my ($searchfilenameref, $includepatharrayref) = @_;
272
273    my $onefile = "";
274    my $foundsourcefile = 0;
275
276    for ( my $j = 0; $j <= $#{$includepatharrayref}; $j++ )
277    {
278        my $includepath = ${$includepatharrayref}[$j];
279        $includepath =~ s/^\s*//;
280        $includepath =~ s/\s*$//;
281
282        $onefile = $includepath . $separator . $$searchfilenameref;
283
284        if ( -f $onefile )
285        {
286            $foundsourcefile = 1;
287            last;
288        }
289    }
290
291    if (!($foundsourcefile)) { $onefile = ""; }
292
293    return \$onefile;
294}
295
296##############################################################
297# Removing all empty directories below a specified directory
298##############################################################
299
300sub remove_empty_dirs_in_folder
301{
302    my ( $dir, $firstrun ) = @_;
303
304    if ( $firstrun )
305    {
306        print "Removing superfluous directories\n";
307    }
308
309    my @content = ();
310
311    $dir =~ s/\Q$separator\E\s*$//;
312
313    if ( -d $dir )
314    {
315        opendir(DIR, $dir);
316        @content = readdir(DIR);
317        closedir(DIR);
318
319        my $oneitem;
320
321        foreach $oneitem (@content)
322        {
323            if ((!($oneitem eq ".")) && (!($oneitem eq "..")))
324            {
325                my $item = $dir . $separator . $oneitem;
326
327                if ( -d $item ) # recursive
328                {
329                    remove_empty_dirs_in_folder($item, 0);
330                }
331            }
332        }
333
334        # try to remove empty directory
335        my $returnvalue = rmdir $dir;
336
337        # if ( $returnvalue ) { print "Successfully removed empty dir $dir\n"; }
338    }
339}
340
341####################################################
342# Detecting the directory with extensions
343####################################################
344
345sub get_extensions_dir
346{
347    my ( $unopkgfile ) = @_;
348
349    my $localbranddir = $unopkgfile;
350    get_path_from_fullqualifiedname(\$localbranddir); # "program" dir in brand layer
351    get_path_from_fullqualifiedname(\$localbranddir); # root dir in brand layer
352    $localbranddir =~ s/\Q$separator\E\s*$//;
353    my $extensiondir = $localbranddir . $separator . "share" . $separator . "extensions";
354    my $preregdir = $localbranddir . $separator . "share" . $separator . "prereg" . $separator . "bundled";
355
356    return ($extensiondir, $preregdir);
357}
358
359########################################################
360# Finding all files with a specified file extension
361# in a specified directory.
362########################################################
363
364sub find_file_with_file_extension
365{
366    my ($extension, $dir) = @_;
367
368    my @allfiles = ();
369    my @sourcefiles = ();
370
371    $dir =~ s/\Q$separator\E\s*$//;
372
373    opendir(DIR, $dir);
374    @sourcefiles = readdir(DIR);
375    closedir(DIR);
376
377    my $onefile;
378
379    foreach $onefile (@sourcefiles)
380    {
381        if ((!($onefile eq ".")) && (!($onefile eq "..")))
382        {
383            if ( $onefile =~ /^\s*(\S.*?)\.$extension\s*$/ )
384            {
385                push(@allfiles, $onefile)
386            }
387        }
388    }
389
390    return \@allfiles;
391}
392
393##############################################################
394# Creating a directory with all parent directories
395##############################################################
396
397sub create_directories
398{
399    my ($directory) = @_;
400
401    if ( ! try_to_create_directory($directory) )
402    {
403        my $parentdir = $directory;
404        get_path_from_fullqualifiedname(\$parentdir);
405        create_directories($parentdir);   # recursive
406    }
407
408    create_directory($directory);   # now it has to succeed
409}
410
411##############################################################
412# Creating one directory
413##############################################################
414
415sub create_directory
416{
417    my ($directory) = @_;
418
419    if ( ! -d $directory ) { mkdir($directory, 0775); }
420}
421
422##############################################################
423# Trying to create a directory, no error if this fails
424##############################################################
425
426sub try_to_create_directory
427{
428    my ($directory) = @_;
429
430    my $returnvalue = 1;
431    my $created_directory = 0;
432
433    if (!(-d $directory))
434    {
435        $returnvalue = mkdir($directory, 0775);
436
437        if ($returnvalue)
438        {
439            $created_directory = 1;
440
441            my $localcall = "chmod 775 $directory \>\/dev\/null 2\>\&1";
442            system($localcall);
443        }
444        else
445        {
446            $created_directory = 0;
447        }
448    }
449    else
450    {
451        $created_directory = 1;
452    }
453
454    return $created_directory;
455}
456
457###########################################
458# Getting path from full file name
459###########################################
460
461sub get_path_from_fullqualifiedname
462{
463    my ($longfilenameref) = @_;
464
465    if ( $$longfilenameref =~ /\Q$separator\E/ )    # Is there a separator in the path? Otherwise the path is empty.
466    {
467        if ( $$longfilenameref =~ /^\s*(\S.*\Q$separator\E)(\S.+\S?)/ )
468        {
469            $$longfilenameref = $1;
470        }
471    }
472    else
473    {
474        $$longfilenameref = ""; # there is no path
475    }
476}
477
478##############################################################
479# Getting file name from full file name
480##############################################################
481
482sub make_absolute_filename_to_relative_filename
483{
484    my ($longfilenameref) = @_;
485
486    # Either '/' or '\'.
487    if ( $$longfilenameref =~ /^.*[\/\\](\S.+\S?)/ )
488    {
489        $$longfilenameref = $1;
490    }
491}
492
493############################################
494# Exiting the program with an error
495# This function is used instead of "die"
496############################################
497
498sub exit_program
499{
500    my ($message) = @_;
501
502    print "\n***************************************************************\n";
503    print "$message\n";
504    print "***************************************************************\n";
505    remove_complete_directory($savetemppath, 1);
506    print "\n" . get_time_string();
507    exit(-1);
508}
509
510#################################################################################
511# Unpacking cabinet files with expand
512#################################################################################
513
514sub unpack_cabinet_file
515{
516    my ($cabfilename, $unpackdir) = @_;
517
518    my $expandfile = "expand.exe"; # has to be in the PATH
519
520    # expand.exe has to be located in the system directory.
521    # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course.
522    # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack
523    # cabinet files.
524
525    if ( $^O =~ /cygwin/i )
526    {
527        $expandfile = $ENV{'SYSTEMROOT'} . "/system32/expand.exe"; # Has to be located in the systemdirectory
528        $expandfile =~ s/\\/\//;
529        if ( ! -f $expandfile ) { exit_program("ERROR: Did not find file $expandfile in the Windows system folder!"); }
530    }
531
532    my $expandlogfile = $unpackdir . $separator . "expand.log";
533
534    # exclude cabinet file
535    # my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};
536
537    my $systemcall = "";
538    if ( $^O =~ /cygwin/i ) {
539        my $localunpackdir = qx{cygpath -w "$unpackdir"};
540        $localunpackdir =~ s/\\/\\\\/g;
541
542        my $localcabfilename = qx{cygpath -w "$cabfilename"};
543        $localcabfilename =~ s/\\/\\\\/g;
544        $localcabfilename =~ s/\s*$//g;
545
546        $systemcall = $expandfile . " " . $localcabfilename . " -F:\* " . $localunpackdir . " \>\/dev\/null 2\>\&1";
547    }
548    else
549    {
550        $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
551    }
552
553    my $returnvalue = system($systemcall);
554
555    if ($returnvalue) { exit_program("ERROR: Could not execute $systemcall !"); }
556}
557
558#################################################################################
559# Extracting tables from msi database
560#################################################################################
561
562sub extract_tables_from_database
563{
564    my ($fullmsidatabasepath, $workdir, $tablelist) = @_;
565
566    my $msidb = "msidb.exe";    # Has to be in the path
567    if ( $localmsidbpath ) { $msidb = $localmsidbpath; }
568    my $infoline = "";
569    my $systemcall = "";
570    my $returnvalue = "";
571
572    if ( $^O =~ /cygwin/i ) {
573        chomp( $fullmsidatabasepath = qx{cygpath -w "$fullmsidatabasepath"} );
574        # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
575        $fullmsidatabasepath =~ s/\\/\\\\/g;
576        $workdir =~ s/\\/\\\\/g;
577        # and if there are still slashes, they also need to be double backslash
578        $fullmsidatabasepath =~ s/\//\\\\/g;
579        $workdir =~ s/\//\\\\/g;
580    }
581
582    # Export of all tables by using "*"
583
584    $systemcall = $msidb . " -d " . $fullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
585    print "\nAnalyzing msi database\n";
586    $returnvalue = system($systemcall);
587
588    if ($returnvalue)
589    {
590        $infoline = "ERROR: Could not execute $systemcall !\n";
591        exit_program($infoline);
592    }
593}
594
595########################################################
596# Check, if this installation set contains
597# internal cabinet files included into the msi
598# database.
599########################################################
600
601sub check_for_internal_cabfiles
602{
603    my ($cabfilehash) = @_;
604
605    my $contains_internal_cabfiles = 0;
606    my %allcabfileshash = ();
607
608    foreach my $filename ( keys %{$cabfilehash} )
609    {
610        if ( $filename =~ /^\s*\#/ )     # starting with a hash
611        {
612            $contains_internal_cabfiles = 1;
613            # setting real filename without hash as key and name with hash as value
614            my $realfilename = $filename;
615            $realfilename =~ s/^\s*\#//;
616            $allcabfileshash{$realfilename} = $filename;
617        }
618    }
619
620    return ( $contains_internal_cabfiles, \%allcabfileshash );
621}
622
623#################################################################
624# Exclude all cab files from the msi database.
625#################################################################
626
627sub extract_cabs_from_database
628{
629    my ($msidatabase, $allcabfiles) = @_;
630
631    my $infoline = "";
632    my $fullsuccess = 1;
633    my $msidb = "msidb.exe";    # Has to be in the path
634    if ( $localmsidbpath ) { $msidb = $localmsidbpath; }
635
636    my @all_excluded_cabfiles = ();
637
638    if( $^O =~ /cygwin/i )
639    {
640        $msidatabase = qx{cygpath -w "$msidatabase"};
641        $msidatabase =~ s/\\/\\\\/g;
642        $msidatabase =~ s/\s*$//g;
643    }
644    else
645    {
646        # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
647        $msidatabase =~ s/\//\\\\/g;
648    }
649
650    foreach my $onefile ( keys %{$allcabfiles} )
651    {
652        my $systemcall = $msidb . " -d " . $msidatabase . " -x " . $onefile;
653        system($systemcall);
654        push(@all_excluded_cabfiles, $onefile);
655    }
656
657    \@all_excluded_cabfiles;
658}
659
660################################################################################
661# Collect all DiskIds to the corresponding cabinet files from Media.idt.
662################################################################################
663
664sub analyze_media_file
665{
666    my ($filecontent) = @_;
667
668    my %diskidhash = ();
669
670    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
671    {
672        if ( $i < 3 ) { next; }
673
674        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
675        {
676            my $diskid = $1;
677            my $cabfile = $4;
678
679            $diskidhash{$cabfile} = $diskid;
680        }
681    }
682
683    return \%diskidhash;
684}
685
686sub analyze_customaction_file
687{
688    my ($filecontent) = @_;
689
690    my $register_extensions_exists = 0;
691
692    my %table = ();
693
694    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
695    {
696        if ( ${$filecontent}[$i] =~ /^\s*RegisterExtensions\s+/ )
697        {
698            $register_extensions_exists = 1;
699            last;
700        }
701    }
702
703    return $register_extensions_exists;
704}
705
706################################################################################
707# Analyzing the content of Directory.idt
708#################################################################################
709
710sub analyze_directory_file
711{
712    my ($filecontent) = @_;
713
714    my %table = ();
715
716    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
717    {
718        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
719
720        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
721        {
722            my $dir = $1;
723            my $parent = $2;
724            my $name = $3;
725
726            if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
727            if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
728
729            my %helphash = ();
730            $helphash{'Directory_Parent'} = $parent;
731            $helphash{'DefaultDir'} = $name;
732            $table{$dir} = \%helphash;
733        }
734    }
735
736    return \%table;
737}
738
739#################################################################################
740# Analyzing the content of Component.idt
741#################################################################################
742
743sub analyze_component_file
744{
745    my ($filecontent) = @_;
746
747    my %table = ();
748
749    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
750    {
751        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
752
753        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
754        {
755            my $component = $1;
756            my $dir = $3;
757
758            $table{$component} = $dir;
759        }
760    }
761
762    return \%table;
763}
764
765#################################################################################
766# Analyzing the content of File.idt
767#################################################################################
768
769sub analyze_file_file
770{
771    my ($filecontent) = @_;
772
773    my %table = ();
774    my %fileorder = ();
775    my $maxsequence = 0;
776
777    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
778    {
779        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
780
781        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
782        {
783            my $file = $1;
784            my $comp = $2;
785            my $filename = $3;
786            my $sequence = $8;
787
788            if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
789
790            my %helphash = ();
791            $helphash{'Component'} = $comp;
792            $helphash{'FileName'} = $filename;
793            $helphash{'Sequence'} = $sequence;
794
795            $table{$file} = \%helphash;
796
797            $fileorder{$sequence} = $file;
798
799            if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
800        }
801    }
802
803    return (\%table, \%fileorder, $maxsequence);
804}
805
806####################################################################################
807# Recursively creating the directory tree
808####################################################################################
809
810sub create_directory_tree
811{
812    my ($parent, $pathcollector, $fulldir, $dirhash) = @_;
813
814    foreach my $dir ( keys %{$dirhash} )
815    {
816        if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
817        {
818            my $dirname = $dirhash->{$dir}->{'DefaultDir'};
819            # Create the directory
820            my $newdir = $fulldir . $separator . $dirname;
821            if ( ! -f $newdir ) { mkdir $newdir; }
822            # Saving in collector
823            $pathcollector->{$dir} = $newdir;
824            # Iteration
825            create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
826        }
827    }
828}
829
830####################################################################################
831# Creating the directory tree
832####################################################################################
833
834sub create_directory_structure
835{
836    my ($dirhash, $targetdir) = @_;
837
838    print "Creating directories\n";
839
840    my %fullpathhash = ();
841
842    my @startparents = ("TARGETDIR", "INSTALLLOCATION");
843
844    foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); }
845
846    # Also adding the pathes of the startparents
847    foreach $dir (@startparents)
848    {
849        if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
850    }
851
852    return \%fullpathhash;
853}
854
855####################################################################################
856# Cygwin: Setting privileges for files
857####################################################################################
858
859sub change_privileges
860{
861    my ($destfile, $privileges) = @_;
862
863    my $localcall = "chmod $privileges " . "\"" . $destfile . "\"";
864    system($localcall);
865}
866
867####################################################################################
868# Cygwin: Setting privileges for files recursively
869####################################################################################
870
871sub change_privileges_full
872{
873    my ($target) = @_;
874
875    print "Changing privileges\n";
876
877    my $localcall = "chmod -R 755 " . "\"" . $target . "\"";
878    system($localcall);
879}
880
881######################################################
882# Creating a new directory with defined privileges
883######################################################
884
885sub create_directory_with_privileges
886{
887    my ($directory, $privileges) = @_;
888
889    my $returnvalue = 1;
890    my $infoline = "";
891
892    if (!(-d $directory))
893    {
894        my $localprivileges = oct("0".$privileges); # changes "777" to 0777
895        $returnvalue = mkdir($directory, $localprivileges);
896
897        if ($returnvalue)
898        {
899            my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
900            system($localcall);
901        }
902    }
903    else
904    {
905        my $localcall = "chmod $privileges $directory \>\/dev\/null 2\>\&1";
906        system($localcall);
907    }
908}
909
910######################################################
911# Creating a unique directory with pid extension
912######################################################
913
914sub create_pid_directory
915{
916    my ($directory) = @_;
917
918    $directory =~ s/\Q$separator\E\s*$//;
919    my $pid = $$;           # process id
920    my $time = time();      # time
921
922    $directory = $directory . "_" . $pid . $time;
923
924    if ( ! -d $directory ) { create_directory($directory); }
925    else { exit_program("ERROR: Directory $directory already exists!"); }
926
927    return $directory;
928}
929
930####################################################################################
931# Copying files into installation set
932####################################################################################
933
934sub copy_files_into_directory_structure
935{
936    my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;
937
938    print "Copying files\n";
939
940    my $unopkgfile = "";
941
942    for ( my $i = 1; $i <= $maxsequence; $i++ )
943    {
944        if ( exists($fileorder->{$i}) )
945        {
946            my $file = $fileorder->{$i};
947            if ( ! exists($filehash->{$file}->{'Component'}) ) { exit_program("ERROR: Did not find component for file: \"$file\"."); }
948            my $component = $filehash->{$file}->{'Component'};
949            if ( ! exists($componenthash->{$component}) ) { exit_program("ERROR: Did not find directory for component: \"$component\"."); }
950            my $dirname = $componenthash->{$component};
951            if ( ! exists($fullpathhash->{$dirname}) ) { exit_program("ERROR: Did not find full directory path for dir: \"$dirname\"."); }
952            my $destdir = $fullpathhash->{$dirname};
953            if ( ! exists($filehash->{$file}->{'FileName'}) ) { exit_program("ERROR: Did not find \"FileName\" for file: \"$file\"."); }
954            my $destfile = $filehash->{$file}->{'FileName'};
955
956            $destfile = $destdir . $separator . $destfile;
957            my $sourcefile = $unpackdir . $separator . $file;
958
959            if ( ! -f $sourcefile )
960            {
961                # It is possible, that this was an unpacked file
962                # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
963                # subdir is not recursively analyzed, only one directory.
964
965                my $oldsourcefile = $sourcefile;
966                my $subdir = "";
967                if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $separator; }
968                my $realfilename = $filehash->{$file}->{'FileName'};
969                my $localinstalldir = $installdir;
970
971                $localinstalldir =~ s/\\\s*$//;
972                $localinstalldir =~ s/\/\s*$//;
973
974                $sourcefile = $localinstalldir . $separator . $subdir . $realfilename;
975
976                if ( ! -f $sourcefile ) { exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\")."); }
977            }
978
979            my $copyreturn = copy($sourcefile, $destfile);
980
981            if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); }
982
983            # Searching unopkg.exe
984            if ( $destfile =~ /unopkg\.exe\s*$/ ) { $unopkgfile = $destfile; }
985            # if (( $^O =~ /cygwin/i ) && ( $destfile =~ /\.exe\s*$/ )) { change_privileges($destfile, "775"); }
986        }
987        # else  # allowing missing sequence numbers ?
988        # {
989        #   exit_program("ERROR: No file assigned to sequence $i");
990        # }
991    }
992
993    return ($unopkgfile);
994}
995
996######################################################
997# Removing a complete directory with subdirectories
998######################################################
999
1000sub remove_complete_directory
1001{
1002    my ($directory, $start) = @_;
1003
1004    my @content = ();
1005    my $infoline = "";
1006
1007    $directory =~ s/\Q$separator\E\s*$//;
1008
1009    if ( -d $directory )
1010    {
1011        if ( $start ) { print "Removing directory $directory\n"; }
1012
1013        opendir(DIR, $directory);
1014        @content = readdir(DIR);
1015        closedir(DIR);
1016
1017        my $oneitem;
1018
1019        foreach $oneitem (@content)
1020        {
1021            if ((!($oneitem eq ".")) && (!($oneitem eq "..")))
1022            {
1023                my $item = $directory . $separator . $oneitem;
1024
1025                if ( -f $item || -l $item )     # deleting files or links
1026                {
1027                    unlink($item);
1028                }
1029
1030                if ( -d $item )     # recursive
1031                {
1032                    remove_complete_directory($item, 0);
1033                }
1034            }
1035        }
1036
1037        # try to remove empty directory
1038        my $returnvalue = rmdir $directory;
1039        if ( ! $returnvalue ) { print "Warning: Problem with removing empty dir $directory\n"; }
1040    }
1041}
1042
1043####################################################################################
1044# Defining a temporary path
1045####################################################################################
1046
1047sub get_temppath
1048{
1049    my $temppath = "";
1050
1051    if (( $ENV{'TMP'} ) || ( $ENV{'TEMP'} ))
1052    {
1053        if ( $ENV{'TMP'} ) { $temppath = $ENV{'TMP'}; }
1054        elsif ( $ENV{'TEMP'} )  { $temppath = $ENV{'TEMP'}; }
1055
1056        $temppath =~ s/\Q$separator\E\s*$//;    # removing ending slashes and backslashes
1057        $temppath = $temppath . $separator . $globaltempdirname;
1058        create_directory_with_privileges($temppath, "777");
1059
1060        my $dirsave = $temppath;
1061
1062        $temppath = $temppath . $separator . "a";
1063        $temppath = create_pid_directory($temppath);
1064
1065        if ( ! -d $temppath ) { exit_program("ERROR: Failed to create directory $temppath ! Possible reason: Wrong privileges in directory $dirsave."); }
1066
1067        if ( $^O =~ /cygwin/i )
1068        {
1069            $temppath =~ s/\\/\\\\/g;
1070            chomp( $temppath = qx{cygpath -w "$temppath"} );
1071        }
1072
1073        $savetemppath = $temppath;
1074    }
1075    else
1076    {
1077        exit_program("ERROR: Could not set temporary directory (TMP and TEMP not set!).");
1078    }
1079
1080    return $temppath;
1081}
1082
1083####################################################################################
1084# Registering extensions
1085####################################################################################
1086
1087sub register_extensions_sync
1088{
1089    my ($unopkgfile, $localtemppath, $preregdir) = @_;
1090
1091    if ( $preregdir eq "" )
1092    {
1093        my $logtext = "ERROR: Failed to determine \"prereg\" folder for extension registration! Please check your installation set.";
1094        print $logtext . "\n";
1095        exit_program($logtext);
1096    }
1097
1098    my $from = cwd();
1099
1100    my $path = $unopkgfile;
1101    get_path_from_fullqualifiedname(\$path);
1102    $path =~ s/\\\s*$//;
1103    $path =~ s/\/\s*$//;
1104
1105    my $executable = $unopkgfile;
1106    make_absolute_filename_to_relative_filename(\$executable);
1107
1108    chdir($path);
1109
1110    if ( ! $path_displayed )
1111    {
1112        print "... current dir: $path ...\n";
1113        $path_displayed = 1;
1114    }
1115
1116    $localtemppath =~ s/\\/\//g;
1117
1118    if ( $^O =~ /cygwin/i ) {
1119        $executable = "./" . $executable;
1120        $preregdir = qx{cygpath -m "$preregdir"};
1121        chomp($preregdir);
1122    }
1123
1124    $preregdir =~ s/\/\s*$//g;
1125
1126    my $systemcall = $executable . " sync --verbose 2\>\&1 |";
1127
1128    print "... $systemcall\n";
1129
1130    my @unopkgoutput = ();
1131
1132    open (UNOPKG, $systemcall);
1133    while (<UNOPKG>) {push(@unopkgoutput, $_); }
1134    close (UNOPKG);
1135
1136    my $returnvalue = $?;   # $? contains the return value of the systemcall
1137
1138    if ($returnvalue)
1139    {
1140        print "ERROR: Could not execute \"$systemcall\"!\nExitcode: '$returnvalue'\n";
1141        for ( my $j = 0; $j <= $#unopkgoutput; $j++ ) { print "$unopkgoutput[$j]"; }
1142        exit_program("ERROR: $systemcall failed!");
1143    }
1144
1145    chdir($from);
1146}
1147
1148####################################################################################
1149# Registering all extensions located in /share/extension/install
1150####################################################################################
1151
1152sub register_extensions
1153{
1154    my ($unopkgfile, $temppath, $preregdir) = @_;
1155
1156    print "Registering extensions:\n";
1157
1158    if (( ! -f $unopkgfile ) || ( $unopkgfile eq "" ))
1159    {
1160        print("WARNING: Could not find unopkg.exe (Language Pack?)!\n");
1161    }
1162    else
1163    {
1164        register_extensions_sync($unopkgfile, $temppath, $preregdir);
1165        remove_complete_directory($temppath, 1);
1166    }
1167
1168}
1169
1170####################################################################################
1171# Reading one file
1172####################################################################################
1173
1174sub read_file
1175{
1176    my ($localfile) = @_;
1177
1178    my @localfile = ();
1179
1180    open( IN, "<$localfile" ) || exit_program("ERROR: Cannot open file $localfile for reading");
1181
1182    #   Don't use "my @localfile = <IN>" here, because
1183    #   perl has a problem with the internal "large_and_huge_malloc" function
1184    #   when calling perl using MacOS 10.5 with a perl built with MacOS 10.4
1185    while ( $line = <IN> ) {
1186        push @localfile, $line;
1187    }
1188
1189    close( IN );
1190
1191    return \@localfile;
1192}
1193
1194###############################################################
1195# Setting the time string for the
1196# Summary Information stream in the
1197# msi database of the admin installations.
1198###############################################################
1199
1200sub get_sis_time_string
1201{
1202    # Syntax: <yyyy/mm/dd hh:mm:ss>
1203    my $second = (localtime())[0];
1204    my $minute = (localtime())[1];
1205    my $hour = (localtime())[2];
1206    my $day = (localtime())[3];
1207    my $month = (localtime())[4];
1208    my $year = 1900 + (localtime())[5];
1209    $month++;
1210
1211    if ( $second < 10 ) { $second = "0" . $second; }
1212    if ( $minute < 10 ) { $minute = "0" . $minute; }
1213    if ( $hour < 10 ) { $hour = "0" . $hour; }
1214    if ( $day < 10 ) { $day = "0" . $day; }
1215    if ( $month < 10 ) { $month = "0" . $month; }
1216
1217    my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
1218
1219    return $timestring;
1220}
1221
1222###############################################################
1223# Writing content of administrative installations into
1224# Summary Information Stream of msi database.
1225# This is required for example for following
1226# patch processes using Windows Installer service.
1227###############################################################
1228
1229sub write_sis_info
1230{
1231    my ($msidatabase) = @_;
1232
1233    print "Setting SIS in msi database\n";
1234
1235    if ( ! -f $msidatabase ) { exit_program("ERROR: Cannot find file $msidatabase"); }
1236
1237    my $msiinfo = "msiinfo.exe";    # Has to be in the path
1238    my $infoline = "";
1239    my $systemcall = "";
1240    my $returnvalue = "";
1241
1242    # Required setting for administrative installations:
1243    # -w 4   (source files are unpacked),  wordcount
1244    # -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
1245    # -l <person_making_admin_installation>, LastSavedBy
1246
1247    my $wordcount = 4;  # Unpacked files
1248    my $lastprinted = get_sis_time_string();
1249    my $lastsavedby = "Installer";
1250
1251    my $localmsidatabase = $msidatabase;
1252
1253    if( $^O =~ /cygwin/i )
1254    {
1255        $localmsidatabase = qx{cygpath -w "$localmsidatabase"};
1256        $localmsidatabase =~ s/\\/\\\\/g;
1257        $localmsidatabase =~ s/\s*$//g;
1258    }
1259
1260    $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
1261
1262    $returnvalue = system($systemcall);
1263
1264    if ($returnvalue)
1265    {
1266        $infoline = "ERROR: Could not execute $systemcall !\n";
1267        exit_program($infoline);
1268    }
1269}
1270
1271###############################################################
1272# Convert time string
1273###############################################################
1274
1275sub convert_timestring
1276{
1277    my ($secondstring) = @_;
1278
1279    my $timestring = "";
1280
1281    if ( $secondstring < 60 )    # less than a minute
1282    {
1283        if ( $secondstring < 10 ) { $secondstring = "0" . $secondstring; }
1284        $timestring = "00\:$secondstring min\.";
1285    }
1286    elsif ( $secondstring < 3600 )
1287    {
1288        my $minutes = $secondstring / 60;
1289        my $seconds = $secondstring % 60;
1290        if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
1291        if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
1292        if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
1293        $timestring = "$minutes\:$seconds min\.";
1294    }
1295    else    # more than one hour
1296    {
1297        my $hours = $secondstring / 3600;
1298        my $secondstring = $secondstring % 3600;
1299        my $minutes = $secondstring / 60;
1300        my $seconds = $secondstring % 60;
1301        if ( $hours =~ /(\d*)\.\d*/ ) { $hours = $1; }
1302        if ( $minutes =~ /(\d*)\.\d*/ ) { $minutes = $1; }
1303        if ( $hours < 10 ) { $hours = "0" . $hours; }
1304        if ( $minutes < 10 ) { $minutes = "0" . $minutes; }
1305        if ( $seconds < 10 ) { $seconds = "0" . $seconds; }
1306        $timestring = "$hours\:$minutes\:$seconds hours";
1307    }
1308
1309    return $timestring;
1310}
1311
1312###############################################################
1313# Returning time string for logging
1314###############################################################
1315
1316sub get_time_string
1317{
1318    my $currenttime = time();
1319    $currenttime = $currenttime - $starttime;
1320    $currenttime = convert_timestring($currenttime);
1321    $currenttime = localtime() . " \(" . $currenttime . "\)\n";
1322    return $currenttime;
1323}
1324
1325####################################################################################
1326# Simulating an administrative installation
1327####################################################################################
1328
1329$starttime = time();
1330
1331getparameter();
1332controlparameter();
1333check_local_msidb();
1334check_system_path();
1335my $temppath = get_temppath();
1336
1337print("\nmsi database: $databasepath\n");
1338print("Destination directory: $targetdir\n" );
1339
1340my $helperdir = $temppath . $separator . "installhelper";
1341create_directory($helperdir);
1342
1343# Get File.idt, Component.idt and Directory.idt from database
1344
1345my $tablelist = "File Directory Component Media CustomAction";
1346extract_tables_from_database($databasepath, $helperdir, $tablelist);
1347
1348# Set unpackdir
1349my $unpackdir = $helperdir . $separator . "unpack";
1350create_directory($unpackdir);
1351
1352# Reading media table to check for internal cabinet files
1353my $filename = $helperdir . $separator . "Media.idt";
1354if ( ! -f $filename ) { exit_program("ERROR: Could not find required file: $filename !"); }
1355my $filecontent = read_file($filename);
1356my $cabfilehash = analyze_media_file($filecontent);
1357
1358# Check, if there are internal cab files
1359my ( $contains_internal_cabfiles, $all_internal_cab_files) = check_for_internal_cabfiles($cabfilehash);
1360
1361if ( $contains_internal_cabfiles )
1362{
1363    # Set unpackdir
1364    my $cabdir = $helperdir . $separator . "internal_cabs";
1365    create_directory($cabdir);
1366    my $from = cwd();
1367    chdir($cabdir);
1368    # Exclude all cabinet files from database
1369    my $all_excluded_cabs = extract_cabs_from_database($databasepath, $all_internal_cab_files);
1370    print "Unpacking files from internal cabinet file(s)\n";
1371    foreach my $cabfile ( @{$all_excluded_cabs} ) { unpack_cabinet_file($cabfile, $unpackdir); }
1372    chdir($from);
1373}
1374
1375# Unpack all cab files into $helperdir, cab files must be located next to msi database
1376my $installdir = $databasepath;
1377
1378get_path_from_fullqualifiedname(\$installdir);
1379
1380my $databasefilename = $databasepath;
1381make_absolute_filename_to_relative_filename(\$databasefilename);
1382
1383my $cabfiles = find_file_with_file_extension("cab", $installdir);
1384
1385if (( $#{$cabfiles} < 0 ) && ( ! $contains_internal_cabfiles )) { exit_program("ERROR: Did not find any cab file in directory $installdir"); }
1386
1387print "Unpacking files from cabinet file(s)\n";
1388for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
1389{
1390    my $cabfile = $installdir . $separator . ${$cabfiles}[$i];
1391    unpack_cabinet_file($cabfile, $unpackdir);
1392}
1393
1394# Reading tables
1395$filename = $helperdir . $separator . "Directory.idt";
1396$filecontent = read_file($filename);
1397my $dirhash = analyze_directory_file($filecontent);
1398
1399$filename = $helperdir . $separator . "Component.idt";
1400$filecontent = read_file($filename);
1401my $componenthash = analyze_component_file($filecontent);
1402
1403$filename = $helperdir . $separator . "File.idt";
1404$filecontent = read_file($filename);
1405my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);
1406
1407# Creating the directory structure
1408my $fullpathhash = create_directory_structure($dirhash, $targetdir);
1409
1410# Copying files
1411my ($unopkgfile) = copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
1412if ( $^O =~ /cygwin/i ) { change_privileges_full($targetdir); }
1413
1414my $msidatabase = $targetdir . $separator . $databasefilename;
1415my $copyreturn = copy($databasepath, $msidatabase);
1416if ( ! $copyreturn) { exit_program("ERROR: Could not copy $source to $dest\n"); }
1417
1418# Reading tables
1419$filename = $helperdir . $separator . "CustomAction.idt";
1420$filecontent = read_file($filename);
1421my $register_extensions_exists = analyze_customaction_file($filecontent);
1422
1423# Removing empty dirs in extension folder
1424my ( $extensionfolder, $preregdir ) = get_extensions_dir($unopkgfile);
1425if ( -d $extensionfolder ) { remove_empty_dirs_in_folder($extensionfolder, 1); }
1426
1427if ( $register_extensions_exists )
1428{
1429    # Registering extensions
1430    register_extensions($unopkgfile, $temppath, $preregdir);
1431}
1432
1433# Saving info in Summary Information Stream of msi database (required for following patches)
1434if ( $msiinfo_available ) { write_sis_info($msidatabase); }
1435
1436# Removing the helper directory
1437remove_complete_directory($temppath, 1);
1438
1439print "\nSuccessful installation: " . get_time_string();
1440