#**************************************************************
#  
#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#  
#    http://www.apache.org/licenses/LICENSE-2.0
#  
#  Unless required by applicable law or agreed to in writing,
#  software distributed under the License is distributed on an
#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
#  KIND, either express or implied.  See the License for the
#  specific language governing permissions and limitations
#  under the License.
#  
#**************************************************************



package installer::windows::admin;

use File::Copy;
use installer::exiter;
use installer::files;
use installer::globals;
use installer::pathanalyzer;
use installer::systemactions;
use installer::worker;
use installer::windows::idtglobal;

#################################################################################
# Unpacking cabinet files with expand
#################################################################################

sub unpack_cabinet_file
{
	my ($cabfilename, $unpackdir) = @_;
	
	my $infoline = "Unpacking cabinet file: $cabfilename\n";
	$installer::logger::Lang->print($infoline);

	my $expandfile = "expand.exe";	# Has to be in the path

	# expand.exe has to be located in the system directory.
	# Cygwin has another tool expand.exe, that converts tabs to spaces. This cannot be used of course. 
	# But this wrong expand.exe is typically in the PATH before this expand.exe, to unpack 
	# cabinet files.

#	if ( $^O =~ /cygwin/i )
#	{
#		$expandfile = $ENV{'SYSTEMROOT'} . "/system32/expand.exe"; # Has to be located in the systemdirectory
#		$expandfile =~ s/\\/\//;
#		if ( ! -f $expandfile ) { exit_program("ERROR: Did not find file $expandfile in the Windows system folder!"); }
#	}

	if ( $^O =~ /cygwin/i )
	{
		$expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
		chomp $expandfile;
	}

	my $expandlogfile = $unpackdir . $installer::globals::separator . "expand.log";

	# exclude cabinet file
	# my $systemcall = $cabarc . " -o X " . $mergemodulehash->{'cabinetfile'};

	my $systemcall = "";
	if ( $^O =~ /cygwin/i ) {
		my $localunpackdir = qx{cygpath -w "$unpackdir"};
        chomp ($localunpackdir);
		$localunpackdir =~ s/\\/\\\\/g;
		$cabfilename =~ s/\\/\\\\/g;
		$cabfilename =~ s/\s*$//g;
		$systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $localunpackdir . " \> " . $expandlogfile;
	}
	else
	{
		$systemcall = $expandfile . " " . $cabfilename . " -F:\* " . $unpackdir . " \> " . $expandlogfile;
	}

	my $returnvalue = system($systemcall);
	$infoline = "Systemcall: $systemcall\n";
	$installer::logger::Lang->print($infoline);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		$installer::logger::Lang->print($infoline);
		installer::exiter::exit_program("ERROR: Could not extract cabinet file: $mergemodulehash->{'cabinetfile'} !", "change_file_table");
	}
	else
	{
		$infoline = "Success: Executed $systemcall successfully!\n";
		$installer::logger::Lang->print($infoline);
	}
}

#################################################################################
# Include tables into a msi database
#################################################################################

sub include_tables_into_pcpfile
{
	my ($fullmsidatabasepath, $workdir, $tables) = @_;

	my $msidb = "msidb.exe";	# Has to be in the path
	my $infoline = "";
	my $systemcall = "";
	my $returnvalue = "";

	# Make all table 8+3 conform
	my $alltables = installer::converter::convert_stringlist_into_array(\$tables, " ");
	
	for ( my $i = 0; $i <= $#{$alltables}; $i++ )
	{
		my $tablename = ${$alltables}[$i];
		$tablename =~ s/\s*$//;
		my $namelength = length($tablename);
		if ( $namelength > 8 )
		{
			my $newtablename = substr($tablename, 0, 8);	# name, offset, length
			my $oldfile = $workdir . $installer::globals::separator . $tablename . ".idt";
			my $newfile = $workdir . $installer::globals::separator . $newtablename . ".idt";
			if ( -f $newfile ) { unlink $newfile; }
			installer::systemactions::copy_one_file($oldfile, $newfile);
			my $savfile = $oldfile . ".orig";
			installer::systemactions::copy_one_file($oldfile, $savfile);
		}
	}

	# Import of tables

	$systemcall = $msidb . " -d " . $fullmsidatabasepath . " -f " . $workdir . " -i " . $tables;
							
	$returnvalue = system($systemcall);

	$infoline = "Systemcall: $systemcall\n";
	$installer::logger::Lang->print($infoline);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		$installer::logger::Lang->print($infoline);
		installer::exiter::exit_program("ERROR: Could not include tables into msi database: $fullmsidatabasepath !", "include_tables_into_pcpfile");
	}
	else
	{
		$infoline = "Success: Executed $systemcall successfully!\n";
		$installer::logger::Lang->print($infoline);
	}
}

