#**************************************************************
#  
#  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::mergemodule;

use Cwd;
use Digest::MD5;
use installer::converter;
use installer::exiter;
use installer::files;
use installer::globals;
use installer::logger;
use installer::pathanalyzer;
use installer::remover;
use installer::scriptitems;
use installer::systemactions;
use installer::worker;
use installer::windows::idtglobal;
use installer::windows::language;

#################################################################
# Merging the Windows MergeModules into the msi database.
#################################################################

sub merge_mergemodules_into_msi_database
{
	my ($mergemodules, $filesref, $msifilename, $languagestringref, $language, $languagefile, $allvariables, $includepatharrayref, $allupdatesequences, $allupdatelastsequences, $allupdatediskids) = @_;

	my $domerge = 0;
	if (( $#{$mergemodules} > -1 ) && ( ! $installer::globals::patch ) && ( ! $installer::globals::languagepack )) { $domerge = 1; }

	if ( $domerge )
	{
		installer::logger::include_header_into_logfile("Merging merge modules into msi database");
		installer::logger::print_message( "... merging msm files into msi database ... \n" );
		installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, start");

		my $msidb = "msidb.exe";	# Has to be in the path
		my $cabinetfile = "MergeModule.CABinet"; # the name of each cabinet file in a merge file
		my $infoline = "";
		my $systemcall = "";
		my $returnvalue = "";

		# 1. Analyzing the MergeModule (has only to be done once)
		#   a. -> Extracting cabinet file: msidb.exe -d <msmfile> -x MergeModule.CABinet
		#   b. -> Number of files in cabinet file: msidb.exe -d <msmfile> -f <directory> -e File
		#   c. -> List of components: msidb.exe -d <msmfile> -f <directory> -e Component

		if ( ! $installer::globals::mergemodules_analyzed )
		{
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, start");
			$infoline = "Analyzing all Merge Modules\n\n";
			push( @installer::globals::logfileinfo, $infoline);

			%installer::globals::mergemodules = ();
			
			my $mergemoduledir = installer::systemactions::create_directories("mergefiles", $languagestringref);
			# push(@installer::globals::removedirs, $mergemoduledir);

			my $mergemodule;
			foreach $mergemodule ( @{$mergemodules} )
			{
				my $filename = $mergemodule->{'Name'};
				my $mergefile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$filename, $includepatharrayref, 1);
	
				if ( ! -f $$mergefile ) { installer::exiter::exit_program("ERROR: msm file not found: $filename !", "merge_mergemodules_into_msi_database"); }
				my $completesource = $$mergefile;
			
				my $mergegid = $mergemodule->{'gid'};
				my $workdir = $mergemoduledir . $installer::globals::separator . $mergegid;
				if ( ! -d $workdir ) { installer::systemactions::create_directory($workdir); }

				$infoline = "Analyzing Merge Module: $filename\n";
				push( @installer::globals::logfileinfo, $infoline);
			
				# copy msm file into working directory
				my $completedest = $workdir . $installer::globals::separator . $filename;
				installer::systemactions::copy_one_file($completesource, $completedest);
				if ( ! -f $completedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completedest !", "merge_mergemodules_into_msi_database"); }
			
				# changing directory
				my $from = cwd();
				my $to = $workdir;
				chdir($to);

				# remove an existing cabinet file
				if ( -f $cabinetfile ) { unlink($cabinetfile); }

				# exclude cabinet file
				$systemcall = $msidb . " -d " . $filename . " -x " . $cabinetfile;
				$returnvalue = system($systemcall);

				$infoline = "Systemcall: $systemcall\n";
				push( @installer::globals::logfileinfo, $infoline);

				if ($returnvalue)
				{
					$infoline = "ERROR: Could not execute $systemcall !\n";
					push( @installer::globals::logfileinfo, $infoline);
					installer::exiter::exit_program("ERROR: Could not extract cabinet file from merge file: $completedest !", "merge_mergemodules_into_msi_database");
				}
				else
				{
					$infoline = "Success: Executed $systemcall successfully!\n";
					push( @installer::globals::logfileinfo, $infoline);
				}
			
				# exclude tables from mergefile
				# Attention: All listed tables have to exist in the database. If they not exist, an error window pops up
				# and the return value of msidb.exe is not zero. The error window makes it impossible to check the existence
				# of a table with the help of the return value. 
				# Solution: Export of all tables by using "*" . Some tables must exist (File Component Directory), other
				# tables do not need to exist (MsiAssembly).

				if ( $^O =~ /cygwin/i ) {
					# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
					my $localworkdir = $workdir;
					$localworkdir =~ s/\//\\\\/g;
					$systemcall = $msidb . " -d " . $filename . " -f " . $localworkdir . " -e \\\*";					
				}
				else
				{
					# $systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e File Component MsiAssembly Directory";
					$systemcall = $msidb . " -d " . $filename . " -f " . $workdir . " -e \*";					
				}
											
				$returnvalue = system($systemcall);

				$infoline = "Systemcall: $systemcall\n";
				push( @installer::globals::logfileinfo, $infoline);

				if ($returnvalue)
				{
					$infoline = "ERROR: Could not execute $systemcall !\n";
					push( @installer::globals::logfileinfo, $infoline);
					installer::exiter::exit_program("ERROR: Could not exclude tables from merge file: $completedest !", "merge_mergemodules_into_msi_database");
				}
				else
				{
					$infoline = "Success: Executed $systemcall successfully!\n";
					push( @installer::globals::logfileinfo, $infoline);
				}

				# Determining  files
				my $idtfilename = "File.idt"; # must exist
				if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
				my $filecontent = installer::files::read_file($idtfilename);
				my @file_idt_content = ();
				my $filecounter = 0;
				my %mergefilesequence = ();
				for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
				{
					if ( $i <= 2 ) { next; }						# ignoring first three lines
					if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
					$filecounter++;
					push(@file_idt_content, ${$filecontent}[$i]);
					if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(\d+?)\s*$/ )
					{
						my $filename = $1;
						my $filesequence = $8;
						$mergefilesequence{$filename} = $filesequence;
					}
					else
					{
						my $linecount = $i + 1;
						installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "merge_mergemodules_into_msi_database");
					}
				}

				# Determining components
				$idtfilename = "Component.idt"; # must exist
				if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
				$filecontent = installer::files::read_file($idtfilename);
				my %componentnames = ();
				for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
				{
					if ( $i <= 2 ) { next; }						# ignoring first three lines
					if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
					if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $componentnames{$1} = 1; } 
				}

				# Determining directories
				$idtfilename = "Directory.idt";  # must exist
				if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: File \"$idtfilename\" not found in directory \"$workdir\" !", "merge_mergemodules_into_msi_database"); }
				$filecontent = installer::files::read_file($idtfilename);
				my %mergedirectories = ();
				for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
				{
					if ( $i <= 2 ) { next; }						# ignoring first three lines
					if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
					if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergedirectories{$1} = 1; } 
				}

				# Determining assemblies
				$idtfilename = "MsiAssembly.idt"; # does not need to exist
				my $hasmsiassemblies = 0;
				my %mergeassemblies = ();
				if ( -f $idtfilename )
				{
					$filecontent = installer::files::read_file($idtfilename);
					$hasmsiassemblies = 1;
					for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
					{
						if ( $i <= 2 ) { next; }						# ignoring first three lines
						if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
						if ( ${$filecontent}[$i] =~ /^\s*(\S+)\s+/ ) { $mergeassemblies{$1} = 1; } 
					}
				}
				
				# It is possible, that other tables have to be checked here. This happens, if tables in the
				# merge module have to know the "Feature" or the "Directory", under which the content of the 
				# msm file is integrated into the msi database. 

				# Determining name of cabinet file in installation set
				my $cabfilename = $mergemodule->{'Cabfilename'};
				installer::packagelist::resolve_packagevariables(\$cabfilename, $allvariables, 0);
				
				# Analyzing styles
				# Flag REMOVE_FILE_TABLE is required for msvc9 Merge-Module, because otherwise msidb.exe 
				# fails during integration of msm file into msi database.
				
				my $styles = "";
				my $removefiletable = 0;
				if ( $mergemodule->{'Styles'} ) { $styles = $mergemodule->{'Styles'}; }
				if ( $styles =~ /\bREMOVE_FILE_TABLE\b/ ) { $removefiletable = 1; }

				if ( $removefiletable )
				{
					my $removeworkdir = $workdir . $installer::globals::separator . "remove_file_idt";
					if ( ! -d $removeworkdir ) { installer::systemactions::create_directory($removeworkdir); }
					my $completeremovedest = $removeworkdir . $installer::globals::separator . $filename;
					installer::systemactions::copy_one_file($completedest, $completeremovedest);
					if ( ! -f $completeremovedest ) { installer::exiter::exit_program("ERROR: msm file not found: $completeremovedest !", "merge_mergemodules_into_msi_database"); }

					# Unpacking msm file
					if ( $^O =~ /cygwin/i ) {
						# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
						my $localcompleteremovedest = $completeremovedest;
						my $localremoveworkdir = $removeworkdir;
						$localcompleteremovedest =~ s/\//\\\\/g;
						$localremoveworkdir =~ s/\//\\\\/g;
						$systemcall = $msidb . " -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -e \\\*";					
					}
					else
					{
						$systemcall = $msidb . " -d " . $completeremovedest . " -f " . $removeworkdir . " -e \*";
					}

					$returnvalue = system($systemcall);

					my $idtfilename = $removeworkdir . $installer::globals::separator . "File.idt";
					if ( -f $idtfilename ) { unlink $idtfilename; }
					unlink $completeremovedest;

					# Packing msm file without "File.idt"
					if ( $^O =~ /cygwin/i ) {
						# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
						my $localcompleteremovedest = $completeremovedest;
						my $localremoveworkdir = $removeworkdir;
						$localcompleteremovedest =~ s/\//\\\\/g;
						$localremoveworkdir =~ s/\//\\\\/g;
						$systemcall = $msidb . " -c -d " . $localcompleteremovedest . " -f " . $localremoveworkdir . " -i \\\*";					
					}
					else
					{
						$systemcall = $msidb . " -c -d " . $completeremovedest . " -f " . $removeworkdir . " -i \*";
					}
					$returnvalue = system($systemcall);

					# Using this msm file for merging
					if ( -f $completeremovedest ) { $completedest = $completeremovedest; }
					else { installer::exiter::exit_program("ERROR: Could not find msm file without File.idt: $completeremovedest !", "merge_mergemodules_into_msi_database"); }
				}

				# Saving MergeModule info				
				
				my %onemergemodulehash = ();
				$onemergemodulehash{'mergefilepath'} = $completedest;
				$onemergemodulehash{'workdir'} = $workdir;
				$onemergemodulehash{'cabinetfile'} = $workdir . $installer::globals::separator . $cabinetfile;
				$onemergemodulehash{'filenumber'} = $filecounter;
				$onemergemodulehash{'componentnames'} = \%componentnames;
				$onemergemodulehash{'cabfilename'} = $cabfilename;
				$onemergemodulehash{'feature'} = $mergemodule->{'Feature'};
				$onemergemodulehash{'rootdir'} = $mergemodule->{'RootDir'};
				$onemergemodulehash{'name'} = $mergemodule->{'Name'};
				$onemergemodulehash{'mergefilesequence'} = \%mergefilesequence;
				$onemergemodulehash{'mergeassemblies'} = \%mergeassemblies;
				$onemergemodulehash{'mergedirectories'} = \%mergedirectories;
				$onemergemodulehash{'hasmsiassemblies'} = $hasmsiassemblies;
				$onemergemodulehash{'removefiletable'} = $removefiletable;
				$onemergemodulehash{'fileidtcontent'} = \@file_idt_content;
			
				$installer::globals::mergemodules{$mergegid} = \%onemergemodulehash;
				
				# Collecting all cab files, to copy them into installation set
				$installer::globals::copy_msm_files{$cabfilename} = $onemergemodulehash{'cabinetfile'};

				chdir($from);
			}

			$infoline = "All Merge Modules successfully analyzed\n";
			push( @installer::globals::logfileinfo, $infoline);

			$installer::globals::mergemodules_analyzed = 1;
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Analyzing MergeModules, stop");

			$infoline = "\n";
			push( @installer::globals::logfileinfo, $infoline);
		}

		# 2. Change msi database (has to be done for every msi database -> for every language)
		#   a. Merge msm file into msi database: msidb.exe -d <msifile> -m <mergefile>
		#   b. Extracting tables from msi database: msidb.exe -d <msifile> -f <directory> -e File Media, ...
		#   c. Changing content of msi database in tables: File, Media, Directory, FeatureComponent
		#   d. Including tables into msi database: msidb.exe -d <msifile> -f <directory> -i File Media, ...
		#   e. Copying cabinet file into installation set (later)

		my $counter = 0;
		my $mergemodulegid;
		foreach $mergemodulegid (keys %installer::globals::mergemodules)
		{
			my $mergemodulehash = $installer::globals::mergemodules{$mergemodulegid};
			$counter++;
			
			installer::logger::include_header_into_logfile("Merging Module: $mergemodulehash->{'name'}");
			installer::logger::print_message( "\t... $mergemodulehash->{'name'} ... \n" );

			$msifilename = installer::converter::make_path_conform($msifilename);
			my $workdir = $msifilename;
			installer::pathanalyzer::get_path_from_fullqualifiedname(\$workdir);

			# changing directory
			my $from = cwd();
			my $to = $workdir;
			chdir($to);

			# Saving original msi database
			installer::systemactions::copy_one_file($msifilename, "$msifilename\.$counter");
			
			# Merging msm file, this is the "real" merge command

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before merging database");

			if ( $^O =~ /cygwin/i ) {
				# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
				my $localmergemodulepath = $mergemodulehash->{'mergefilepath'};
				my $localmsifilename = $msifilename;
				$localmergemodulepath =~ s/\//\\\\/g;
				$localmsifilename =~ s/\//\\\\/g;
				$systemcall = $msidb . " -d " . $localmsifilename . " -m " . $localmergemodulepath;					
			}
			else
			{
				$systemcall = $msidb . " -d " . $msifilename . " -m " . $mergemodulehash->{'mergefilepath'};
			}
			$returnvalue = system($systemcall);

			$infoline = "Systemcall: $systemcall\n";
			push( @installer::globals::logfileinfo, $infoline);

			if ($returnvalue)
			{
				$infoline = "ERROR: Could not execute $systemcall . Returnvalue: $returnvalue!\n";
				push( @installer::globals::logfileinfo, $infoline);
				installer::exiter::exit_program("ERROR: Could not merge msm file into database: $mergemodulehash->{'mergefilepath'} !", "merge_mergemodules_into_msi_database");
			}
			else
			{
				$infoline = "Success: Executed $systemcall successfully!\n";
				push( @installer::globals::logfileinfo, $infoline);
			}

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: After merging database");
			
			# Saving original idt files
			if ( -f "File.idt" ) { installer::systemactions::rename_one_file("File.idt", "File.idt.$counter"); }
			if ( -f "Media.idt" ) { installer::systemactions::rename_one_file("Media.idt", "Media.idt.$counter"); }
			if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Directory.idt.$counter"); }
			if ( -f "Director.idt" ) { installer::systemactions::rename_one_file("Director.idt", "Director.idt.$counter"); }
			if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureComponents.idt.$counter"); }
			if ( -f "FeatureC.idt" ) { installer::systemactions::rename_one_file("FeatureC.idt", "FeatureC.idt.$counter"); }
			if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssembly.idt.$counter"); }
			if ( -f "MsiAssem.idt" ) { installer::systemactions::rename_one_file("MsiAssem.idt", "MsiAssem.idt.$counter"); }
			
			# Extracting tables

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before extracting tables");
			
			my $workingtables = "File Media Directory FeatureComponents"; # required tables
			# Optional tables can be added now
			if ( $mergemodulehash->{'hasmsiassemblies'} ) { $workingtables = $workingtables . " MsiAssembly"; }

			# Table "Feature" has to be exported, but it is not necessary to import it.
			if ( $^O =~ /cygwin/i ) {
				# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
				my $localmsifilename = $msifilename;
				my $localworkdir = $workdir;
				$localmsifilename =~ s/\//\\\\/g;
				$localworkdir =~ s/\//\\\\/g;
				$systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $workingtables;					
			}
			else
			{
				$systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $workingtables;
			}
			$returnvalue = system($systemcall);

			$infoline = "Systemcall: $systemcall\n";
			push( @installer::globals::logfileinfo, $infoline);

			if ($returnvalue)
			{
				$infoline = "ERROR: Could not execute $systemcall !\n";
				push( @installer::globals::logfileinfo, $infoline);
				installer::exiter::exit_program("ERROR: Could not exclude tables from msi database: $msifilename !", "merge_mergemodules_into_msi_database");
			}
			else
			{
				$infoline = "Success: Executed $systemcall successfully!\n";
				push( @installer::globals::logfileinfo, $infoline);
			}

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: After extracting tables");

			# Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
			# creates idt-files, that have long names.

			if ( -f "Directory.idt" ) { installer::systemactions::rename_one_file("Directory.idt", "Director.idt"); }
			if ( -f "FeatureComponents.idt" ) { installer::systemactions::rename_one_file("FeatureComponents.idt", "FeatureC.idt"); }
			if ( -f "MsiAssembly.idt" ) { installer::systemactions::rename_one_file("MsiAssembly.idt", "MsiAssem.idt"); }
		
			# Changing content of tables: File, Media, Directory, FeatureComponent, MsiAssembly
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Media table");
			change_media_table($mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids);
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing File table");
			$filesref = change_file_table($mergemodulehash, $workdir, $allupdatesequences, $includepatharrayref, $filesref, $mergemodulegid);
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing FeatureComponent table");
			change_featurecomponent_table($mergemodulehash, $workdir);
			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing Directory table");
			change_directory_table($mergemodulehash, $workdir);
			if ( $mergemodulehash->{'hasmsiassemblies'} )
			{
				installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing MsiAssembly table");
				change_msiassembly_table($mergemodulehash, $workdir);
			}

			# msidb.exe does not merge InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence. Instead it creates
			# new tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence that need to be
			# merged into the three ExecuteSequences with the following process (also into InstallUISequence.idt). 
			
			# Saving original idt files
			if ( -f "InstallE.idt" ) { installer::systemactions::rename_one_file("InstallE.idt", "InstallE.idt.$counter"); }
			if ( -f "InstallU.idt" ) { installer::systemactions::rename_one_file("InstallU.idt", "InstallU.idt.$counter"); }
			if ( -f "AdminExe.idt" ) { installer::systemactions::rename_one_file("AdminExe.idt", "AdminExe.idt.$counter"); }
			if ( -f "AdvtExec.idt" ) { installer::systemactions::rename_one_file("AdvtExec.idt", "AdvtExec.idt.$counter"); }
			if ( -f "ModuleInstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleInstallExecuteSequence.idt", "ModuleInstallExecuteSequence.idt.$counter"); }
			if ( -f "ModuleAdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdminExecuteSequence.idt", "ModuleAdminExecuteSequence.idt.$counter"); }
			if ( -f "ModuleAdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("ModuleAdvtExecuteSequence.idt", "ModuleAdvtExecuteSequence.idt.$counter"); }
			
			# Extracting tables			
			my $moduleexecutetables = "ModuleInstallExecuteSequence ModuleAdminExecuteSequence ModuleAdvtExecuteSequence"; # new tables
			my $executetables = "InstallExecuteSequence InstallUISequence AdminExecuteSequence AdvtExecuteSequence"; # tables to be merged


			if ( $^O =~ /cygwin/i ) {
				# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
				my $localmsifilename = $msifilename;
				my $localworkdir = $workdir;
				$localmsifilename =~ s/\//\\\\/g;
				$localworkdir =~ s/\//\\\\/g;
				$systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $moduleexecutetables;					
			}
			else
			{
				$systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $moduleexecutetables;
			}
			$returnvalue = system($systemcall);
			
			if ( $^O =~ /cygwin/i ) {
				# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
				my $localmsifilename = $msifilename;
				my $localworkdir = $workdir;
				$localmsifilename =~ s/\//\\\\/g;
				$localworkdir =~ s/\//\\\\/g;
				$systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -e " . "Feature " . $executetables;					
			}
			else
			{
				$systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -e " . "Feature " . $executetables;
			}
			$returnvalue = system($systemcall);

			# Using 8+3 table names, that are used, when tables are integrated into database. The export of tables
			# creates idt-files, that have long names.

			if ( -f "InstallExecuteSequence.idt" ) { installer::systemactions::rename_one_file("InstallExecuteSequence.idt", "InstallE.idt"); }
			if ( -f "InstallUISequence.idt" ) { installer::systemactions::rename_one_file("InstallUISequence.idt", "InstallU.idt"); }
			if ( -f "AdminExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdminExecuteSequence.idt", "AdminExe.idt"); }
			if ( -f "AdvtExecuteSequence.idt" ) { installer::systemactions::rename_one_file("AdvtExecuteSequence.idt", "AdvtExec.idt"); }
		
			# Merging content of tables ModuleInstallExecuteSequence, ModuleAdminExecuteSequence and ModuleAdvtExecuteSequence
			# into tables InstallExecuteSequence, AdminExecuteSequence and AdvtExecuteSequence
			if ( -f "ModuleInstallExecuteSequence.idt" )
			{
				installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallExecuteSequence table");
				change_executesequence_table($mergemodulehash, $workdir, "InstallE.idt", "ModuleInstallExecuteSequence.idt");
				installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing InstallUISequence table");
				change_executesequence_table($mergemodulehash, $workdir, "InstallU.idt", "ModuleInstallExecuteSequence.idt");
			}

			if ( -f "ModuleAdminExecuteSequence.idt" )
			{
				installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdminExecuteSequence table");
				change_executesequence_table($mergemodulehash, $workdir, "AdminExe.idt", "ModuleAdminExecuteSequence.idt");
			}
			
			if ( -f "ModuleAdvtExecuteSequence.idt" )
			{
				installer::logger::include_timestamp_into_logfile("\nPerformance Info: Changing AdvtExecuteSequence table");
				change_executesequence_table($mergemodulehash, $workdir, "AdvtExec.idt", "ModuleAdvtExecuteSequence.idt");
			}

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: All tables edited");
			
			# Including tables into msi database

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: Before including tables");

			if ( $^O =~ /cygwin/i ) {
				# msidb.exe really wants backslashes. (And double escaping because system() expands the string.)
				my $localmsifilename = $msifilename;
				my $localworkdir = $workdir;
				$localmsifilename =~ s/\//\\\\/g;
				$localworkdir =~ s/\//\\\\/g;
				$systemcall = $msidb . " -d " . $localmsifilename . " -f " . $localworkdir . " -i " . $workingtables. " " . $executetables;
			}
			else
			{
				$systemcall = $msidb . " -d " . $msifilename . " -f " . $workdir . " -i " . $workingtables. " " . $executetables;
			}
			$returnvalue = system($systemcall);

			$infoline = "Systemcall: $systemcall\n";
			push( @installer::globals::logfileinfo, $infoline);

			if ($returnvalue)
			{
				$infoline = "ERROR: Could not execute $systemcall !\n";
				push( @installer::globals::logfileinfo, $infoline);
				installer::exiter::exit_program("ERROR: Could not include tables into msi database: $msifilename !", "merge_mergemodules_into_msi_database");
			}
			else
			{
				$infoline = "Success: Executed $systemcall successfully!\n";
				push( @installer::globals::logfileinfo, $infoline);
			}

			installer::logger::include_timestamp_into_logfile("\nPerformance Info: After including tables");

			chdir($from);		
		}

		if ( ! $installer::globals::mergefiles_added_into_collector ) { $installer::globals::mergefiles_added_into_collector = 1; } # Now all mergemodules are merged for one language.

		installer::logger::include_timestamp_into_logfile("\nPerformance Info: MergeModule into msi database, stop");
	}
	
	return $filesref;
}

