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	push( @installer::globals::logfileinfo, $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	push( @installer::globals::logfileinfo, $infoline);
88
89	if ($returnvalue)
90	{
91		$infoline = "ERROR: Could not execute $systemcall !\n";
92		push( @installer::globals::logfileinfo, $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		push( @installer::globals::logfileinfo, $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	push( @installer::globals::logfileinfo, $infoline);
143
144	if ($returnvalue)
145	{
146		$infoline = "ERROR: Could not execute $systemcall !\n";
147		push( @installer::globals::logfileinfo, $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		push( @installer::globals::logfileinfo, $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	push( @installer::globals::logfileinfo, $infoline);
201
202	if ($returnvalue)
203	{
204		$infoline = "ERROR: Could not execute $systemcall !\n";
205		push( @installer::globals::logfileinfo, $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		push( @installer::globals::logfileinfo, $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				push(@installer::globals::logfileinfo, $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::include_timestamp_into_logfile("\nPerformance Info: Start remove_properties_from_registry_table");
544
545	my @registrytable = ();
546
547	# Registry hash
548	# Collecting all RegistryItems with values containing a property: [...]
549	# To which component do they belong
550	# Is this after removal an empty component? Create a replacement, so that
551	# no Component has to be removed.
552	# Is this RegistryItem a KeyPath of a component. Then it cannot be removed.
553
554	my %problemitems = ();
555	my %problemcomponents = ();
556	my %securecomponents = ();
557	my $changevalue = "";
558	my $changeroot = "";
559	my $infoline = "";
560
561	my $newitemcounter = 0;
562	my $olditemcounter = 0;
563
564	foreach my $regitem ( keys %{$registryhash} )
565	{
566		my $value = "";
567		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
568
569		if ( $value =~ /^.*(\[.*?\]).*$/ )
570		{
571			my $property = $1;
572
573			# Collecting registry item
574			$problemitems{$regitem} = 1;	# "1" -> can be removed
575			if ( exists($componentkeypathhash->{$regitem}) ) { $problemitems{$regitem} = 2; } 	# "2" -> cannot be removed, KeyPath
576
577			# Collecting component (and number of problematic registry items
578			# my $component = $registryhash->{$regitem}->{'Component'};
579			# if ( exists($problemcomponents{$regitem}) ) { $problemcomponents{$regitem} = $problemcomponents{$regitem} + 1; }
580			# else { $problemcomponents{$regitem} = 1; }
581		}
582		else
583		{
584			# Collecting all components with secure regisry items
585			my $component = "";
586			if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
587			if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item \"$regitem\".", "remove_properties_from_registry_table"); }
588			$securecomponents{$component} = 1;
589		}
590
591		# Searching for change value
592		my $localkey = "";
593		if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
594		if (( $localkey =~ /^\s*(Software\\.*\\)StartMenu\s*$/ ) && ( $changevalue eq "" ))
595		{
596			$changevalue = $1;
597			$changeroot = $registryhash->{$regitem}->{'Root'};
598		}
599
600		$olditemcounter++;
601	}
602
603	my $removecounter = 0;
604	my $renamecounter = 0;
605
606	foreach my $regitem ( keys %{$registryhash} )
607	{
608		my $value = "";
609		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
610
611		if ( $value =~ /^.*(\[.*?\]).*$/ )
612		{
613			# Removing registry items, that are no KeyPath and that belong to components,
614			# that have other secure registry items.
615
616			my $component = "";
617			if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
618			if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item (2) \"$regitem\".", "remove_properties_from_registry_table"); }
619
620			if (( $problemitems{$regitem} == 1 ) && ( exists($securecomponents{$component}) ))
621			{
622				# remove complete registry item
623				delete($registryhash->{$regitem});
624				$removecounter++;
625				$infoline = "Removing registry item: $regitem : $value\n";
626				push( @installer::globals::logfileinfo, $infoline);
627			}
628			else
629			{
630				# Changing values of registry items, that are KeyPath or that contain to
631				# components with only unsecure registry items.
632
633				if (( $problemitems{$regitem} == 2 ) || ( ! exists($securecomponents{$component}) ))
634				{
635					# change value of registry item
636					if ( $changevalue eq "" ) { installer::exiter::exit_program("ERROR: Did not find good change value for registry items", "remove_properties_from_registry_table"); }
637
638					my $oldkey = "";
639					if ( exists($registryhash->{$regitem}->{'Key'}) ) { $oldkey = $registryhash->{$regitem}->{'Key'}; };
640					my $oldname = "";
641					if ( exists($registryhash->{$regitem}->{'Name'}) ) { $oldname = $registryhash->{$regitem}->{'Name'}; }
642					my $oldvalue = "";
643					if ( exists($registryhash->{$regitem}->{'Value'}) ) { $oldvalue = $registryhash->{$regitem}->{'Value'}; }
644
645					$registryhash->{$regitem}->{'Key'} = $changevalue . "RegistryItem";
646					$registryhash->{$regitem}->{'Root'} = $changeroot;
647					$registryhash->{$regitem}->{'Name'} = $regitem;
648					$registryhash->{$regitem}->{'Value'} = 1;
649					$renamecounter++;
650
651					$infoline = "Changing registry item: $regitem\n";
652					$infoline = "Old: $oldkey : $oldname : $oldvalue\n";
653					$infoline = "New: $registryhash->{$regitem}->{'Key'} : $registryhash->{$regitem}->{'Name'} : $registryhash->{$regitem}->{'Value'}\n";
654					push( @installer::globals::logfileinfo, $infoline);
655				}
656			}
657		}
658	}
659
660	$infoline = "Number of removed registry items: $removecounter\n";
661	push( @installer::globals::logfileinfo, $infoline);
662	$infoline = "Number of changed registry items: $renamecounter\n";
663	push( @installer::globals::logfileinfo, $infoline);
664
665	# Creating the new content of Registry table
666	# First three lines from $registryfilecontent
667	# All further files from changed $registryhash
668
669	for ( my $i = 0; $i <= 2; $i++ ) { push(@registrytable, ${$registryfilecontent}[$i]); }
670
671	foreach my $regitem ( keys %{$registryhash} )
672	{
673		my $root = "";
674		if ( exists($registryhash->{$regitem}->{'Root'}) ) { $root = $registryhash->{$regitem}->{'Root'}; }
675		else { installer::exiter::exit_program("ERROR: Did not find root in registry table for item: \"$regitem\".", "remove_properties_from_registry_table"); }
676		my $localkey = "";
677		if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
678		my $name = "";
679		if ( exists($registryhash->{$regitem}->{'Name'}) ) { $name = $registryhash->{$regitem}->{'Name'}; }
680		my $value = "";
681		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
682		my $comp = "";
683		if ( exists($registryhash->{$regitem}->{'Component'}) ) { $comp = $registryhash->{$regitem}->{'Component'}; }
684
685		my $oneline = $regitem . "\t" . $root . "\t" . $localkey . "\t" . $name . "\t" . $value . "\t" . $comp . "\n";
686		push(@registrytable, $oneline);
687
688		$newitemcounter++;
689	}
690
691	$infoline = "Number of registry items: $newitemcounter. Old value: $olditemcounter.\n";
692	push( @installer::globals::logfileinfo, $infoline);
693
694	installer::logger::include_timestamp_into_logfile("\nPerformance 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	push(@installer::globals::logfileinfo, $systemcall);
737	$returnvalue = system($systemcall);
738
739	if ($returnvalue)
740	{
741		$infoline = "ERROR: Could not execute $systemcall !\n";
742		push(@installer::globals::logfileinfo, $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::print_message( "... installing $databasepath in directory $targetdir ...\n" );
820
821	my $helperdir = $targetdir . $installer::globals::separator . "installhelper";
822	installer::systemactions::create_directory($helperdir);
823
824	# Get File.idt, Component.idt and Directory.idt from database
825
826	my $tablelist = "File Directory Component Registry";
827	extract_tables_from_pcpfile($databasepath, $helperdir, $tablelist);
828
829	# Unpack all cab files into $helperdir, cab files must be located next to msi database
830	my $installdir = $databasepath;
831
832	if ( $^O =~ /cygwin/i ) { $installdir =~ s/\\/\//g; } # backslash to slash
833
834	installer::pathanalyzer::get_path_from_fullqualifiedname(\$installdir);
835
836	if ( $^O =~ /cygwin/i ) { $installdir =~ s/\//\\/g; } # slash to backslash
837
838	my $databasefilename = $databasepath;
839	installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$databasefilename);
840
841	my $cabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir);
842
843	if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); }
844
845	# Set unpackdir
846	my $unpackdir = $helperdir . $installer::globals::separator . "unpack";
847	installer::systemactions::create_directory($unpackdir);
848
849	for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
850	{
851		my $cabfile = "";
852		if ( $^O =~ /cygwin/i )
853		{
854			$cabfile = $installdir . ${$cabfiles}[$i];
855		}
856		else
857		{
858			$cabfile = $installdir . $installer::globals::separator . ${$cabfiles}[$i];
859		}
860		unpack_cabinet_file($cabfile, $unpackdir);
861	}
862
863	# Reading tables
864	my $filename = $helperdir . $installer::globals::separator . "Directory.idt";
865	my $filecontent = installer::files::read_file($filename);
866	my $dirhash = analyze_directory_file($filecontent);
867
868	$filename = $helperdir . $installer::globals::separator . "Component.idt";
869	my $componentfilecontent = installer::files::read_file($filename);
870	my $componenthash = analyze_component_file($componentfilecontent);
871
872	$filename = $helperdir . $installer::globals::separator . "File.idt";
873	$filecontent = installer::files::read_file($filename);
874	my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);
875
876	# Creating the directory structure
877	my $fullpathhash = create_directory_structure($dirhash, $targetdir);
878
879	# Copying files
880	my $unopkgfile = copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
881
882	my $msidatabase = $targetdir . $installer::globals::separator . $databasefilename;
883	installer::systemactions::copy_one_file($databasepath, $msidatabase);
884
885	if ( $unopkgfile ne "" )
886	{
887		# Removing empty dirs in extension folder
888		my $extensionfolder = get_extensions_dir($unopkgfile);
889		if ( -d $extensionfolder ) { remove_empty_dirs_in_folder($extensionfolder, 1); }
890	}
891
892	# Editing registry table because of wrong Property value
893	#	my $registryfilename = $helperdir . $installer::globals::separator . "Registry.idt";
894	#	my $componentfilename = $helperdir . $installer::globals::separator . "Component.idt";
895	#	my $componentkeypathhash = analyze_keypath_component_file($componentfilecontent);
896
897	#	my $registryfilecontent = installer::files::read_file($registryfilename);
898	#	my $registryhash = analyze_registry_file($registryfilecontent);
899
900	#	$registryfilecontent = remove_properties_from_registry_table($registryhash, $componentkeypathhash, $registryfilecontent);
901
902	#	installer::files::save_file($registryfilename, $registryfilecontent);
903	#	$tablelist = "Registry";
904	#	include_tables_into_pcpfile($msidatabase, $helperdir, $tablelist);
905
906	# Saving info in Summary Information Stream of msi database (required for following patches)
907	write_sis_info($msidatabase);
908
909	return $msidatabase;
910}
911
9121;
913