#################################################################################
# Extracting tables from msi database
#################################################################################

sub extract_tables_from_pcpfile
{
	my ($fullmsidatabasepath, $workdir, $tablelist) = @_;

	my $msidb = "msidb.exe";	# Has to be in the path
	my $infoline = "";
	my $systemcall = "";
	my $returnvalue = "";
	
	my $localfullmsidatabasepath = $fullmsidatabasepath;

	# Export of all tables by using "*"

	if ( $^O =~ /cygwin/i ) {
		# Copying the msi database locally guarantees the format of the directory. 
		# Otherwise it is defined in the file of UPDATE_DATABASE_LISTNAME 

		my $msifilename = $localfullmsidatabasepath;
		installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$msifilename);
		my $destdatabasename = $workdir . $installer::globals::separator . $msifilename;
		installer::systemactions::copy_one_file($localfullmsidatabasepath, $destdatabasename);
		$localfullmsidatabasepath = $destdatabasename;
		
		chomp( $localfullmsidatabasepath = qx{cygpath -w "$localfullmsidatabasepath"} ); 
		chomp( $workdir = qx{cygpath -w "$workdir"} ); 

		# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
		$localfullmsidatabasepath =~ s/\\/\\\\/g;
		$workdir =~ s/\\/\\\\/g;

		# and if there are still slashes, they also need to be double backslash
		$localfullmsidatabasepath =~ s/\//\\\\/g;
		$workdir =~ s/\//\\\\/g;
	}
							
	$systemcall = $msidb . " -d " . $localfullmsidatabasepath . " -f " . $workdir . " -e $tablelist";
	$returnvalue = system($systemcall);

	$infoline = "Systemcall: $systemcall\n";
	$installer::logger::Lang->print($infoline);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		$installer::logger::Lang->print($infoline);
		installer::exiter::exit_program("ERROR: Could not exclude tables from pcp file: $localfullmsidatabasepath !", "extract_tables_from_pcpfile");
	}
	else
	{
		$infoline = "Success: Executed $systemcall successfully!\n";
		$installer::logger::Lang->print($infoline);
	}
}

################################################################################
# Analyzing the content of Directory.idt
#################################################################################

sub analyze_directory_file
{
	my ($filecontent) = @_;
	
	my %table = ();

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $dir = $1;
			my $parent = $2;
			my $name = $3;
			
			if ( $name =~ /^\s*(.*?)\s*\:\s*(.*?)\s*$/ ) { $name = $2; }
			if ( $name =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $name = $2; }
			
			my %helphash = ();
			$helphash{'Directory_Parent'} = $parent;
			$helphash{'DefaultDir'} = $name;
			$table{$dir} = \%helphash;
		}
	}
	
	return \%table;	
}

#################################################################################
# Analyzing the content of Component.idt
#################################################################################

sub analyze_component_file
{
	my ($filecontent) = @_;
	
	my %table = ();
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $component = $1;
			my $dir = $3;
			
			$table{$component} = $dir;
		}
	}

	return \%table;	
}

#################################################################################
# Analyzing the full content of Component.idt
#################################################################################

sub analyze_keypath_component_file
{
	my ($filecontent) = @_;

	my %keypathtable = ();
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $component = $1;
			my $keypath = $6;

			$keypathtable{$keypath} = $component;
		}
	}

	return (\%keypathtable);

}

#################################################################################
# Analyzing the content of Registry.idt
#################################################################################

sub analyze_registry_file
{
	my ($filecontent) = @_;
	
	my %table = ();
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $registry = $1;
			my $root = $2;
			my $key = $3;
			my $name = $4;
			my $value = $5;
			my $component = $6;
			
			my %helphash = ();
			# $helphash{'Registry'} = $registry;
			$helphash{'Root'} = $root;
			$helphash{'Key'} = $key;
			$helphash{'Name'} = $name;
			$helphash{'Value'} = $value;
			$helphash{'Component'} = $component;

			$table{$registry} = \%helphash;
		}
	}

	return \%table;		
}