#########################################################################
# Analyzing the content of the media table.
#########################################################################

sub analyze_media_file
{
	my ($filecontent, $workdir) = @_;
	
	my %filehash = ();
	my $linecount = 0;
	my $counter = 0;
	my $filename = "Media.idt";
	
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.+?)\t(.*?)\s*$/ )
		{
			my %line = ();
			# Format: DiskId	LastSequence	DiskPrompt	Cabinet	VolumeLabel	Source
			$line{'DiskId'} = $1;
			$line{'LastSequence'} = $2;
			$line{'DiskPrompt'} = $3;
			$line{'Cabinet'} = $4;
			$line{'VolumeLabel'} = $5;
			$line{'Source'} = $6;
		
			$counter++;	
			$filehash{$counter} = \%line;
		}
		else
		{
			$linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$filename\" in \"$workdir\" (line $linecount) !", "analyze_media_file");	
		}
	}
	
	return \%filehash;
}

#########################################################################
# Setting the DiskID for the new cabinet file
#########################################################################

sub get_diskid
{
	my ($mediafile, $allupdatediskids, $cabfilename) = @_;

	my $diskid = 0;
	my $line;
	
	if (( $installer::globals::updatedatabase ) && ( exists($allupdatediskids->{$cabfilename}) ))
	{
		$diskid = $allupdatediskids->{$cabfilename};
	}
	else
	{
		foreach $line ( keys %{$mediafile} )
		{
			if ( $mediafile->{$line}->{'DiskId'} > $diskid ) { $diskid = $mediafile->{$line}->{'DiskId'}; }	
		}

		$diskid++;
	} 
	
	return $diskid;	
}

