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