#################################################################################
# Analyzing the content of File.idt
#################################################################################

sub analyze_file_file
{
	my ($filecontent) = @_;
	
	my %table = ();
	my %fileorder = ();
	my $maxsequence = 0;
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if (( $i == 0 ) || ( $i == 1 ) || ( $i == 2 )) { next; }

		if ( ${$filecontent}[$i] =~ /^\s*(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my $file = $1;
			my $comp = $2;
			my $filename = $3;
			my $sequence = $8;

			if ( $filename =~ /^\s*(.*?)\s*\|\s*(.*?)\s*$/ ) { $filename = $2; }
			
			my %helphash = ();
			$helphash{'Component'} = $comp;
			$helphash{'FileName'} = $filename;
			$helphash{'Sequence'} = $sequence;

			$table{$file} = \%helphash;
			
			$fileorder{$sequence} = $file;
			
			if ( $sequence > $maxsequence ) { $maxsequence = $sequence; }
		}
	}

	return (\%table, \%fileorder, $maxsequence);
}

####################################################################################
# Recursively creating the directory tree
####################################################################################

sub create_directory_tree
{
	my ($parent, $pathcollector, $fulldir, $dirhash) = @_;	

	foreach my $dir ( keys %{$dirhash} )
	{
		if (( $dirhash->{$dir}->{'Directory_Parent'} eq $parent ) && ( $dirhash->{$dir}->{'DefaultDir'} ne "." ))
		{
			my $dirname = $dirhash->{$dir}->{'DefaultDir'};
			# Create the directory
			my $newdir = $fulldir . $installer::globals::separator . $dirname;
			if ( ! -f $newdir ) { mkdir $newdir; }
			# Saving in collector
			$pathcollector->{$dir} = $newdir;
			# Iteration
			create_directory_tree($dir, $pathcollector, $newdir, $dirhash);
		}
	}
}

####################################################################################
# Creating the directory tree
####################################################################################

sub create_directory_structure
{
	my ($dirhash, $targetdir) = @_;
	
	my %fullpathhash = ();
	
	my @startparents = ("TARGETDIR", "INSTALLLOCATION");
	
	foreach $dir (@startparents) { create_directory_tree($dir, \%fullpathhash, $targetdir, $dirhash); }

	# Also adding the pathes of the startparents
	foreach $dir (@startparents)
	{
		if ( ! exists($fullpathhash{$dir}) ) { $fullpathhash{$dir} = $targetdir; }
	}
	
	return \%fullpathhash;
}

####################################################################################
# Copying files into installation set
####################################################################################

sub copy_files_into_directory_structure
{
	my ($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash) = @_;

	my $unopkgfile = "";

	for ( my $i = 1; $i <= $maxsequence; $i++ )
	{
		if ( exists($fileorder->{$i}) )
		{
			my $file = $fileorder->{$i};
			if ( ! exists($filehash->{$file}->{'Component'}) ) { installer::exiter::exit_program("ERROR: Did not find component for file: \"$file\".", "copy_files_into_directory_structure"); }
			my $component = $filehash->{$file}->{'Component'};
			if ( ! exists($componenthash->{$component}) ) { installer::exiter::exit_program("ERROR: Did not find directory for component: \"$component\".", "copy_files_into_directory_structure"); }
			my $dirname = $componenthash->{$component};
			if ( ! exists($fullpathhash->{$dirname}) ) { installer::exiter::exit_program("ERROR: Did not find full directory path for dir: \"$dirname\".", "copy_files_into_directory_structure"); }
			my $destdir = $fullpathhash->{$dirname};
			if ( ! exists($filehash->{$file}->{'FileName'}) ) { installer::exiter::exit_program("ERROR: Did not find \"FileName\" for file: \"$file\".", "copy_files_into_directory_structure"); }
			my $destfile = $filehash->{$file}->{'FileName'};

			$destfile = $destdir . $installer::globals::separator . $destfile;
			my $sourcefile = $unpackdir . $installer::globals::separator . $file;
			
			if ( ! -f $sourcefile )
			{
				# It is possible, that this was an unpacked file
				# Looking in the dirhash, to find the subdirectory in the installation set (the id is $dirname)
				# subdir is not recursively analyzed, only one directory.
				
				my $oldsourcefile = $sourcefile;			
				my $subdir = "";
				if ( exists($dirhash->{$dirname}->{'DefaultDir'}) ) { $subdir = $dirhash->{$dirname}->{'DefaultDir'} . $installer::globals::separator; }
				my $realfilename = $filehash->{$file}->{'FileName'};
				my $localinstalldir = $installdir;
				
				$localinstalldir =~ s/\\\s*$//;
				$localinstalldir =~ s/\/\s*$//;
				
				$sourcefile = $localinstalldir . $installer::globals::separator . $subdir . $realfilename;
				
				if ( ! -f $sourcefile )
				{
					installer::exiter::exit_program("ERROR: File not found: \"$oldsourcefile\" (or \"$sourcefile\").", "copy_files_into_directory_structure");
				}
			}

			my $copyreturn = copy($sourcefile, $destfile);

			if ( ! $copyreturn)	# only logging problems
			{
				my $infoline = "ERROR: Could not copy $sourcefile to $destfile (insufficient disc space for $destfile ?)\n";
				$returnvalue = 0;
				$installer::logger::Lang->print($infoline);
				installer::exiter::exit_program($infoline, "copy_files_into_directory_structure");
			}

			if ( $destfile =~ /unopkg\.exe\s*$/ ) { $unopkgfile = $destfile; }

			# installer::systemactions::copy_one_file($sourcefile, $destfile);
		}
		# else	# allowing missing sequence numbers ?
		# {
		# 	installer::exiter::exit_program("ERROR: No file assigned to sequence $i", "copy_files_into_directory_structure");
		# }
	}
	
	return $unopkgfile;
}


