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