#########################################################################
# Setting the global LastSequence variable
#########################################################################

sub set_current_last_sequence
{
	my ($mediafile) = @_;

	my $lastsequence = 0;
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( $mediafile->{$line}->{'LastSequence'} > $lastsequence ) { $lastsequence = $mediafile->{$line}->{'LastSequence'}; }	
	} 

	$installer::globals::lastsequence_before_merge = $lastsequence;	
}

#########################################################################
# Setting the LastSequence for the new cabinet file
#########################################################################

sub get_lastsequence
{
	my ($mergemodulehash, $allupdatelastsequences) = @_;
	
	my $lastsequence = 0;
	
	if (( $installer::globals::updatedatabase ) && ( exists($allupdatelastsequences->{$mergemodulehash->{'cabfilename'}}) ))
	{
		$lastsequence = $allupdatelastsequences->{$mergemodulehash->{'cabfilename'}};
	}
	else
	{
		$lastsequence = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};
	}
		
	return $lastsequence;
}

#########################################################################
# Setting the DiskPrompt for the new cabinet file
#########################################################################

sub get_diskprompt
{
	my ($mediafile) = @_;

	my $diskprompt = "";
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( exists($mediafile->{$line}->{'DiskPrompt'}) )
		{
			$diskprompt = $mediafile->{$line}->{'DiskPrompt'};
			last;
		}
	} 
	
	return $diskprompt;	
}