###############################################################
# Setting the time string for the 
# Summary Information stream in the 
# msi database of the admin installations.
###############################################################

sub get_sis_time_string
{	
	# Syntax: <yyyy/mm/dd hh:mm:ss>
	my $second = (localtime())[0];
	my $minute = (localtime())[1];
	my $hour = (localtime())[2];
	my $day = (localtime())[3];
	my $month = (localtime())[4];
	my $year = 1900 + (localtime())[5];

	$month++; # zero based month

	if ( $second < 10 ) { $second = "0" . $second; }
	if ( $minute < 10 ) { $minute = "0" . $minute; }
	if ( $hour < 10 ) { $hour = "0" . $hour; }
	if ( $day < 10 ) { $day = "0" . $day; }
	if ( $month < 10 ) { $month = "0" . $month; }
	
	my $timestring = $year . "/" . $month . "/" . $day . " " . $hour . ":" . $minute . ":" . $second;
		
	return $timestring;
}

###############################################################
# Windows registry entries containing properties are not set
# correctly during msp patch process. The properties are 
# empty or do get their default values. This destroys the 
# values of many entries in Windows registry.
# This can be fixed by removing all entries in Registry table,
# containing a property before starting msimsp.exe.
###############################################################

sub remove_properties_from_registry_table
{
	my ($registryhash, $componentkeypathhash, $registryfilecontent) = @_;

	$installer::logger::Lang->print("\n");
	$installer::logger::Lang->add_timestamp("Performance Info: Start remove_properties_from_registry_table");
	
	my @registrytable = ();
	
	# Registry hash
	# Collecting all RegistryItems with values containing a property: [...]
	# To which component do they belong
	# Is this after removal an empty component? Create a replacement, so that
	# no Component has to be removed.
	# Is this RegistryItem a KeyPath of a component. Then it cannot be removed.
	
	my %problemitems = ();
	my %problemcomponents = ();
	my %securecomponents = ();
	my $changevalue = "";
	my $changeroot = "";
	my $infoline = "";

	my $newitemcounter = 0;
	my $olditemcounter = 0;
	
	foreach my $regitem ( keys %{$registryhash} )
	{
		my $value = "";
		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }

		if ( $value =~ /^.*(\[.*?\]).*$/ )
		{
			my $property = $1;
			
			# Collecting registry item
			$problemitems{$regitem} = 1;	# "1" -> can be removed
			if ( exists($componentkeypathhash->{$regitem}) ) { $problemitems{$regitem} = 2; } 	# "2" -> cannot be removed, KeyPath
			
			# Collecting component (and number of problematic registry items
			# my $component = $registryhash->{$regitem}->{'Component'};
			# if ( exists($problemcomponents{$regitem}) ) { $problemcomponents{$regitem} = $problemcomponents{$regitem} + 1; }
			# else { $problemcomponents{$regitem} = 1; }
		}
		else
		{
			# Collecting all components with secure regisry items
			my $component = "";
			if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
			if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item \"$regitem\".", "remove_properties_from_registry_table"); }
			$securecomponents{$component} = 1;
		}
		
		# Searching for change value
		my $localkey = "";
		if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
		if (( $localkey =~ /^\s*(Software\\.*\\)StartMenu\s*$/ ) && ( $changevalue eq "" ))
		{
			$changevalue = $1;
			$changeroot = $registryhash->{$regitem}->{'Root'};
		}	
		
		$olditemcounter++;	
	}
	
	my $removecounter = 0;
	my $renamecounter = 0;
	
	foreach my $regitem ( keys %{$registryhash} )
	{
		my $value = "";
		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }

		if ( $value =~ /^.*(\[.*?\]).*$/ )
		{
			# Removing registry items, that are no KeyPath and that belong to components,
			# that have other secure registry items.

			my $component = "";
			if ( exists($registryhash->{$regitem}->{'Component'}) ) { $component = $registryhash->{$regitem}->{'Component'}; }
			if ( $component eq "" ) { installer::exiter::exit_program("ERROR: Did not find component for registry item (2) \"$regitem\".", "remove_properties_from_registry_table"); }

			if (( $problemitems{$regitem} == 1 ) && ( exists($securecomponents{$component}) ))
			{
				# remove complete registry item
				delete($registryhash->{$regitem});
				$removecounter++;
				$infoline = "Removing registry item: $regitem : $value\n";
				$installer::logger::Lang->print($infoline);
			}
			else
			{
				# Changing values of registry items, that are KeyPath or that contain to
				# components with only unsecure registry items.
	
				if (( $problemitems{$regitem} == 2 ) || ( ! exists($securecomponents{$component}) ))
				{
					# change value of registry item
					if ( $changevalue eq "" ) { installer::exiter::exit_program("ERROR: Did not find good change value for registry items", "remove_properties_from_registry_table"); }

					my $oldkey = "";
					if ( exists($registryhash->{$regitem}->{'Key'}) ) { $oldkey = $registryhash->{$regitem}->{'Key'}; };
					my $oldname = ""; 
					if ( exists($registryhash->{$regitem}->{'Name'}) ) { $oldname = $registryhash->{$regitem}->{'Name'}; }
					my $oldvalue = "";
					if ( exists($registryhash->{$regitem}->{'Value'}) ) { $oldvalue = $registryhash->{$regitem}->{'Value'}; }
					
					$registryhash->{$regitem}->{'Key'} = $changevalue . "RegistryItem";
					$registryhash->{$regitem}->{'Root'} = $changeroot;
					$registryhash->{$regitem}->{'Name'} = $regitem;
					$registryhash->{$regitem}->{'Value'} = 1;
					$renamecounter++;

					$infoline = "Changing registry item: $regitem\n";
					$infoline = "Old: $oldkey : $oldname : $oldvalue\n";
					$infoline = "New: $registryhash->{$regitem}->{'Key'} : $registryhash->{$regitem}->{'Name'} : $registryhash->{$regitem}->{'Value'}\n";
					$installer::logger::Lang->print($infoline);
				}
			}
		}
	}

	$infoline = "Number of removed registry items: $removecounter\n";
	$installer::logger::Lang->print($infoline);
	$infoline = "Number of changed registry items: $renamecounter\n";
	$installer::logger::Lang->print($infoline);
	
	# Creating the new content of Registry table
	# First three lines from $registryfilecontent
	# All further files from changed $registryhash
	
	for ( my $i = 0; $i <= 2; $i++ ) { push(@registrytable, ${$registryfilecontent}[$i]); }

	foreach my $regitem ( keys %{$registryhash} )
	{
		my $root = "";
		if ( exists($registryhash->{$regitem}->{'Root'}) ) { $root = $registryhash->{$regitem}->{'Root'}; }
		else { installer::exiter::exit_program("ERROR: Did not find root in registry table for item: \"$regitem\".", "remove_properties_from_registry_table"); }
		my $localkey = "";
		if ( exists($registryhash->{$regitem}->{'Key'}) ) { $localkey = $registryhash->{$regitem}->{'Key'}; }
		my $name = "";
		if ( exists($registryhash->{$regitem}->{'Name'}) ) { $name = $registryhash->{$regitem}->{'Name'}; }
		my $value = "";
		if ( exists($registryhash->{$regitem}->{'Value'}) ) { $value = $registryhash->{$regitem}->{'Value'}; }
		my $comp = "";
		if ( exists($registryhash->{$regitem}->{'Component'}) ) { $comp = $registryhash->{$regitem}->{'Component'}; }
		
		my $oneline = $regitem . "\t" . $root . "\t" . $localkey . "\t" . $name . "\t" . $value . "\t" . $comp . "\n";
		push(@registrytable, $oneline);

		$newitemcounter++;
	}

	$infoline = "Number of registry items: $newitemcounter. Old value: $olditemcounter.\n";
	$installer::logger::Lang->print($infoline);

	$installer::logger::Lang->print("\n");
	$installer::logger::Lang->add_timestamp("Performance Info: End remove_properties_from_registry_table");
	
	return (\@registrytable);
}

