xref: /AOO41X/main/solenv/bin/modules/installer/windows/admin.pm (revision 5979ef3c542ac870a02043decc543300c0ec3dfb)
1#*************************************************************************
2#
3# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4#
5# Copyright 2000, 2010 Oracle and/or its affiliates.
6#
7# OpenOffice.org - a multi-platform office productivity suite
8#
9# This file is part of OpenOffice.org.
10#
11# OpenOffice.org is free software: you can redistribute it and/or modify
12# it under the terms of the GNU Lesser General Public License version 3
13# only, as published by the Free Software Foundation.
14#
15# OpenOffice.org is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU Lesser General Public License version 3 for more details
19# (a copy is included in the LICENSE file that accompanied this code).
20#
21# You should have received a copy of the GNU Lesser General Public License
22# version 3 along with OpenOffice.org.  If not, see
23# <http://www.openoffice.org/license.html>
24# for a copy of the LGPLv3 License.
25#
26#*************************************************************************
27
28package installer::windows::admin;
29
30use File::Copy;
31use installer::exiter;
32use installer::files;
33use installer::globals;
34use installer::pathanalyzer;
35use installer::systemactions;
36use installer::worker;
37use installer::windows::idtglobal;
38
39#################################################################################
40# Unpacking cabinet files with expand
41#################################################################################
42
43sub unpack_cabinet_file
44{
45    my ($cabfilename, $unpackdir) = @_;
46
47    my $infoline = "Unpacking cabinet file: $cabfilename\n";
48    push( @installer::globals::logfileinfo, $infoline);
49
50    my $expandfile = "expand.exe";  # Has to be in the path
51
52    # expand.exe has to be located in the system directory.
53    # Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course.
54    # But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack
55    # cabinet files.
56
57#   if ( $^O =~ /cygwin/i )
58#   {
59#       $expandfile = $ENV{'SYSTEMROOT'} . "/system32/expand.exe"; # Has to be located in the systemdirectory
60#       $expandfile =~ s/\\/\//;
61#       if ( ! -f $expandfile ) { exit_program("ERROR: Did not find file $expandfile in the Windows system folder!"); }
62#   }
63
64    if ( $^O =~ /cygwin/i )
65    {
66        $expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
67        chomp $expandfile;
68    }
69
70    my $expandlogfile = $unpackdir . $installer::globals::separator . "expand.log";
71
72    # exclude cabinet file
73    # my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};
74
75    my $systemcall = "";
76    if ( $^O =~ /cygwin/i ) {
77        my $localunpackdir = qx{cygpath -w "$unpackdir"};
78        chomp ($localunpackdir);
79        $localunpackdir =~ s/\\/\\\\/g;
80        $cabfilename =~ s/\\/\\\\/g;
81        $cabfilename =~ s/\s*$//g;
82        $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $localunpackdir . " \> " . $expandlogfile;
83    }
84    else
85    {
86        $systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
87    }
88
89    my $returnvalue = system($systemcall);
90    $infoline = "Systemcall: $systemcall\n";
91    push( @installer::globals::logfileinfo, $infoline);
92
93    if ($returnvalue)
94    {
95        $infoline = "ERROR: Could not execute $systemcall !\n";
96        push( @installer::globals::logfileinfo, $infoline);
97        installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
98    }
99    else
100    {
101        $infoline = "Success: Executed $systemcall successfully!\n";
102        push( @installer::globals::logfileinfo, $infoline);
103    }
104}
105
106#################################################################################
107# Include tables into a msi database
108#################################################################################
109
110sub include_tables_into_pcpfile
111{
112    my ($fullmsidatabasepath, $workdir, $tables) = @_;
113
114    my $msidb = "msidb.exe";    # Has to be in the path
115    my $infoline = "";
116    my $systemcall = "";
117    my $returnvalue = "";
118
119    # Make all table 8+3 conform
120    my $alltables = installer::converter::convert_stringlist_into_array(\$tables, " ");
121
122    for ( my $i = 0; $i <= $#{$alltables}; $i++ )
123    {
124        my $tablename = ${$alltables}[$i];
125        $tablename =~ s/\s*$//;
126        my $namelength = length($tablename);
127        if ( $namelength > 8 )
128        {
129            my $newtablename = substr($tablename, 0, 8);    # name, offset, length
130            my $oldfile = $workdir . $installer::globals::separator . $tablename . ".idt";
131            my $newfile = $workdir . $installer::globals::separator . $newtablename . ".idt";
132            if ( -f $newfile ) { unlink $newfile; }
133            installer::systemactions::copy_one_file($oldfile, $newfile);
134            my $savfile = $oldfile . ".orig";
135            installer::systemactions::copy_one_file($oldfile, $savfile);
136        }
137    }
138
139    # Import of tables
140
141    $systemcall = $msidb . " -d " . $fullmsidatabasepath . " -f " . $workdir . " -i " . $tables;
142
143    $returnvalue = system($systemcall);
144
145    $infoline = "Systemcall: $systemcall\n";
146    push( @installer::globals::logfileinfo, $infoline);
147
148    if ($returnvalue)
149    {
150        $infoline = "ERROR: Could not execute $systemcall !\n";
151        push( @installer::globals::logfileinfo, $infoline);
152        installer::exiter::exit_program("ERROR: Could not include tables into msi database: $fullmsidatabasepath !", "include_tables_into_pcpfile");
153    }
154    else
155    {
156        $infoline = "Success: Executed $systemcall successfully!\n";
157        push( @installer::globals::logfileinfo, $infoline);
158    }
159}
160
161#################################################################################
162# Extracting tables from msi database
163#################################################################################
164
165sub extract_tables_from_pcpfile
166{
167    my ($fullmsidatabasepath, $workdir, $tablelist) = @_;
168
169    my $msidb = "msidb.exe";    # Has to be in the path
170    my $infoline = "";
171    my $systemcall = "";
172    my $returnvalue = "";
173
174    my $localfullmsidatabasepath = $fullmsidatabasepath;
175
176    # Export of all tables by using "*"
177
178    if ( $^O =~ /cygwin/i ) {
179        # Copying the msi database locally guarantees the format of the directory.
180        # Otherwise it is defined in the file of UPDATE_DATABASE_LISTNAME
181
182        my $msifilename = $localfullmsidatabasepath;
183        installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename);
184        my $destdatabasename = $workdir . $installer::globals::separator . $msifilename;
185        installer::systemactions::copy_one_file($localfullmsidatabasepath, $destdatabasename);
186        $localfullmsidatabasepath = $destdatabasename;
187
188        chomp( $localfullmsidatabasepath = qx{cygpath -w "$localfullmsidatabasepath"} );
189        chomp( $workdir = qx{cygpath -w "$workdir"} );
190
191        # msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
192        $localfullmsidatabasepath =~ s/\\/\\\\/g;
193        $workdir =~ s/\\/\\\\/g;
194
195        # and if there are still slashes, they also need to be double backslash
196        $localfullmsidatabasepath =~ s/\//\\\\/g;
197        $workdir =~ s/\//\\\\/g;
198    }
199
200    $systemcall = $msidb . " -d " . $localfullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
201    $returnvalue = system($systemcall);
202
203    $infoline = "Systemcall: $systemcall\n";
204    push( @installer::globals::logfileinfo, $infoline);
205
206    if ($returnvalue)
207    {
208        $infoline = "ERROR: Could not execute $systemcall !\n";
209        push( @installer::globals::logfileinfo, $infoline);
210        installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $localfullmsidatabasepath !", "extract_tables_from_pcpfile");
211    }
212    else
213    {
214        $infoline = "Success: Executed $systemcall successfully!\n";
215        push( @installer::globals::logfileinfo, $infoline);
216    }
217}
218
219################################################################################
220# Analyzing the content of Directory.idt
221#################################################################################
222
223sub analyze_directory_file
224{
225    my ($filecontent) = @_;
226
227    my %table = ();
228
229    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
230    {
231        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
232
233        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
234        {
235            my $dir = $1;
236            my $parent = $2;
237            my $name = $3;
238
239            if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
240            if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
241
242            my %helphash = ();
243            $helphash{'Directory_Parent'} = $parent;
244            $helphash{'DefaultDir'} = $name;
245            $table{$dir} = \%helphash;
246        }
247    }
248
249    return \%table;
250}
251
252#################################################################################
253# Analyzing the content of Component.idt
254#################################################################################
255
256sub analyze_component_file
257{
258    my ($filecontent) = @_;
259
260    my %table = ();
261
262    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
263    {
264        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
265
266        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
267        {
268            my $component = $1;
269            my $dir = $3;
270
271            $table{$component} = $dir;
272        }
273    }
274
275    return \%table;
276}
277
278#################################################################################
279# Analyzing the full content of Component.idt
280#################################################################################
281
282sub analyze_keypath_component_file
283{
284    my ($filecontent) = @_;
285
286    my %keypathtable = ();
287
288    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
289    {
290        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
291
292        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
293        {
294            my $component = $1;
295            my $keypath = $6;
296
297            $keypathtable{$keypath} = $component;
298        }
299    }
300
301    return (\%keypathtable);
302
303}
304
305#################################################################################
306# Analyzing the content of Registry.idt
307#################################################################################
308
309sub analyze_registry_file
310{
311    my ($filecontent) = @_;
312
313    my %table = ();
314
315    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
316    {
317        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
318
319        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
320        {
321            my $registry = $1;
322            my $root = $2;
323            my $key = $3;
324            my $name = $4;
325            my $value = $5;
326            my $component = $6;
327
328            my %helphash = ();
329            # $helphash{'Registry'} = $registry;
330            $helphash{'Root'} = $root;
331            $helphash{'Key'} = $key;
332            $helphash{'Name'} = $name;
333            $helphash{'Value'} = $value;
334            $helphash{'Component'} = $component;
335
336            $table{$registry} = \%helphash;
337        }
338    }
339
340    return \%table;
341}
342
343#################################################################################
344# Analyzing the content of File.idt
345#################################################################################
346
347sub analyze_file_file
348{
349    my ($filecontent) = @_;
350
351    my %table = ();
352    my %fileorder = ();
353    my $maxsequence = 0;
354
355    for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
356    {
357        if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }
358
359        if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
360        {
361            my $file = $1;
362            my $comp = $2;
363            my $filename = $3;
364            my $sequence = $8;
365
366            if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
367
368            my %helphash = ();
369            $helphash{'Component'} = $comp;
370            $helphash{'FileName'} = $filename;
371            $helphash{'Sequence'} = $sequence;
372
373            $table{$file} = \%helphash;
374
375            $fileorder{$sequence} = $file;
376
377            if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
378        }
379    }
380
381    return (\%table, \%fileorder, $maxsequence);
382}
383
384####################################################################################
385# Recursively creating the directory tree
386####################################################################################
387
388sub create_directory_tree
389{
390    my ($parent, $pathcollector, $fulldir, $dirhash) = @_;
391
392    foreach my $dir ( keys %{$dirhash} )
393    {
394        if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
395        {
396            my $dirname = $dirhash->{$dir}->{'DefaultDir'};
397            # Create the directory
398            my $newdir = $fulldir . $installer::globals::separator . $dirname;
399            if ( ! -f $newdir ) { mkdir $newdir; }
400            # Saving in collector
401            $pathcollector->{$dir} = $newdir;
402            # Iteration
403            create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
404        }
405    }
406}
407
408####################################################################################
409# Creating the directory tree
410####################################################################################
411
412sub create_directory_structure
413{
414    my ($dirhash, $targetdir) = @_;
415
416    my %fullpathhash = ();
417
418    my @startparents = ("TARGETDIR", "INSTALLLOCATION");
419
420    foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); }
421
422    # Also adding the pathes of the startparents
423    foreach $dir (@startparents)
424    {
425        if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
426    }
427
428    return \%fullpathhash;
429}
430
431####################################################################################
432# Copying files into installation set
433####################################################################################
434
435sub copy_files_into_directory_structure
436{
437    my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;
438
439    my $unopkgfile = "";
440
441    for ( my $i = 1; $i <= $maxsequence; $i++ )
442    {
443        if ( exists($fileorder->{$i}) )
444        {
445            my $file = $fileorder->{$i};
446            if ( ! exists($filehash->{$file}->{'Component'}) ) { installer::exiter::exit_program("ERROR: Did not find component for file: \"$file\".", "copy_files_into_directory_structure"); }
447            my $component = $filehash->{$file}->{'Component'};
448            if ( ! exists($componenthash->{$component}) ) { installer::exiter::exit_program("ERROR: Did not find directory for component: \"$component\".", "copy_files_into_directory_structure"); }
449            my $dirname = $componenthash->{$component};
450            if ( ! exists($fullpathhash->{$dirname}) ) { installer::exiter::exit_program("ERROR: Did not find full directory path for dir: \"$dirname\".", "copy_files_into_directory_structure"); }
451            my $destdir = $fullpathhash->{$dirname};
452            if ( ! exists($filehash->{$file}->{'FileName'}) ) { installer::exiter::exit_program("ERROR: Did not find \"FileName\" for file: \"$file\".", "copy_files_into_directory_structure"); }
453            my $destfile = $filehash->{$file}->{'FileName'};
454
455            $destfile = $destdir . $installer::globals::separator . $destfile;
456            my $sourcefile = $unpackdir . $installer::globals::separator . $file;
457
458            if ( ! -f $sourcefile )
459            {
460                # It is possible, that this was an unpacked file
461                # Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
462                # subdir is not recursively analyzed, only one directory.
463
464                my $oldsourcefile = $sourcefile;
465                my $subdir = "";
466                if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $installer::globals::separator; }
467                my $realfilename = $filehash->{$file}->{'FileName'};
468                my $localinstalldir = $installdir;
469
470                $localinstalldir =~ s/\\\s*$//;
471                $localinstalldir =~ s/\/\s*$//;
472
473                $sourcefile = $localinstalldir . $installer::globals::separator . $subdir . $realfilename;
474
475                if ( ! -f $sourcefile )
476                {
477                    installer::exiter::exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\").", "copy_files_into_directory_structure");
478                }
479            }
480
481            my $copyreturn = copy($sourcefile, $destfile);
482
483            if ( ! $copyreturn) # only logging problems
484            {
485                my $infoline = "ERROR: Could not copy $sourcefile to $destfile (insufficient disc space for $destfile ?)\n";
486                $returnvalue = 0;
487                push(@installer::globals::logfileinfo, $infoline);
488                installer::exiter::exit_program($infoline, "copy_files_into_directory_structure");
489            }
490
491            if ( $destfile =~ /unopkg\.exe\s*$/ ) { $unopkgfile = $destfile; }
492
493            # installer::systemactions::copy_one_file($sourcefile, $destfile);
494        }
495        # else  # allowing missing sequence numbers ?
496        # {
497        #   installer::exiter::exit_program("ERROR: No file assigned to sequence $i", "copy_files_into_directory_structure");
498        # }
499    }
500
501    return $unopkgfile;
502}
503
504
505###############################################################
506# Setting the time string for the
507# Summary Information stream in the
508# msi database of the admin installations.
509###############################################################
510
511sub get_sis_time_string
512{
513    # Syntax: <yyyy/mm/dd hh:mm:ss>
514    my $second = (localtime())[0];
515    my $minute = (localtime())[1];
516    my $hour = (localtime())[2];
517    my $day = (localtime())[3];
518    my $month = (localtime())[4];
519    my $year = 1900 + (localtime())[5];
520
521    $month++; # zero based month
522
523    if ( $second < 10 ) { $second = "0" . $second; }
524    if ( $minute < 10 ) { $minute = "0" . $minute; }
525    if ( $hour < 10 ) { $hour = "0" . $hour; }
526    if ( $day < 10 ) { $day = "0" . $day; }
527    if ( $month < 10 ) { $month = "0" . $month; }
528
529    my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
530
531    return $timestring;
532}
533
534###############################################################
535# Windows registry entries containing properties are not set
536# correctly during msp patch process. The properties are
537# empty or do get their default values. This destroys the
538# values of many entries in Windows registry.
539# This can be fixed by removing all entries in Registry table,
540# containing a property before starting msimsp.exe.
541###############################################################
542
543sub remove_properties_from_registry_table
544{
545    my ($registryhash, $componentkeypathhash, $registryfilecontent) = @_;
546
547    installer::logger::include_timestamp_into_logfile("\nPerformance Info: Start remove_properties_from_registry_table");
548
549    my @registrytable = ();
550
551    # Registry hash
552    # Collecting all RegistryItems with values containing a property: [...]
553    # To which component do they belong
554    # Is this after removal an empty component? Create a replacement, so that
555    # no Component has to be removed.
556    # Is this RegistryItem a KeyPath of a component. Then it cannot be removed.
557
558    my %problemitems = ();
559    my %problemcomponents = ();
560    my %securecomponents = ();
561    my $changevalue = "";
562    my $changeroot = "";
563    my $infoline = "";
564
565    my $newitemcounter = 0;
566    my $olditemcounter = 0;
567
568    foreach my $regitem ( keys %{$registryhash} )
569    {
570        my $value = "";
571        if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
572
573        if ( $value =~ /^.*(\[.*?\]).*$/ )
574        {
575            my $property = $1;
576
577            # Collecting registry item
578            $problemitems{$regitem} = 1;    # "1" -> can be removed
579            if ( exists($componentkeypathhash->{$regitem}) ) { $problemitems{$regitem} = 2; }   # "2" -> cannot be removed, KeyPath
580
581            # Collecting component (and number of problematic registry items
582            # my $component = $registryhash->{$regitem}->{'Component'};
583            # if ( exists($problemcomponents{$regitem}) ) { $problemcomponents{$regitem} = $problemcomponents{$regitem} + 1; }
584            # else { $problemcomponents{$regitem} = 1; }
585        }
586        else
587        {
588            # Collecting all components with secure regisry items
589            my $component = "";
590            if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
591            if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item \"$regitem\".", "remove_properties_from_registry_table"); }
592            $securecomponents{$component} = 1;
593        }
594
595        # Searching for change value
596        my $localkey = "";
597        if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
598        if (( $localkey =~ /^\s*(Software\\.*\\)StartMenu\s*$/ ) && ( $changevalue eq "" ))
599        {
600            $changevalue = $1;
601            $changeroot = $registryhash->{$regitem}->{'Root'};
602        }
603
604        $olditemcounter++;
605    }
606
607    my $removecounter = 0;
608    my $renamecounter = 0;
609
610    foreach my $regitem ( keys %{$registryhash} )
611    {
612        my $value = "";
613        if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
614
615        if ( $value =~ /^.*(\[.*?\]).*$/ )
616        {
617            # Removing registry items, that are no KeyPath and that belong to components,
618            # that have other secure registry items.
619
620            my $component = "";
621            if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
622            if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item (2) \"$regitem\".", "remove_properties_from_registry_table"); }
623
624            if (( $problemitems{$regitem} == 1 ) && ( exists($securecomponents{$component}) ))
625            {
626                # remove complete registry item
627                delete($registryhash->{$regitem});
628                $removecounter++;
629                $infoline = "Removing registry item: $regitem : $value\n";
630                push( @installer::globals::logfileinfo, $infoline);
631            }
632            else
633            {
634                # Changing values of registry items, that are KeyPath or that contain to
635                # components with only unsecure registry items.
636
637                if (( $problemitems{$regitem} == 2 ) || ( ! exists($securecomponents{$component}) ))
638                {
639                    # change value of registry item
640                    if ( $changevalue eq "" ) { installer::exiter::exit_program("ERROR: Did not find good change value for registry items", "remove_properties_from_registry_table"); }
641
642                    my $oldkey = "";
643                    if ( exists($registryhash->{$regitem}->{'Key'}) ) { $oldkey = $registryhash->{$regitem}->{'Key'}; };
644                    my $oldname = "";
645                    if ( exists($registryhash->{$regitem}->{'Name'}) ) { $oldname = $registryhash->{$regitem}->{'Name'}; }
646                    my $oldvalue = "";
647                    if ( exists($registryhash->{$regitem}->{'Value'}) ) { $oldvalue = $registryhash->{$regitem}->{'Value'}; }
648
649                    $registryhash->{$regitem}->{'Key'} = $changevalue . "RegistryItem";
650                    $registryhash->{$regitem}->{'Root'} = $changeroot;
651                    $registryhash->{$regitem}->{'Name'} = $regitem;
652                    $registryhash->{$regitem}->{'Value'} = 1;
653                    $renamecounter++;
654
655                    $infoline = "Changing registry item: $regitem\n";
656                    $infoline = "Old: $oldkey : $oldname : $oldvalue\n";
657                    $infoline = "New: $registryhash->{$regitem}->{'Key'} : $registryhash->{$regitem}->{'Name'} : $registryhash->{$regitem}->{'Value'}\n";
658                    push( @installer::globals::logfileinfo, $infoline);
659                }
660            }
661        }
662    }
663
664    $infoline = "Number of removed registry items: $removecounter\n";
665    push( @installer::globals::logfileinfo, $infoline);
666    $infoline = "Number of changed registry items: $renamecounter\n";
667    push( @installer::globals::logfileinfo, $infoline);
668
669    # Creating the new content of Registry table
670    # First three lines from $registryfilecontent
671    # All further files from changed $registryhash
672
673    for ( my $i = 0; $i <= 2; $i++ ) { push(@registrytable, ${$registryfilecontent}[$i]); }
674
675    foreach my $regitem ( keys %{$registryhash} )
676    {
677        my $root = "";
678        if ( exists($registryhash->{$regitem}->{'Root'}) ) { $root = $registryhash->{$regitem}->{'Root'}; }
679        else { installer::exiter::exit_program("ERROR: Did not find root in registry table for item: \"$regitem\".", "remove_properties_from_registry_table"); }
680        my $localkey = "";
681        if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
682        my $name = "";
683        if ( exists($registryhash->{$regitem}->{'Name'}) ) { $name = $registryhash->{$regitem}->{'Name'}; }
684        my $value = "";
685        if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
686        my $comp = "";
687        if ( exists($registryhash->{$regitem}->{'Component'}) ) { $comp = $registryhash->{$regitem}->{'Component'}; }
688
689        my $oneline = $regitem . "\t" . $root . "\t" . $localkey . "\t" . $name . "\t" . $value . "\t" . $comp . "\n";
690        push(@registrytable, $oneline);
691
692        $newitemcounter++;
693    }
694
695    $infoline = "Number of registry items: $newitemcounter. Old value: $olditemcounter.\n";
696    push( @installer::globals::logfileinfo, $infoline);
697
698    installer::logger::include_timestamp_into_logfile("\nPerformance Info: End remove_properties_from_registry_table");
699
700    return (\@registrytable);
701}
702
703###############################################################
704# Writing content of administrative installations into
705# Summary Information Stream of msi database.
706# This is required for example for following
707# patch processes using Windows Installer service.
708###############################################################
709
710sub write_sis_info
711{
712    my ($msidatabase) = @_ ;
713
714    if ( ! -f $msidatabase ) { installer::exiter::exit_program("ERROR: Cannot find file $msidatabase", "write_sis_info"); }
715
716    my $msiinfo = "msiinfo.exe";    # Has to be in the path
717    my $infoline = "";
718    my $systemcall = "";
719    my $returnvalue = "";
720
721    # Required setting for administrative installations:
722    # -w 4   (source files are unpacked),  wordcount
723    # -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
724    # -l <person_making_admin_installation>, LastSavedBy
725
726    my $wordcount = 4;  # Unpacked files
727    my $lastprinted = get_sis_time_string();
728    my $lastsavedby = "Installer";
729
730    my $localmsidatabase = $msidatabase;
731
732    if( $^O =~ /cygwin/i )
733    {
734        $localmsidatabase = qx{cygpath -w "$localmsidatabase"};
735        $localmsidatabase =~ s/\\/\\\\/g;
736        $localmsidatabase =~ s/\s*$//g;
737    }
738
739    $systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
740    push(@installer::globals::logfileinfo, $systemcall);
741    $returnvalue = system($systemcall);
742
743    if ($returnvalue)
744    {
745        $infoline = "ERROR: Could not execute $systemcall !\n";
746        push(@installer::globals::logfileinfo, $infoline);
747        installer::exiter::exit_program($infoline, "write_sis_info");
748    }
749}
750
751####################################################
752# Detecting the directory with extensions
753####################################################
754
755sub get_extensions_dir
756{
757    my ( $unopkgfile ) = @_;
758
759    my $localbranddir = $unopkgfile;
760    installer::pathanalyzer::get_path_from_fullqualifiedname(\$localbranddir); # "program" dir in brand layer
761    installer::pathanalyzer::get_path_from_fullqualifiedname(\$localbranddir); # root dir in brand layer
762    $localbranddir =~ s/\Q$installer::globals::separator\E\s*$//;
763    my $extensiondir = $localbranddir . $installer::globals::separator . "share" . $installer::globals::separator . "extensions";
764
765    return $extensiondir;
766}
767
768##############################################################
769# Removing all empty directories below a specified directory
770##############################################################
771
772sub remove_empty_dirs_in_folder
773{
774    my ( $dir, $firstrun ) = @_;
775
776    if ( $firstrun )
777    {
778        print "Removing superfluous directories\n";
779    }
780
781    my @content = ();
782
783    $dir =~ s/\Q$installer::globals::separator\E\s*$//;
784
785    if ( -d $dir )
786    {
787        opendir(DIR, $dir);
788        @content = readdir(DIR);
789        closedir(DIR);
790
791        my $oneitem;
792
793        foreach $oneitem (@content)
794        {
795            if ((!($oneitem eq ".")) && (!($oneitem eq "..")))
796            {
797                my $item = $dir . $installer::globals::separator . $oneitem;
798
799                if ( -d $item ) # recursive
800                {
801                    remove_empty_dirs_in_folder($item, 0);
802                }
803            }
804        }
805
806        # try to remove empty directory
807        my $returnvalue = rmdir $dir;
808
809        # if ( $returnvalue ) { print "Successfully removed empty dir $dir\n"; }
810    }
811}
812
813####################################################################################
814# Simulating an administrative installation
815####################################################################################
816
817sub make_admin_install
818{
819    my ($databasepath, $targetdir) = @_;
820
821    # Create helper directory
822
823    installer::logger::print_message( "... installing $databasepath in directory $targetdir ...\n" );
824
825    my $helperdir = $targetdir . $installer::globals::separator . "installhelper";
826    installer::systemactions::create_directory($helperdir);
827
828    # Get File.idt, Component.idt and Directory.idt from database
829
830    my $tablelist = "File Directory Component Registry";
831    extract_tables_from_pcpfile($databasepath, $helperdir, $tablelist);
832
833    # Unpack all cab files into $helperdir, cab files must be located next to msi database
834    my $installdir = $databasepath;
835
836    if ( $^O =~ /cygwin/i ) { $installdir =~ s/\\/\//g; } # backslash to slash
837
838    installer::pathanalyzer::get_path_from_fullqualifiedname(\$installdir);
839
840    if ( $^O =~ /cygwin/i ) { $installdir =~ s/\//\\/g; } # slash to backslash
841
842    my $databasefilename = $databasepath;
843    installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$databasefilename);
844
845    my $cabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir);
846
847    if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); }
848
849    # Set unpackdir
850    my $unpackdir = $helperdir . $installer::globals::separator . "unpack";
851    installer::systemactions::create_directory($unpackdir);
852
853    for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
854    {
855        my $cabfile = "";
856        if ( $^O =~ /cygwin/i )
857        {
858            $cabfile = $installdir . ${$cabfiles}[$i];
859        }
860        else
861        {
862            $cabfile = $installdir . $installer::globals::separator . ${$cabfiles}[$i];
863        }
864        unpack_cabinet_file($cabfile, $unpackdir);
865    }
866
867    # Reading tables
868    my $filename = $helperdir . $installer::globals::separator . "Directory.idt";
869    my $filecontent = installer::files::read_file($filename);
870    my $dirhash = analyze_directory_file($filecontent);
871
872    $filename = $helperdir . $installer::globals::separator . "Component.idt";
873    my $componentfilecontent = installer::files::read_file($filename);
874    my $componenthash = analyze_component_file($componentfilecontent);
875
876    $filename = $helperdir . $installer::globals::separator . "File.idt";
877    $filecontent = installer::files::read_file($filename);
878    my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);
879
880    # Creating the directory structure
881    my $fullpathhash = create_directory_structure($dirhash, $targetdir);
882
883    # Copying files
884    my $unopkgfile = copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
885
886    my $msidatabase = $targetdir . $installer::globals::separator . $databasefilename;
887    installer::systemactions::copy_one_file($databasepath, $msidatabase);
888
889    if ( $unopkgfile ne "" )
890    {
891        # Removing empty dirs in extension folder
892        my $extensionfolder = get_extensions_dir($unopkgfile);
893        if ( -d $extensionfolder ) { remove_empty_dirs_in_folder($extensionfolder, 1); }
894    }
895
896    # Editing registry table because of wrong Property value
897    #   my $registryfilename = $helperdir . $installer::globals::separator . "Registry.idt";
898    #   my $componentfilename = $helperdir . $installer::globals::separator . "Component.idt";
899    #   my $componentkeypathhash = analyze_keypath_component_file($componentfilecontent);
900
901    #   my $registryfilecontent = installer::files::read_file($registryfilename);
902    #   my $registryhash = analyze_registry_file($registryfilecontent);
903
904    #   $registryfilecontent = remove_properties_from_registry_table($registryhash, $componentkeypathhash, $registryfilecontent);
905
906    #   installer::files::save_file($registryfilename, $registryfilecontent);
907    #   $tablelist = "Registry";
908    #   include_tables_into_pcpfile($msidatabase, $helperdir, $tablelist);
909
910    # Saving info in Summary Information Stream of msi database (required for following patches)
911    write_sis_info($msidatabase);
912
913    return $msidatabase;
914}
915
9161;
917