#########################################################################
# Setting the VolumeLabel for the new cabinet file
#########################################################################

sub get_volumelabel
{
	my ($mediafile) = @_;

	my $volumelabel = "";
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( exists($mediafile->{$line}->{'VolumeLabel'}) )
		{
			$volumelabel = $mediafile->{$line}->{'VolumeLabel'};
			last;
		}
	} 
	
	return $volumelabel;	
}

#########################################################################
# Setting the Source for the new cabinet file
#########################################################################

sub get_source
{
	my ($mediafile) = @_;

	my $source = "";
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( exists($mediafile->{$line}->{'Source'}) )
		{
			$diskprompt = $mediafile->{$line}->{'Source'};
			last;
		}
	} 
	
	return $source;	
}

#########################################################################
# For each Merge Module one new line has to be included into the
# media table.
#########################################################################

sub create_new_media_line
{
	my ($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids) = @_;

	my $diskid = get_diskid($mediafile, $allupdatediskids, $mergemodulehash->{'cabfilename'});
	my $lastsequence = get_lastsequence($mergemodulehash, $allupdatelastsequences);
	my $diskprompt = get_diskprompt($mediafile);
	my $cabinet = $mergemodulehash->{'cabfilename'};
	my $volumelabel = get_volumelabel($mediafile);
	my $source = get_source($mediafile);

	if ( $installer::globals::include_cab_in_msi ) { $cabinet = "\#" . $cabinet; }

	my $newline = "$diskid\t$lastsequence\t$diskprompt\t$cabinet\t$volumelabel\t$source\n";

	return $newline;	
}

#########################################################################
# Setting the last diskid in media table.
#########################################################################