###############################################################
# Writing content of administrative installations into 
# Summary Information Stream of msi database. 
# This is required for example for following
# patch processes using Windows Installer service.
###############################################################

sub write_sis_info
{
	my ($msidatabase) = @_ ;
	
	if ( ! -f $msidatabase ) { installer::exiter::exit_program("ERROR: Cannot find file $msidatabase", "write_sis_info"); }

	my $msiinfo = "msiinfo.exe";	# Has to be in the path
	my $infoline = "";
	my $systemcall = "";
	my $returnvalue = "";

	# Required setting for administrative installations:
	# -w 4   (source files are unpacked),  wordcount
	# -s <date of admin installation>, LastPrinted, Syntax: <yyyy/mm/dd hh:mm:ss>
	# -l <person_making_admin_installation>, LastSavedBy
	
	my $wordcount = 4;  # Unpacked files
	my $lastprinted = get_sis_time_string();
	my $lastsavedby = "Installer";

	my $localmsidatabase = $msidatabase;
	
	if( $^O =~ /cygwin/i )
	{
		$localmsidatabase = qx{cygpath -w "$localmsidatabase"};
		$localmsidatabase =~ s/\\/\\\\/g;
		$localmsidatabase =~ s/\s*$//g;
	}
							
	$systemcall = $msiinfo . " " . "\"" . $localmsidatabase . "\"" . " -w " . $wordcount . " -s " . "\"" . $lastprinted . "\"" . " -l $lastsavedby";
    $installer::logger::Lang->printf($systemcall);
	$returnvalue = system($systemcall);

	if ($returnvalue)
	{
		$infoline = "ERROR: Could not execute $systemcall !\n";
		$installer::logger::Lang->print($infoline);
		installer::exiter::exit_program($infoline, "write_sis_info");
	}	
}

####################################################
# Detecting the directory with extensions
####################################################

sub get_extensions_dir
{
	my ( $unopkgfile ) = @_;
	
	my $localbranddir = $unopkgfile;
	installer::pathanalyzer::get_path_from_fullqualifiedname(\$localbranddir); # "program" dir in brand layer
	installer::pathanalyzer::get_path_from_fullqualifiedname(\$localbranddir); # root dir in brand layer
	$localbranddir =~ s/\Q$installer::globals::separator\E\s*$//;
	my $extensiondir = $localbranddir . $installer::globals::separator . "share" . $installer::globals::separator . "extensions";
	
	return $extensiondir;	
}

##############################################################
# Removing all empty directories below a specified directory
##############################################################

sub remove_empty_dirs_in_folder
{
	my ( $dir, $firstrun ) = @_;

	if ( $firstrun )
	{
		print "Removing superfluous directories\n";
	}

	my @content = ();
	
	$dir =~ s/\Q$installer::globals::separator\E\s*$//;

	if ( -d $dir )
	{
		opendir(DIR, $dir);
		@content = readdir(DIR);
		closedir(DIR);

		my $oneitem;
	
		foreach $oneitem (@content)
		{
			if ((!($oneitem eq ".")) && (!($oneitem eq "..")))
			{
				my $item = $dir . $installer::globals::separator . $oneitem;

				if ( -d $item ) # recursive
				{
					remove_empty_dirs_in_folder($item, 0);
				}
			}
		}
		
		# try to remove empty directory	
		my $returnvalue = rmdir $dir;

		# if ( $returnvalue ) { print "Successfully removed empty dir $dir\n"; }	
	}
}