sub get_last_diskid
{
	my ($mediafile) = @_;

	my $lastdiskid = 0;
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( $mediafile->{$line}->{'DiskId'} > $lastdiskid ) { $lastdiskid = $mediafile->{$line}->{'DiskId'}; }	
	} 

	return $lastdiskid;
}

#########################################################################
# Setting global variable for last cab file name.
#########################################################################

sub set_last_cabfile_name
{
	my ($mediafile, $lastdiskid) = @_;
	
	my $line;
	foreach $line ( keys %{$mediafile} )
	{
		if ( $mediafile->{$line}->{'DiskId'} == $lastdiskid ) { $installer::globals::lastcabfilename = $mediafile->{$line}->{'Cabinet'}; }	
	}
	my $infoline = "Setting last cabinet file: $installer::globals::lastcabfilename\n";
	push( @installer::globals::logfileinfo, $infoline);
}

#########################################################################
# In the media table the new cabinet file has to be added or the
# number of the last cabinet file has to be increased.
#########################################################################

sub change_media_table
{
	my ( $mergemodulehash, $workdir, $mergemodulegid, $allupdatelastsequences, $allupdatediskids ) = @_;

	my $infoline = "Changing content of table \"Media\"\n";
	push( @installer::globals::logfileinfo, $infoline);

	my $filename = "Media.idt";
	if ( ! -f $filename ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$workdir\" !", "change_media_table"); }

	my $filecontent = installer::files::read_file($filename);
	my $mediafile = analyze_media_file($filecontent, $workdir);
	set_current_last_sequence($mediafile);

	if ( $installer::globals::fix_number_of_cab_files )
	{
		# Determining the line with the highest sequencenumber. That file needs to be updated.
		my $lastdiskid = get_last_diskid($mediafile);
		if ( $installer::globals::lastcabfilename eq "" ) { set_last_cabfile_name($mediafile, $lastdiskid); }
		my $newmaxsequencenumber = $installer::globals::lastsequence_before_merge + $mergemodulehash->{'filenumber'};

		for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
		{
			if ( $i <= 2 ) { next; }						# ignoring first three lines
			if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
			if ( ${$filecontent}[$i] =~ /^\s*(\Q$lastdiskid\E\t)\Q$installer::globals::lastsequence_before_merge\E(\t.*)$/ )
			{
				my $start = $1;
				my $final = $2;
				$infoline = "Merge: Old line in media table: ${$filecontent}[$i]\n";
				push( @installer::globals::logfileinfo, $infoline);
				my $newline = $start . $newmaxsequencenumber . $final . "\n";
				${$filecontent}[$i] = $newline;
				$infoline = "Merge: Changed line in media table: ${$filecontent}[$i]\n";
				push( @installer::globals::logfileinfo, $infoline);
			}
		}
	}
	else
	{
		# the new line is identical for all localized databases, but has to be created for each MergeModule ($mergemodulegid)
		if ( ! exists($installer::globals::merge_media_line{$mergemodulegid}) )
		{	
			$installer::globals::merge_media_line{$mergemodulegid} = create_new_media_line($mergemodulehash, $mediafile, $allupdatelastsequences, $allupdatediskids);
		}

		$infoline = "Adding line: $installer::globals::merge_media_line{$mergemodulegid}\n";
		push( @installer::globals::logfileinfo, $infoline);
	
		# adding new line
		push(@{$filecontent}, $installer::globals::merge_media_line{$mergemodulegid});
	}
	
	# saving file
	installer::files::save_file($filename, $filecontent);
}

#########################################################################
# Putting the directory table content into a hash.
#########################################################################

sub analyze_directorytable_file
{
	my ($filecontent, $idtfilename) = @_;
	
	my %dirhash = ();	
	# Iterating over the file content
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
		{
			my %line = ();
			# Format: Directory	Directory_Parent	DefaultDir
			$line{'Directory'} = $1;
			$line{'Directory_Parent'} = $2;
			$line{'DefaultDir'} = $3;
			$line{'linenumber'} = $i; # saving also the line number for direct access
			
			my $uniquekey = $line{'Directory'};
			$dirhash{$uniquekey} = \%line;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_directorytable_file");	
		}
	}
	
	return \%dirhash;
}

#########################################################################
# Putting the msi assembly table content into a hash.
#########################################################################

sub analyze_msiassemblytable_file
{
	my ($filecontent, $idtfilename) = @_;
	
	my %assemblyhash = ();	
	# Iterating over the file content
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\s*$/ )
		{
			my %line = ();
			# Format: Component_	Feature_	File_Manifest	File_Application	Attributes
			$line{'Component'} = $1;
			$line{'Feature'} = $2;
			$line{'File_Manifest'} = $3;
			$line{'File_Application'} = $4;
			$line{'Attributes'} = $5;
			$line{'linenumber'} = $i; # saving also the line number for direct access
			
			my $uniquekey = $line{'Component'};
			$assemblyhash{$uniquekey} = \%line;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_msiassemblytable_file");	
		}
	}
	
	return \%assemblyhash;
}

#########################################################################
# Putting the file table content into a hash.
#########################################################################

sub analyze_filetable_file
{
	my ( $filecontent, $idtfilename ) = @_;

	my %filehash = ();	
	# Iterating over the file content
	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.+?)\t(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.+?)\s*$/ )
		{
			my %line = ();
			# Format: File	Component_	FileName	FileSize	Version	Language	Attributes	Sequence
			$line{'File'} = $1;
			$line{'Component'} = $2;
			$line{'FileName'} = $3;
			$line{'FileSize'} = $4;
			$line{'Version'} = $5;
			$line{'Language'} = $6;
			$line{'Attributes'} = $7;
			$line{'Sequence'} = $8;
			$line{'linenumber'} = $i; # saving also the line number for direct access
			
			my $uniquekey = $line{'File'};
			$filehash{$uniquekey} = \%line;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "analyze_filetable_file");	
		}
	}
	
	return \%filehash;
}

#########################################################################
# Creating a new line for the directory table.
#########################################################################

sub get_new_line_for_directory_table
{
	my ($dir) = @_;
	
	my $newline = "$dir->{'Directory'}\t$dir->{'Directory_Parent'}\t$dir->{'DefaultDir'}\n";

	return $newline;
}

#########################################################################
# Creating a new line for the file table.
#########################################################################

sub get_new_line_for_file_table
{
	my ($file) = @_;
	
	my $newline = "$file->{'File'}\t$file->{'Component'}\t$file->{'FileName'}\t$file->{'FileSize'}\t$file->{'Version'}\t$file->{'Language'}\t$file->{'Attributes'}\t$file->{'Sequence'}\n";

	return $newline;
}

#########################################################################
# Creating a new line for the msiassembly table.
#########################################################################

sub get_new_line_for_msiassembly_table
{
	my ($assembly) = @_;
	
	my $newline = "$assembly->{'Component'}\t$assembly->{'Feature'}\t$assembly->{'File_Manifest'}\t$assembly->{'File_Application'}\t$assembly->{'Attributes'}\n";

	return $newline;
}

#########################################################################
# Sorting the files collector, if there are files, following
# the merge module files.
#########################################################################

sub sort_files_collector_for_sequence
{
	my ($filesref) = @_;

	my @sortarray = ();
	my %helphash = ();
	
	for ( my $i = 0; $i <= $#{$filesref}; $i++ )
	{
		my $onefile = ${$filesref}[$i];
		if ( ! exists($onefile->{'sequencenumber'}) ) { installer::exiter::exit_program("ERROR: Could not find sequencenumber for file: $onefile->{'uniquename'} !", "sort_files_collector_for_sequence"); }
		my $sequence = $onefile->{'sequencenumber'};
		$helphash{$sequence} = $onefile;
	}
	
	foreach my $seq ( sort { $a <=> $b } keys %helphash ) { push(@sortarray, $helphash{$seq}); }
	
	return \@sortarray;
}

#########################################################################
# In the file table "Sequence" and "Attributes" have to be changed.
#########################################################################

sub change_file_table
{
	my ($mergemodulehash, $workdir, $allupdatesequenceshashref, $includepatharrayref, $filesref, $mergemodulegid) = @_;

	my $infoline = "Changing content of table \"File\"\n";
	push( @installer::globals::logfileinfo, $infoline);

	my $idtfilename = "File.idt";
	if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_file_table"); }

	my $filecontent = installer::files::read_file($idtfilename);
	
	# If File.idt needed to be removed before the msm database was merged into the msi database,
	# now it is time to add the content into File.idt
	if ( $mergemodulehash->{'removefiletable'} )
	{
		for ( my $i = 0; $i <= $#{$mergemodulehash->{'fileidtcontent'}}; $i++ )
		{
			push(@{$filecontent}, ${$mergemodulehash->{'fileidtcontent'}}[$i]);
		}
	}

	# Unpacking the MergeModule.CABinet (only once)
	# Unpacking into temp directory. Warning: expand.exe has problems with very long unpack directories.
	
	my $unpackdir = installer::systemactions::create_directories("cab", "");
	push(@installer::globals::removedirs, $unpackdir);
	$unpackdir = $unpackdir . $installer::globals::separator . $mergemodulegid;

	my %newfileshash = ();
	if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
	{
		if ( ! -d $unpackdir ) { installer::systemactions::create_directory($unpackdir); }

		# Unpack the cab file, so that in can be included into the last office cabinet file. Attention: cararc.exe from cabsdk required.
		# cabarc.exe -o X <fullcabfilepath>
		
		# my $cabarcfilename = "cabarc.exe";
		# my $cabarcfile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$cabarcfilename, $includepatharrayref, 1);
	
		# if ( ! -f $$cabarcfile )
		# {
		#	$cabarcfilename = "CABARC.EXE";
		#	$cabarcfile = installer::scriptitems::get_sourcepath_from_filename_and_includepath(\$cabarcfilename, $includepatharrayref, 1);
		#	if ( ! -f $$cabarcfile )
		#	{
		#		installer::exiter::exit_program("ERROR: cabarc.exe not found !", "change_file_table");
		#	}
		# }
		# my $cabarc = $$cabarcfile;
						
		# changing directory
		my $from = cwd();
		my $to = $mergemodulehash->{'workdir'};
 		if ( $^O =~ /cygwin/i ) {
			$to = qx(cygpath -u "$to");
			chomp $to;
		}

		chdir($to) || die "Could not chdir to \"$to\"\n";

		# Unpack the cab file, so that in can be included into the last office cabinet file.
		# Not using cabarc.exe from cabsdk for unpacking cabinet files, but "expand.exe" that
		# should be available on every Windows system.

		$infoline = "Unpacking cabinet file: $mergemodulehash->{'cabinetfile'}\n";
		push( @installer::globals::logfileinfo, $infoline);

		# Avoid the Cygwin expand command
		my $expandfile = "expand.exe";	# Has to be in the path
 		if ( $^O =~ /cygwin/i ) {
			$expandfile = qx(cygpath -u "$ENV{WINDIR}"/System32/expand.exe);
			chomp $expandfile;
		}

		my $cabfilename = "MergeModule.CABinet";

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

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

		my $returnvalue = system($systemcall);

		$infoline = "Systemcall: $systemcall\n";
		push( @installer::globals::logfileinfo, $infoline);

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

		chdir($from);	
	}	

	# For performance reasons creating a hash with file names and rows
	# The content of File.idt is changed after every merge -> content cannot be saved in global hash
	$merge_filetablehashref = analyze_filetable_file($filecontent, $idtfilename);

	my $attributes = "16384"; # Always

	my $filename;
	foreach $filename (keys %{$mergemodulehash->{'mergefilesequence'}} )
	{
		my $mergefilesequence = $mergemodulehash->{'mergefilesequence'}->{$filename};
		
		if ( ! exists($merge_filetablehashref->{$filename}) ) { installer::exiter::exit_program("ERROR: Could not find file \"$filename\" in \"$idtfilename\" !", "change_file_table"); }
		my $filehash = $merge_filetablehashref->{$filename};
		my $linenumber = $filehash->{'linenumber'};
		
		# <- this line has to be changed concerning "Sequence" and "Attributes"
		$filehash->{'Attributes'} = $attributes;
		
		# If this is an update process, the sequence numbers have to be reused.
		if ( $installer::globals::updatedatabase )
		{
			if ( ! exists($allupdatesequenceshashref->{$filehash->{'File'}}) ) { installer::exiter::exit_program("ERROR: Sequence not defined for file \"$filehash->{'File'}\" !", "change_file_table"); }
			$filehash->{'Sequence'} = $allupdatesequenceshashref->{$filehash->{'File'}};
			# Saving all mergemodule sequence numbers. This is important for creating ddf files
			$installer::globals::allmergemodulefilesequences{$filehash->{'Sequence'}} = 1;
		}
		else
		{
			# Important saved data: $installer::globals::lastsequence_before_merge.
			# This mechanism keeps the correct order inside the new cabinet file.
			$filehash->{'Sequence'} = $filehash->{'Sequence'} + $installer::globals::lastsequence_before_merge;
		} 
		
		my $oldline = ${$filecontent}[$linenumber];
		my $newline = get_new_line_for_file_table($filehash);
		${$filecontent}[$linenumber] = $newline;
		
		$infoline = "Merge, replacing line:\n";
		push( @installer::globals::logfileinfo, $infoline);
		$infoline = "Old: $oldline\n";
		push( @installer::globals::logfileinfo, $infoline);
		$infoline = "New: $newline\n";
		push( @installer::globals::logfileinfo, $infoline);

		# Adding files to the files collector (but only once)
		if (( $installer::globals::fix_number_of_cab_files ) && ( ! $installer::globals::mergefiles_added_into_collector ))
		{
			# If the number of cabinet files is kept constant,
			# all files from the mergemodule cabinet files will
			# be integrated into the last office cabinet file
			# (installer::globals::lastcabfilename).
			# Therefore the files must now be added to the filescollector,
			# so that they will be integrated into the ddf files.
						
			# Problem with very long filenames -> copying to shorter filenames
			my $newfilename = "f" . $filehash->{'Sequence'};
			my $completesource = $unpackdir . $installer::globals::separator . $filehash->{'File'};
			my $completedest = $unpackdir . $installer::globals::separator . $newfilename;
			installer::systemactions::copy_one_file($completesource, $completedest);

			my $locallastcabfilename = $installer::globals::lastcabfilename;
			if ( $locallastcabfilename =~ /^\s*\#/ ) { $locallastcabfilename =~ s/^\s*\#//; }  # removing beginning hashes
						
			# Create new file hash for file collector			
			my %newfile = ();
			$newfile{'sequencenumber'} = $filehash->{'Sequence'};
			$newfile{'assignedsequencenumber'} = $filehash->{'Sequence'};
			$newfile{'cabinet'} = $locallastcabfilename;
			$newfile{'sourcepath'} = $completedest;
			$newfile{'componentname'} = $filehash->{'Component'};
			$newfile{'uniquename'} = $filehash->{'File'};
			$newfile{'Name'} = $filehash->{'File'};

			# Saving in globals sequence hash
			$installer::globals::uniquefilenamesequence{$filehash->{'File'}} = $filehash->{'Sequence'};
			
			if ( ! -f $newfile{'sourcepath'} ) { installer::exiter::exit_program("ERROR: File \"$newfile{'sourcepath'}\" must exist!", "change_file_table"); }
			
			# Collecting all new files. Attention: This files must be included into files collector in correct order!
			$newfileshash{$filehash->{'Sequence'}} = \%newfile;
			# push(@{$filesref}, \%newfile); -> this is not the correct order	
		}
	}
	
	# Now the files can be added to the files collector
	# In the case of an update process, there can be new files, that have to be added after the merge module files.
	# Warning: In multilingual installation sets, the files only have to be added once to the files collector!

	if ( ! $installer::globals::mergefiles_added_into_collector )
	{
		foreach my $localsequence ( sort { $a <=> $b } keys %newfileshash ) { push(@{$filesref}, $newfileshash{$localsequence}); }
		if ( $installer::globals::newfilesexist ) { $filesref = sort_files_collector_for_sequence($filesref); }
		# $installer::globals::mergefiles_added_into_collector = 1; -> Not yet. Only if all mergemodules are merged for one language.
	}

	# Saving the idt file (for every language)
	installer::files::save_file($idtfilename, $filecontent);
	
	return $filesref;	
}

#########################################################################
# Reading the file "Director.idt". The Directory, that is defined in scp
# has to be defined in this table.
#########################################################################

sub collect_directories
{
	my $idtfilename = "Director.idt";
	my $filecontent = installer::files::read_file($idtfilename);

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		# Format: Directory	Directory_Parent	DefaultDir
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
		{
			$installer::globals::merge_alldirectory_hash{$1} = 1;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_directories");	
		}
	}	
}

#########################################################################
# Reading the file "Feature.idt". The Feature, that is defined in scp
# has to be defined in this table.
#########################################################################

sub collect_feature
{
	my $idtfilename = "Feature.idt";
	if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "collect_feature"); }
	my $filecontent = installer::files::read_file($idtfilename);

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		# Format: Feature	Feature_Parent	Title	Description	Display	Level	Directory_	Attributes
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			$installer::globals::merge_allfeature_hash{$1} = 1;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "collect_feature");	
		}
	}	
}