####################################################################################
# Simulating an administrative installation
####################################################################################

sub make_admin_install
{
	my ($databasepath, $targetdir) = @_;

	# Create helper directory

    $installer::logger::Info->printf("... installing %s in directory %s ...\n",
        $databasepath,
        $targetdir);

	my $helperdir = $targetdir . $installer::globals::separator . "installhelper";
	installer::systemactions::create_directory($helperdir);
	
	# Get File.idt, Component.idt and Directory.idt from database
	
	my $tablelist = "File Directory Component Registry";
	extract_tables_from_pcpfile($databasepath, $helperdir, $tablelist);
	
	# Unpack all cab files into $helperdir, cab files must be located next to msi database
	my $installdir = $databasepath;

	if ( $^O =~ /cygwin/i ) { $installdir =~ s/\\/\//g; } # backslash to slash
	
	installer::pathanalyzer::get_path_from_fullqualifiedname(\$installdir);

	if ( $^O =~ /cygwin/i ) { $installdir =~ s/\//\\/g; } # slash to backslash

	my $databasefilename = $databasepath;
	installer::pathanalyzer::make_absolute_filename_to_relative_filename(\$databasefilename);

	my $cabfiles = installer::systemactions::find_file_with_file_extension("cab", $installdir);
	
	if ( $#{$cabfiles} < 0 ) { installer::exiter::exit_program("ERROR: Did not find any cab file in directory $installdir", "make_admin_install"); }

	# Set unpackdir
	my $unpackdir = $helperdir . $installer::globals::separator . "unpack";
	installer::systemactions::create_directory($unpackdir);

	for ( my $i = 0; $i <= $#{$cabfiles}; $i++ )
	{
		my $cabfile = "";
		if ( $^O =~ /cygwin/i )
		{
			$cabfile = $installdir . ${$cabfiles}[$i];
		}
		else
		{
			$cabfile = $installdir . $installer::globals::separator . ${$cabfiles}[$i];
		}
		unpack_cabinet_file($cabfile, $unpackdir);
	}
	
	# Reading tables
	my $filename = $helperdir . $installer::globals::separator . "Directory.idt";
	my $filecontent = installer::files::read_file($filename);
	my $dirhash = analyze_directory_file($filecontent);
	
	$filename = $helperdir . $installer::globals::separator . "Component.idt";
	my $componentfilecontent = installer::files::read_file($filename);
	my $componenthash = analyze_component_file($componentfilecontent);
	
	$filename = $helperdir . $installer::globals::separator . "File.idt";
	$filecontent = installer::files::read_file($filename);
	my ( $filehash, $fileorder, $maxsequence ) = analyze_file_file($filecontent);

	# Creating the directory structure
	my $fullpathhash = create_directory_structure($dirhash, $targetdir);

	# Copying files
	my $unopkgfile = copy_files_into_directory_structure($fileorder, $filehash, $componenthash, $fullpathhash, $maxsequence, $unpackdir, $installdir, $dirhash);
	
	my $msidatabase = $targetdir . $installer::globals::separator . $databasefilename;
	installer::systemactions::copy_one_file($databasepath, $msidatabase);
	
	if ( $unopkgfile ne "" )
	{
		# Removing empty dirs in extension folder
		my $extensionfolder = get_extensions_dir($unopkgfile);
		if ( -d $extensionfolder ) { remove_empty_dirs_in_folder($extensionfolder, 1); }
	}
	
	# Editing registry table because of wrong Property value
	#	my $registryfilename = $helperdir . $installer::globals::separator . "Registry.idt";
	#	my $componentfilename = $helperdir . $installer::globals::separator . "Component.idt";
	#	my $componentkeypathhash = analyze_keypath_component_file($componentfilecontent);

	#	my $registryfilecontent = installer::files::read_file($registryfilename);
	#	my $registryhash = analyze_registry_file($registryfilecontent);

	#	$registryfilecontent = remove_properties_from_registry_table($registryhash, $componentkeypathhash, $registryfilecontent);

	#	installer::files::save_file($registryfilename, $registryfilecontent);
	#	$tablelist = "Registry";
	#	include_tables_into_pcpfile($msidatabase, $helperdir, $tablelist);
	
	# Saving info in Summary Information Stream of msi database (required for following patches)
	write_sis_info($msidatabase);

	return $msidatabase;
}

1;