#########################################################################
# In the featurecomponent table, the new connections have to be added.
#########################################################################

sub change_featurecomponent_table
{
	my ($mergemodulehash, $workdir) = @_;
	
	my $infoline = "Changing content of table \"FeatureComponents\"\n";
	push( @installer::globals::logfileinfo, $infoline);

	my $idtfilename = "FeatureC.idt";
	if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_featurecomponent_table"); }

	my $filecontent = installer::files::read_file($idtfilename);

	# Simply adding for each new component one line. The Feature has to be defined in scp project.
	my $feature = $mergemodulehash->{'feature'};

	if ( ! $installer::globals::mergefeaturecollected )
	{
		collect_feature(); # putting content into hash %installer::globals::merge_allfeature_hash
		$installer::globals::mergefeaturecollected = 1;
	}

	if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
	{
		installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_featurecomponent_table");
	}

	my $component;
	foreach $component ( keys %{$mergemodulehash->{'componentnames'}} )
	{
		my $line = "$feature\t$component\n";
		push(@{$filecontent}, $line);
		$infoline = "Adding line: $line\n";
		push( @installer::globals::logfileinfo, $infoline);
	}

	# saving file
	installer::files::save_file($idtfilename, $filecontent);	
}

#########################################################################
# In the directory table, the directory parent has to be changed,
# if it is not TARGETDIR.
#########################################################################

sub change_directory_table
{
	my ($mergemodulehash, $workdir) = @_;

	# directory for MergeModule has to be defined in scp project
	my $scpdirectory = $mergemodulehash->{'rootdir'};
	
	if ( $scpdirectory ne "TARGETDIR" )  # TARGETDIR works fine, when using msidb.exe
	{
		my $infoline = "Changing content of table \"Directory\"\n";
		push( @installer::globals::logfileinfo, $infoline);

		my $idtfilename = "Director.idt";
		if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_directory_table"); }

		my $filecontent = installer::files::read_file($idtfilename);

		if ( ! $installer::globals::mergedirectoriescollected )
		{
			collect_directories(); # putting content into %installer::globals::merge_alldirectory_hash, only first column!
			$installer::globals::mergedirectoriescollected = 1;
		}

		if ( ! exists($installer::globals::merge_alldirectory_hash{$scpdirectory}) )
		{
			installer::exiter::exit_program("ERROR: Unknown directory defined in scp: \"$scpdirectory\" . Not defined in table \"Directory\" !", "change_directory_table");
		}

		# If the definition in scp is okay, now the complete content of "Director.idt" can be analyzed
		my $merge_directorytablehashref = analyze_directorytable_file($filecontent, $idtfilename);

		my $directory;
		foreach $directory (keys %{$mergemodulehash->{'mergedirectories'}} )
		{
			if ( ! exists($merge_directorytablehashref->{$directory}) ) { installer::exiter::exit_program("ERROR: Could not find directory \"$directory\" in \"$idtfilename\" !", "change_directory_table"); }
			my $dirhash = $merge_directorytablehashref->{$directory};
			my $linenumber = $dirhash->{'linenumber'};
		
			# <- this line has to be changed concerning "Directory_Parent",
			# if the current value is "TARGETDIR", which is the default value from msidb.exe
			
			if ( $dirhash->{'Directory_Parent'} eq "TARGETDIR" )
			{
				$dirhash->{'Directory_Parent'} = $scpdirectory;
		
				my $oldline = ${$filecontent}[$linenumber];
				my $newline = get_new_line_for_directory_table($dirhash);
				${$filecontent}[$linenumber] = $newline;
		
				$infoline = "Merge, replacing line:\n";
				push( @installer::globals::logfileinfo, $infoline);
				$infoline = "Old: $oldline\n";
				push( @installer::globals::logfileinfo, $infoline);
				$infoline = "New: $newline\n";
				push( @installer::globals::logfileinfo, $infoline);
			}
		}

		# saving file
		installer::files::save_file($idtfilename, $filecontent);
	}	
}

#########################################################################
# In the msiassembly table, the feature has to be changed.
#########################################################################

sub change_msiassembly_table
{
	my ($mergemodulehash, $workdir) = @_;

	my $infoline = "Changing content of table \"MsiAssembly\"\n";
	push( @installer::globals::logfileinfo, $infoline);

	my $idtfilename = "MsiAssem.idt";
	if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_msiassembly_table"); }

	my $filecontent = installer::files::read_file($idtfilename);

	# feature has to be defined in scp project
	my $feature = $mergemodulehash->{'feature'};
	
	if ( ! $installer::globals::mergefeaturecollected )
	{
		collect_feature();  # putting content into hash %installer::globals::merge_allfeature_hash
		$installer::globals::mergefeaturecollected = 1;
	}

	if ( ! exists($installer::globals::merge_allfeature_hash{$feature}) )
	{
		installer::exiter::exit_program("ERROR: Unknown feature defined in scp: \"$feature\" . Not defined in table \"Feature\" !", "change_msiassembly_table");
	}
	
	my $merge_msiassemblytablehashref = analyze_msiassemblytable_file($filecontent, $idtfilename);

	my $component;
	foreach $component (keys %{$mergemodulehash->{'mergeassemblies'}} )
	{
		if ( ! exists($merge_msiassemblytablehashref->{$component}) ) { installer::exiter::exit_program("ERROR: Could not find component \"$component\" in \"$idtfilename\" !", "change_msiassembly_table"); }
		my $assemblyhash = $merge_msiassemblytablehashref->{$component};
		my $linenumber = $assemblyhash->{'linenumber'};
		
		# <- this line has to be changed concerning "Feature"
		$assemblyhash->{'Feature'} = $feature;
		
		my $oldline = ${$filecontent}[$linenumber];
		my $newline = get_new_line_for_msiassembly_table($assemblyhash);
		${$filecontent}[$linenumber] = $newline;
		
		$infoline = "Merge, replacing line:\n";
		push( @installer::globals::logfileinfo, $infoline);
		$infoline = "Old: $oldline\n";
		push( @installer::globals::logfileinfo, $infoline);
		$infoline = "New: $newline\n";
		push( @installer::globals::logfileinfo, $infoline);
	}
	
	# saving file
	installer::files::save_file($idtfilename, $filecontent);	
}

#########################################################################
# Creating file content hash
#########################################################################

sub make_executeidtcontent_hash
{
	my ($filecontent, $idtfilename) = @_;
	
	my %newhash = ();

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		# Format for all sequence tables: Action	Condition	Sequence
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\s*$/ )
		{
			my %onehash = ();
			$onehash{'Action'} = $1;
			$onehash{'Condition'} = $2;
			$onehash{'Sequence'} = $3;
			$newhash{$onehash{'Action'}} = \%onehash;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");	
		}
	}
	
	return \%newhash;
}

#########################################################################
# Creating file content hash
#########################################################################

sub make_moduleexecuteidtcontent_hash
{
	my ($filecontent, $idtfilename) = @_;
	
	my %newhash = ();

	for ( my $i = 0; $i <= $#{$filecontent}; $i++ )
	{
		if ( $i <= 2 ) { next; }						# ignoring first three lines
		if ( ${$filecontent}[$i] =~ /^\s*$/ ) { next; } # ignoring empty lines
		# Format for all module sequence tables: Action	Sequence	BaseAction	After Condition
		if ( ${$filecontent}[$i] =~ /^\s*(.+?)\t(.*?)\t(.*?)\t(.*?)\t(.*?)\s*$/ )
		{
			my %onehash = ();
			$onehash{'Action'} = $1;
			$onehash{'Sequence'} = $2;
			$onehash{'BaseAction'} = $3;
			$onehash{'After'} = $4;
			$onehash{'Condition'} = $5;
			$newhash{$onehash{'Action'}} = \%onehash;
		}
		else
		{
			my $linecount = $i + 1;
			installer::exiter::exit_program("ERROR: Unknown line format in table \"$idtfilename\" (line $linecount) !", "make_executeidtcontent_hash");	
		}
	}
	
	return \%newhash;
}

#########################################################################
# ExecuteSequence tables need to be merged with
# ModuleExecuteSequence tables created by msidb.exe.
#########################################################################

sub change_executesequence_table
{
	my ($mergemodulehash, $workdir, $idtfilename, $moduleidtfilename) = @_;

	my $infoline = "Changing content of table \"$idtfilename\"\n";
	push( @installer::globals::logfileinfo, $infoline);

	if ( ! -f $idtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$idtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
	if ( ! -f $moduleidtfilename ) { installer::exiter::exit_program("ERROR: Could not find file \"$moduleidtfilename\" in \"$workdir\" !", "change_executesequence_table"); }
	
	# Reading file content
	my $idtfilecontent = installer::files::read_file($idtfilename);
	my $moduleidtfilecontent = installer::files::read_file($moduleidtfilename);
	
	# Converting to hash
	my $idtcontenthash = make_executeidtcontent_hash($idtfilecontent, $idtfilename);
	my $moduleidtcontenthash = make_moduleexecuteidtcontent_hash($moduleidtfilecontent, $moduleidtfilename);
	
	# Merging
	foreach my $action ( keys %{$moduleidtcontenthash} )
	{
		if ( exists($idtcontenthash->{$action}) ) { next; }	# Action already exists, can be ignored
		
		if (( $idtfilename eq "InstallU.idt" ) && ( ! ( $action =~ /^\s*WindowsFolder\./ ))) { next; } # Only "WindowsFolder.*" CustomActions for UI Sequence table
		
		my $actionhashref = $moduleidtcontenthash->{$action};
		if ( $actionhashref->{'Sequence'} ne "" )
		{
			# Format for all sequence tables: Action Condition Sequence
			my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $actionhashref->{'Sequence'} . "\n";
			# Adding to table
			push(@{$idtfilecontent}, $newline);
			# Also adding to hash
			my %idttablehash = ();
			$idttablehash{'Action'} = $actionhashref->{'Action'};
			$idttablehash{'Condition'} = $actionhashref->{'Condition'};
			$idttablehash{'Sequence'} = $actionhashref->{'Sequence'};
			$idtcontenthash->{$action} = \%idttablehash;
			
		}
		else	# no sequence defined, using syntax "BaseAction" and "After"
		{
			my $baseactionname = $actionhashref->{'BaseAction'};
			# If this baseactionname is not defined in execute idt file, it is not possible to merge
			if ( ! exists($idtcontenthash->{$baseactionname}) ) { installer::exiter::exit_program("ERROR: Merge problem: Could not find action \"$baseactionname\" in file \"$idtfilename\" !", "change_executesequence_table"); }
			
			my $baseaction = $idtcontenthash->{$baseactionname};
			my $sequencenumber = $baseaction->{'Sequence'};
			if ( $actionhashref->{'After'} == 1 ) { $sequencenumber = $sequencenumber + 1; }
			else { $sequencenumber = $sequencenumber - 1; }
			
			# Format for all sequence tables: Action Condition Sequence
			my $newline = $actionhashref->{'Action'} . "\t" . $actionhashref->{'Condition'} . "\t" . $sequencenumber . "\n";
			# Adding to table
			push(@{$idtfilecontent}, $newline);
			# Also adding to hash
			my %idttablehash = ();
			$idttablehash{'Action'} = $actionhashref->{'Action'};
			$idttablehash{'Condition'} = $actionhashref->{'Condition'};
			$idttablehash{'Sequence'} = $sequencenumber;
			$idtcontenthash->{$action} = \%idttablehash;		
		}
	} 

	# saving file
	installer::files::save_file($idtfilename, $idtfilecontent);
}


1;
